diff --git a/Gemfile.lock b/Gemfile.lock index 698d330ea..3a53c8c27 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -253,7 +253,10 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.18.1) + json (2.19.2) + json-schema (6.2.0) + addressable (~> 2.8) + bigdecimal (>= 3.1, < 5) jwt (2.2.3) kaminari (1.2.2) activesupport (>= 4.1.0) @@ -280,6 +283,8 @@ GEM net-smtp marcel (1.0.4) matrix (0.4.3) + mcp (0.8.0) + json-schema (>= 4.1) method_source (1.1.0) mini_magick (5.3.1) logger @@ -455,18 +460,19 @@ GEM rspec-support (3.13.7) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.80.1) + rubocop (1.85.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) + mcp (~> 0.6) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.49.0) + rubocop-ast (1.49.1) parser (>= 3.3.7.2) prism (~> 1.7) rubocop-capybara (2.22.1) @@ -488,9 +494,9 @@ GEM rack (>= 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.6.0) + rubocop-rspec (3.9.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) + rubocop (~> 1.81) rubocop-rspec_rails (2.32.0) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) diff --git a/app/controllers/api/school_students_controller.rb b/app/controllers/api/school_students_controller.rb index 430981940..c403fc031 100644 --- a/app/controllers/api/school_students_controller.rb +++ b/app/controllers/api/school_students_controller.rb @@ -82,7 +82,7 @@ def create_batch # should assume the entire student import has failed. def enqueue_batches(students) # Raise if a batch is already in progress for this school. - raise ConcurrencyExceededForSchool if @school.import_in_progress? + raise CreateStudentsJob::ConcurrencyExceededForSchool if @school.import_in_progress? @batch = GoodJob::Batch.new(description: @school.id) @batch.enqueue do diff --git a/app/dashboards/project_dashboard.rb b/app/dashboards/project_dashboard.rb index d9e43c22f..b13e80363 100644 --- a/app/dashboards/project_dashboard.rb +++ b/app/dashboards/project_dashboard.rb @@ -83,6 +83,6 @@ def display_resource(project) end def permitted_attributes - super + [images: []] + super + [{ images: [] }] end end diff --git a/app/jobs/create_students_job.rb b/app/jobs/create_students_job.rb index 18f14c386..7dc5688ca 100644 --- a/app/jobs/create_students_job.rb +++ b/app/jobs/create_students_job.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -class ConcurrencyExceededForSchool < StandardError; end - class CreateStudentsJob < ApplicationJob + class ConcurrencyExceededForSchool < StandardError; end + retry_on StandardError, wait: :polynomially_longer, attempts: 3 do |_job, e| Sentry.capture_exception(e) raise e diff --git a/app/jobs/upload_job.rb b/app/jobs/upload_job.rb index 0f4021cd6..a72de493f 100644 --- a/app/jobs/upload_job.rb +++ b/app/jobs/upload_job.rb @@ -4,10 +4,10 @@ require 'github_api' require 'locales' -class InvalidDirectoryStructureError < StandardError; end -class DataNotFoundError < StandardError; end - class UploadJob < ApplicationJob + class InvalidDirectoryStructureError < StandardError; end + class DataNotFoundError < StandardError; end + retry_on StandardError, wait: :polynomially_longer, attempts: 3 do |_job, e| Sentry.capture_exception(e) raise e diff --git a/app/models/filesystem_project.rb b/app/models/filesystem_project.rb index df0891324..3632ba3e2 100644 --- a/app/models/filesystem_project.rb +++ b/app/models/filesystem_project.rb @@ -61,13 +61,18 @@ def self.component(file, dir) { name:, extension:, content: code, default: } end - def self.file_mime_type(file) - Marcel::MimeType.for(File.open(file), name: File.basename(file)) + def self.file_mime_type(path) + File.open(path) do |file| + Marcel::MimeType.for(file, name: File.basename(path)) + end end def self.media(file, dir) filename = File.basename(file) + # rubocop:disable Style/FileOpen + # This is an issue but we can't easily fix it as the returned IO object is used elsewhere. io = File.open(dir.join(filename).to_s) + # rubocop:enable Style/FileOpen { filename:, io: } end end diff --git a/lib/concepts/school_student/create_batch.rb b/lib/concepts/school_student/create_batch.rb index a7c0d6ef3..ad76b3c28 100644 --- a/lib/concepts/school_student/create_batch.rb +++ b/lib/concepts/school_student/create_batch.rb @@ -3,15 +3,13 @@ module SchoolStudent class Error < StandardError; end - class ConcurrencyExceededForSchool < StandardError; end - class CreateBatch class << self def call(school:, school_students_params:, token:) response = OperationResponse.new response[:job_id] = create_batch(school, school_students_params, token) response - rescue ConcurrencyExceededForSchool => e + rescue CreateStudentsJob::ConcurrencyExceededForSchool => e response[:error] = e response[:error_type] = :job_concurrency_error response diff --git a/lib/tasks/test_seeds.rake b/lib/tasks/test_seeds.rake index 104b598cb..9e03704bd 100644 --- a/lib/tasks/test_seeds.rake +++ b/lib/tasks/test_seeds.rake @@ -2,7 +2,6 @@ require_relative 'seeds_helper' -# rubocop:disable Rails/Output namespace :test_seeds do include SeedsHelper @@ -72,4 +71,3 @@ namespace :test_seeds do end end end -# rubocop:enable Rails/Output diff --git a/spec/concepts/class_member/create_spec.rb b/spec/concepts/class_member/create_spec.rb index e6b67b185..a4744dd98 100644 --- a/spec/concepts/class_member/create_spec.rb +++ b/spec/concepts/class_member/create_spec.rb @@ -48,14 +48,14 @@ it 'assigns the student_id' do response = described_class.call(school_class:, students:, teachers:) - response_students = response[:class_members].select { |member| member.is_a?(ClassStudent) } + response_students = response[:class_members].grep(ClassStudent) expect(response_students.map(&:student_id)).to match_array(student_ids) end it 'assigns the teacher_id' do teacher_ids = teachers.map(&:id) response = described_class.call(school_class:, students:, teachers:) - response_teachers = response[:class_members].select { |member| member.is_a?(ClassTeacher) } + response_teachers = response[:class_members].grep(ClassTeacher) expect(response_teachers.map(&:teacher_id)).to match_array(teacher_ids) end @@ -160,14 +160,14 @@ it 'assigns the successful students' do response = described_class.call(school_class:, students: new_students, teachers:) - response_students = response[:class_members].select { |member| member.is_a?(ClassStudent) } + response_students = response[:class_members].grep(ClassStudent) expect(response_students.map(&:student_id)).to match_array(student_ids) end it 'assigns the successful teachers' do teacher_ids = teachers.map(&:id) response = described_class.call(school_class:, students: new_students, teachers:) - response_teachers = response[:class_members].select { |member| member.is_a?(ClassTeacher) } + response_teachers = response[:class_members].grep(ClassTeacher) expect(response_teachers.map(&:teacher_id)).to match_array(teacher_ids) end diff --git a/spec/features/class_member/creating_a_batch_of_class_members_spec.rb b/spec/features/class_member/creating_a_batch_of_class_members_spec.rb index 95cbabff5..f5ad035ba 100644 --- a/spec/features/class_member/creating_a_batch_of_class_members_spec.rb +++ b/spec/features/class_member/creating_a_batch_of_class_members_spec.rb @@ -166,7 +166,7 @@ end context "with users that don't exist in Profile" do - unknown_user_id = SecureRandom.uuid + let(:unknown_user_id) { SecureRandom.uuid } let(:invalid_params) do { diff --git a/spec/jobs/upload_job_spec.rb b/spec/jobs/upload_job_spec.rb index 2537c188e..61d5bb86a 100644 --- a/spec/jobs/upload_job_spec.rb +++ b/spec/jobs/upload_job_spec.rb @@ -265,7 +265,7 @@ it 'raises DataNotFoundError' do expect do described_class.perform_now(payload) - end.to raise_error(DataNotFoundError) + end.to raise_error(UploadJob::DataNotFoundError) end end diff --git a/spec/lib/profile_api_client_spec.rb b/spec/lib/profile_api_client_spec.rb index 37340a8c4..3a2c40def 100644 --- a/spec/lib/profile_api_client_spec.rb +++ b/spec/lib/profile_api_client_spec.rb @@ -239,7 +239,7 @@ def delete_safeguarding_flag end it 'raises 422 exception with the relevant message if 400 status code is returned' do - response = { errors: [message: 'The password is well dodgy'] } + response = { errors: [{ message: 'The password is well dodgy' }] } stub_request(:post, create_students_url) .to_return(status: 400, body: response.to_json, headers: { 'content-type' => 'application/json' }) @@ -284,7 +284,7 @@ def create_school_student it_behaves_like 'a request that handles an unexpected response status', :post, url: -> { create_students_url }, status: 202 it 'raises 422 exception with the relevant message if 400 status code is returned' do - response = { errors: [message: 'The password is well dodgy'] } + response = { errors: [{ message: 'The password is well dodgy' }] } stub_request(:post, create_students_url) .to_return(status: 400, body: response.to_json, headers: { 'content-type' => 'application/json' }) @@ -356,7 +356,7 @@ def create_school_students it_behaves_like 'a request that handles an unexpected response status', :post, url: -> { create_students_sso_url }, status: 202 it 'raises 422 exception with the relevant message if 400 status code is returned' do - response = { errors: [message: 'The password is well dodgy'] } + response = { errors: [{ message: 'The password is well dodgy' }] } stub_request(:post, create_students_sso_url) .to_return(status: 400, body: response.to_json, headers: { 'content-type' => 'application/json' }) @@ -478,7 +478,7 @@ def list_school_students end it 'raises 422 exception with the relevant message if 400 status code is returned' do - response = { errors: [message: 'The username is well dodgy'] } + response = { errors: [{ message: 'The username is well dodgy' }] } stub_request(:patch, update_student_url) .to_return(status: 400, body: response.to_json, headers: { 'content-type' => 'application/json' })