From 49c5d9607118a8e5609643e4a8690ce4cdc44fea Mon Sep 17 00:00:00 2001 From: Hassan Mir Date: Thu, 5 Dec 2024 15:16:01 +0000 Subject: [PATCH 1/4] Introduce new trs_trn_requests table to replace dqt_trn_requests table with --- app/jobs/update_trs_trn_request_job.rb | 43 ++- app/lib/application_form_status_updater.rb | 10 +- .../fake_data/application_form_generator.rb | 2 +- app/models/application_form.rb | 1 + app/models/trs_trn_request.rb | 30 ++ app/services/award_qts.rb | 2 +- app/services/create_trs_trn_request.rb | 4 +- app/services/destroy_application_form.rb | 2 + .../application_forms/status.html.erb | 2 +- config/analytics.yml | 8 + .../20241205125213_create_trs_trn_requests.rb | 14 + db/schema.rb | 13 +- db/scripts/sanitise.sql | 4 + docs/diagram.pdf | Bin 69690 -> 69770 bytes lib/tasks/fake_data.rake | 1 + spec/factories/trs_trn_requests.rb | 33 ++ spec/jobs/update_trstrn_request_job_spec.rb | 346 ++++++++++++++++-- .../application_form_status_updater_spec.rb | 8 +- spec/models/trstrn_request_spec.rb | 47 +++ spec/services/award_qts_spec.rb | 6 +- spec/services/create_trstrn_request_spec.rb | 6 +- .../services/destroy_application_form_spec.rb | 2 + 22 files changed, 527 insertions(+), 57 deletions(-) create mode 100644 app/models/trs_trn_request.rb create mode 100644 db/migrate/20241205125213_create_trs_trn_requests.rb create mode 100644 spec/factories/trs_trn_requests.rb create mode 100644 spec/models/trstrn_request_spec.rb diff --git a/app/jobs/update_trs_trn_request_job.rb b/app/jobs/update_trs_trn_request_job.rb index eb44385dd9..01cb369a99 100644 --- a/app/jobs/update_trs_trn_request_job.rb +++ b/app/jobs/update_trs_trn_request_job.rb @@ -1,18 +1,31 @@ # frozen_string_literal: true class UpdateTRSTRNRequestJob < ApplicationJob - def perform(dqt_trn_request) - return if dqt_trn_request.complete? + def perform(trs_trn_request) + return if trs_trn_request.complete? - application_form = dqt_trn_request.application_form + application_form = trs_trn_request.application_form - response = fetch_response(dqt_trn_request) + # TODO: We can remove this block once the migration between DQTTRNRequest to TRSTRNRequest + # has fully completed. This job may have quite a few instances in our scheduled queue + # and as a result we need to ensure they switch into using the TRSTRNRequest once we + # migrate all existing DQTTRNRequests into a record within the TRSTRNRequests table. + if trs_trn_request.is_a?(DQTTRNRequest) + if application_form.trs_trn_request + trs_trn_request = application_form.trs_trn_request + else + UpdateTRSTRNRequestJob.set(wait: 1.hour).perform_later(trs_trn_request) + return + end + end + + response = fetch_response(trs_trn_request) - dqt_trn_request.pending! if dqt_trn_request.initial? + trs_trn_request.pending! if trs_trn_request.initial? potential_duplicate = response[:potential_duplicate] - if potential_duplicate != dqt_trn_request.potential_duplicate - dqt_trn_request.update!(potential_duplicate:) + if potential_duplicate != trs_trn_request.potential_duplicate + trs_trn_request.update!(potential_duplicate:) end ApplicationFormStatusUpdater.call(application_form:, user: "DQT") @@ -25,24 +38,24 @@ def perform(dqt_trn_request) access_your_teaching_qualifications_url: response[:access_your_teaching_qualifications_link], ) - dqt_trn_request.complete! + trs_trn_request.complete! end - if dqt_trn_request.pending? - UpdateTRSTRNRequestJob.set(wait: 1.hour).perform_later(dqt_trn_request) + if trs_trn_request.pending? + UpdateTRSTRNRequestJob.set(wait: 1.hour).perform_later(trs_trn_request) end end private - def fetch_response(dqt_trn_request) - if dqt_trn_request.initial? + def fetch_response(trs_trn_request) + if trs_trn_request.initial? TRS::Client::CreateTRNRequest.call( - request_id: dqt_trn_request.request_id, - application_form: dqt_trn_request.application_form, + request_id: trs_trn_request.request_id, + application_form: trs_trn_request.application_form, ) else - TRS::Client::ReadTRNRequest.call(request_id: dqt_trn_request.request_id) + TRS::Client::ReadTRNRequest.call(request_id: trs_trn_request.request_id) end rescue Faraday::Error => e Sentry.configure_scope do |scope| diff --git a/app/lib/application_form_status_updater.rb b/app/lib/application_form_status_updater.rb index 674cb28f94..ba8dff719e 100644 --- a/app/lib/application_form_status_updater.rb +++ b/app/lib/application_form_status_updater.rb @@ -51,7 +51,7 @@ def action_required_by application_form.declined_at.present? || application_form.awarded_at.present? "none" - elsif dqt_trn_request.present? || assessment_in_review? || + elsif trs_trn_request.present? || assessment_in_review? || overdue_further_information || received_further_information "assessor" elsif preliminary_check? || need_to_request_lops? || @@ -76,7 +76,7 @@ def stage application_form.declined_at.present? || application_form.awarded_at.present? "completed" - elsif assessment_in_review? || dqt_trn_request.present? + elsif assessment_in_review? || trs_trn_request.present? "review" elsif preliminary_check? || (teaching_authority_provides_written_statement && waiting_on_lops) @@ -107,8 +107,8 @@ def statuses %w[declined] elsif application_form.awarded_at.present? %w[awarded] - elsif dqt_trn_request.present? - if dqt_trn_request.potential_duplicate? + elsif trs_trn_request.present? + if trs_trn_request.potential_duplicate? %w[potential_duplicate_in_dqt] else %w[awarded_pending_checks] @@ -133,7 +133,7 @@ def statuses end delegate :assessment, - :dqt_trn_request, + :trs_trn_request, :region, :teacher, :teaching_authority_provides_written_statement, diff --git a/app/lib/fake_data/application_form_generator.rb b/app/lib/fake_data/application_form_generator.rb index 7c3b58db38..3b641a34de 100644 --- a/app/lib/fake_data/application_form_generator.rb +++ b/app/lib/fake_data/application_form_generator.rb @@ -504,7 +504,7 @@ def award_application_form(user:) access_your_teaching_qualifications_url = Faker::Internet.url date_generator.travel_to_next_long do - DQTTRNRequest.create!(request_id: SecureRandom.uuid, application_form:) + TRSTRNRequest.create!(request_id: SecureRandom.uuid, application_form:) ApplicationFormStatusUpdater.call(application_form:, user:) end diff --git a/app/models/application_form.rb b/app/models/application_form.rb index b7d1c98013..e8fe17ca84 100644 --- a/app/models/application_form.rb +++ b/app/models/application_form.rb @@ -102,6 +102,7 @@ class ApplicationForm < ApplicationRecord has_many :timeline_events has_one :country, through: :region has_one :dqt_trn_request + has_one :trs_trn_request has_one :assessment has_many :notes, dependent: :destroy diff --git a/app/models/trs_trn_request.rb b/app/models/trs_trn_request.rb new file mode 100644 index 0000000000..9da9cdaa1c --- /dev/null +++ b/app/models/trs_trn_request.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: trs_trn_requests +# +# id :bigint not null, primary key +# potential_duplicate :boolean +# state :string default("initial"), not null +# created_at :datetime not null +# updated_at :datetime not null +# application_form_id :bigint not null +# request_id :uuid not null +# +# Indexes +# +# index_trs_trn_requests_on_application_form_id (application_form_id) +# +# Foreign Keys +# +# fk_rails_... (application_form_id => application_forms.id) +# +class TRSTRNRequest < ApplicationRecord + belongs_to :application_form + + enum :state, + { initial: "initial", pending: "pending", complete: "complete" }, + default: :initial + validates :state, presence: true, inclusion: { in: states.values } +end diff --git a/app/services/award_qts.rb b/app/services/award_qts.rb index a5c0c759e9..47ba05275d 100644 --- a/app/services/award_qts.rb +++ b/app/services/award_qts.rb @@ -19,7 +19,7 @@ def initialize( def call return if application_form.awarded_at.present? - raise InvalidState if application_form.dqt_trn_request.nil? + raise InvalidState if application_form.trs_trn_request.nil? raise MissingTRN if trn.blank? diff --git a/app/services/create_trs_trn_request.rb b/app/services/create_trs_trn_request.rb index 8cac269be9..1069dfea69 100644 --- a/app/services/create_trs_trn_request.rb +++ b/app/services/create_trs_trn_request.rb @@ -10,9 +10,9 @@ def initialize(application_form:, user:) def call request_id = SecureRandom.uuid - dqt_trn_request = DQTTRNRequest.create!(request_id:, application_form:) + trs_trn_request = TRSTRNRequest.create!(request_id:, application_form:) ApplicationFormStatusUpdater.call(application_form:, user:) - UpdateTRSTRNRequestJob.perform_later(dqt_trn_request) + UpdateTRSTRNRequestJob.perform_later(trs_trn_request) end private diff --git a/app/services/destroy_application_form.rb b/app/services/destroy_application_form.rb index ea05998140..8b021a596d 100644 --- a/app/services/destroy_application_form.rb +++ b/app/services/destroy_application_form.rb @@ -11,6 +11,7 @@ def call ActiveRecord::Base.transaction do timeline_events.destroy_all dqt_trn_request&.destroy! + trs_trn_request&.destroy! assessment&.destroy! application_form.destroy! teacher.destroy! unless teacher.application_forms.exists? @@ -25,5 +26,6 @@ def call :teacher, :timeline_events, :dqt_trn_request, + :trs_trn_request, to: :application_form end diff --git a/app/views/assessor_interface/application_forms/status.html.erb b/app/views/assessor_interface/application_forms/status.html.erb index 1469c67818..2824ce05df 100644 --- a/app/views/assessor_interface/application_forms/status.html.erb +++ b/app/views/assessor_interface/application_forms/status.html.erb @@ -25,7 +25,7 @@

You’ve successfully sent your further information request email to the applicant.

<% elsif @view_object.application_form.declined_at.present? %>

The application will be deleted after 90 days unless the applicant chooses to appeal.

-<% elsif @view_object.application_form.dqt_trn_request.present? %> +<% elsif @view_object.application_form.trs_trn_request.present? || @view_object.application_form.dqt_trn_request.present? %>

This status will appear while the award is reconciled with the information in the Database of Qualified Teachers (DQT).

Once these checks are complete, the status will change to ‘Awarded’ and the applicant will receive the email telling them they’ve been awarded QTS.

<% end %> diff --git a/config/analytics.yml b/config/analytics.yml index eb7cc054b1..b154bb13ca 100644 --- a/config/analytics.yml +++ b/config/analytics.yml @@ -357,6 +357,14 @@ - trn - updated_at - uuid + :trs_trn_requests: + - application_form_id + - created_at + - id + - potential_duplicate + - request_id + - state + - updated_at :uploads: - created_at - document_id diff --git a/db/migrate/20241205125213_create_trs_trn_requests.rb b/db/migrate/20241205125213_create_trs_trn_requests.rb new file mode 100644 index 0000000000..86c75edf6d --- /dev/null +++ b/db/migrate/20241205125213_create_trs_trn_requests.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreateTRSTRNRequests < ActiveRecord::Migration[7.2] + def change + create_table :trs_trn_requests do |t| + t.references :application_form, null: false, foreign_key: true + t.uuid :request_id, null: false + t.string :state, null: false, default: "pending" + t.boolean :potential_duplicate + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index f7a66a903a..f9485b66ff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_12_03_141754) do +ActiveRecord::Schema[7.2].define(version: 2024_12_05_125213) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -580,6 +580,16 @@ t.index ["work_history_id"], name: "index_timeline_events_on_work_history_id" end + create_table "trs_trn_requests", force: :cascade do |t| + t.bigint "application_form_id", null: false + t.uuid "request_id", null: false + t.string "state", default: "pending", null: false + t.boolean "potential_duplicate" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["application_form_id"], name: "index_trs_trn_requests_on_application_form_id" + end + create_table "uploads", force: :cascade do |t| t.bigint "document_id", null: false t.boolean "translation", null: false @@ -651,6 +661,7 @@ add_foreign_key "timeline_events", "qualifications" add_foreign_key "timeline_events", "staff", column: "assignee_id" add_foreign_key "timeline_events", "work_histories" + add_foreign_key "trs_trn_requests", "application_forms" add_foreign_key "uploads", "documents" add_foreign_key "work_histories", "application_forms" end diff --git a/db/scripts/sanitise.sql b/db/scripts/sanitise.sql index b24df30c7c..f41a6e0adf 100644 --- a/db/scripts/sanitise.sql +++ b/db/scripts/sanitise.sql @@ -49,6 +49,10 @@ SET -- clear these as the scheduled jobs might run against them DELETE FROM "dqt_trn_requests"; +-- TRSTRNRequest +-- clear these as the scheduled jobs might run against them +DELETE FROM "trs_trn_requests"; + -- EligibilityCheck -- no update required diff --git a/docs/diagram.pdf b/docs/diagram.pdf index 75df017cbbc6f0e9575d5fe14e3ddfc9e1088b72..720e7b88a2cc2fd44df8a20b96b8cb2c9283cf63 100644 GIT binary patch delta 22524 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 XVRCeMa%E-;HaRmiG72RnMNdWwWYHCv delta 22443 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 application_forms.id) +# +FactoryBot.define do + factory :trs_trn_request do + association :application_form, :awarded_pending_checks + + request_id { SecureRandom.uuid } + + trait :potential_duplicate do + potential_duplicate { true } + end + end +end diff --git a/spec/jobs/update_trstrn_request_job_spec.rb b/spec/jobs/update_trstrn_request_job_spec.rb index 722052df2f..bd9de4e204 100644 --- a/spec/jobs/update_trstrn_request_job_spec.rb +++ b/spec/jobs/update_trstrn_request_job_spec.rb @@ -4,9 +4,9 @@ RSpec.describe UpdateTRSTRNRequestJob, type: :job do describe "#perform" do - subject(:perform) { described_class.new.perform(dqt_trn_request) } + subject(:perform) { described_class.new.perform(trs_trn_request) } - let(:application_form) { dqt_trn_request.application_form } + let(:application_form) { trs_trn_request.application_form } let(:teacher) { application_form.teacher } # rubocop:disable Lint/SuppressedException @@ -17,7 +17,7 @@ # rubocop:enable Lint/SuppressedException context "with an initial request" do - let(:dqt_trn_request) { create(:dqt_trn_request, :initial) } + let(:trs_trn_request) { create(:trs_trn_request, :initial) } context "with a successful response" do before do @@ -31,12 +31,12 @@ end it "marks the request as complete" do - expect { perform }.to change(dqt_trn_request, :complete?).to(true) + expect { perform }.to change(trs_trn_request, :complete?).to(true) end it "sets potential duplicate" do expect { perform }.to change( - dqt_trn_request, + trs_trn_request, :potential_duplicate, ).to(false) end @@ -65,14 +65,14 @@ it "leaves the request as initial" do expect { perform_rescue_exception }.not_to change( - dqt_trn_request, + trs_trn_request, :state, ) end it "leaves the potential duplicate" do expect { perform_rescue_exception }.not_to change( - dqt_trn_request, + trs_trn_request, :potential_duplicate, ) end @@ -102,12 +102,12 @@ end it "marks the request as pending" do - expect { perform }.to change(dqt_trn_request, :pending?).to(true) + expect { perform }.to change(trs_trn_request, :pending?).to(true) end it "sets potential duplicate" do expect { perform }.to change( - dqt_trn_request, + trs_trn_request, :potential_duplicate, ).to(true) end @@ -125,14 +125,14 @@ it "queues another job" do expect { perform }.to have_enqueued_job(described_class).with( - dqt_trn_request, + trs_trn_request, ) end end end context "with a pending request" do - let(:dqt_trn_request) { create(:dqt_trn_request, :pending) } + let(:trs_trn_request) { create(:trs_trn_request, :pending) } context "with a successful response" do before do @@ -146,12 +146,12 @@ end it "marks the request as complete" do - expect { perform }.to change(dqt_trn_request, :complete?).to(true) + expect { perform }.to change(trs_trn_request, :complete?).to(true) end it "sets potential duplicate" do expect { perform }.to change( - dqt_trn_request, + trs_trn_request, :potential_duplicate, ).to(false) end @@ -180,14 +180,14 @@ it "leaves the request as pending" do expect { perform_rescue_exception }.not_to change( - dqt_trn_request, + trs_trn_request, :state, ) end it "leaves the potential duplicate" do expect { perform_rescue_exception }.not_to change( - dqt_trn_request, + trs_trn_request, :potential_duplicate, ) end @@ -218,14 +218,14 @@ it "leaves the request as pending" do expect { perform_rescue_exception }.not_to change( - dqt_trn_request, + trs_trn_request, :state, ) end it "sets potential duplicate" do expect { perform }.to change( - dqt_trn_request, + trs_trn_request, :potential_duplicate, ).to(true) end @@ -243,25 +243,25 @@ it "queues another job" do expect { perform }.to have_enqueued_job(described_class).with( - dqt_trn_request, + trs_trn_request, ) end end end context "with a complete request" do - let(:dqt_trn_request) { create(:dqt_trn_request, :complete) } + let(:trs_trn_request) { create(:trs_trn_request, :complete) } it "leaves the request as pending" do expect { perform_rescue_exception }.not_to change( - dqt_trn_request, + trs_trn_request, :state, ) end it "leaves the potential duplicate" do expect { perform_rescue_exception }.not_to change( - dqt_trn_request, + trs_trn_request, :potential_duplicate, ) end @@ -279,5 +279,309 @@ expect { perform }.not_to have_enqueued_job(described_class) end end + + # TODO: This can be removed once the DQTTRNRequest model has been removed and fully migrated + context "when instance passed in is DQTTRNRequest instead of TRSTRNRequest" do + subject(:perform) { described_class.new.perform(dqt_trn_request) } + + context "with an initial request" do + let(:dqt_trn_request) { create(:dqt_trn_request, :initial) } + + it "queues another job" do + expect { perform }.to have_enqueued_job(described_class).with( + dqt_trn_request, + ) + end + + context "when a TRSTRNRequest exists for the application form" do + let!(:trs_trn_request) do + create( + :trs_trn_request, + :initial, + application_form: dqt_trn_request.application_form, + ) + end + + context "with a successful response" do + before do + allow(TRS::Client::CreateTRNRequest).to receive(:call).and_return( + { + potential_duplicate: false, + trn: "abcdef", + access_your_teaching_qualifications_link: "https://aytq.com", + }, + ) + end + + it "marks the request as complete" do + expect { perform }.to change(trs_trn_request, :complete?).to(true) + end + + it "sets potential duplicate" do + expect { perform }.to change( + trs_trn_request, + :potential_duplicate, + ).to(false) + end + + it "awards QTS" do + expect(AwardQTS).to receive(:call).with( + application_form:, + user: "DQT", + trn: "abcdef", + access_your_teaching_qualifications_url: "https://aytq.com", + ) + perform + end + + it "doesn't queue another job" do + expect { perform }.not_to have_enqueued_job(described_class) + end + end + + context "with a failure response" do + before do + allow(TRS::Client::CreateTRNRequest).to receive(:call).and_raise( + Faraday::BadRequestError.new(StandardError.new), + ) + end + + it "leaves the request as initial" do + expect { perform_rescue_exception }.not_to change( + trs_trn_request, + :state, + ) + end + + it "leaves the potential duplicate" do + expect { perform_rescue_exception }.not_to change( + trs_trn_request, + :potential_duplicate, + ) + end + + it "doesn't award QTS" do + expect(AwardQTS).not_to receive(:call) + perform_rescue_exception + end + + it "doesn't change the stage" do + expect { perform_rescue_exception }.not_to change( + application_form, + :stage, + ) + end + + it "raises the error" do + expect { perform }.to raise_error(Faraday::BadRequestError) + end + end + + context "with a potential duplicate response" do + before do + allow(TRS::Client::CreateTRNRequest).to receive(:call).and_return( + { potential_duplicate: true }, + ) + end + + it "marks the request as pending" do + expect { perform }.to change(trs_trn_request, :pending?).to(true) + end + + it "sets potential duplicate" do + expect { perform }.to change( + trs_trn_request, + :potential_duplicate, + ).to(true) + end + + it "doesn't award QTS" do + expect(AwardQTS).not_to receive(:call) + perform + end + + it "changes the state" do + expect { perform }.to change(application_form, :statuses).to( + %w[potential_duplicate_in_dqt], + ) + end + + it "queues another job" do + expect { perform }.to have_enqueued_job(described_class).with( + trs_trn_request, + ) + end + end + end + end + + context "with a pending request" do + let(:dqt_trn_request) { create(:dqt_trn_request, :pending) } + + it "queues another job" do + expect { perform }.to have_enqueued_job(described_class).with( + dqt_trn_request, + ) + end + + context "when a TRSTRNRequest exists for the application form" do + let!(:trs_trn_request) do + create( + :trs_trn_request, + :pending, + application_form: dqt_trn_request.application_form, + ) + end + + context "with a successful response" do + before do + allow(TRS::Client::ReadTRNRequest).to receive(:call).and_return( + { + potential_duplicate: false, + trn: "abcdef", + access_your_teaching_qualifications_link: "https://aytq.com", + }, + ) + end + + it "marks the request as complete" do + expect { perform }.to change(trs_trn_request, :complete?).to(true) + end + + it "sets potential duplicate" do + expect { perform }.to change( + trs_trn_request, + :potential_duplicate, + ).to(false) + end + + it "awards QTS" do + expect(AwardQTS).to receive(:call).with( + application_form:, + user: "DQT", + trn: "abcdef", + access_your_teaching_qualifications_url: "https://aytq.com", + ) + perform + end + + it "doesn't queue another job" do + expect { perform }.not_to have_enqueued_job(described_class) + end + end + + context "with a failure response" do + before do + allow(TRS::Client::ReadTRNRequest).to receive(:call).and_raise( + Faraday::BadRequestError.new(StandardError.new), + ) + end + + it "leaves the request as pending" do + expect { perform_rescue_exception }.not_to change( + trs_trn_request, + :state, + ) + end + + it "leaves the potential duplicate" do + expect { perform_rescue_exception }.not_to change( + trs_trn_request, + :potential_duplicate, + ) + end + + it "doesn't award QTS" do + expect(AwardQTS).not_to receive(:call) + perform_rescue_exception + end + + it "doesn't change the stage" do + expect { perform_rescue_exception }.not_to change( + application_form, + :stage, + ) + end + + it "raises the error" do + expect { perform }.to raise_error(Faraday::BadRequestError) + end + end + + context "with a potential duplicate response" do + before do + allow(TRS::Client::ReadTRNRequest).to receive(:call).and_return( + { potential_duplicate: true }, + ) + end + + it "leaves the request as pending" do + expect { perform_rescue_exception }.not_to change( + trs_trn_request, + :state, + ) + end + + it "sets potential duplicate" do + expect { perform }.to change( + trs_trn_request, + :potential_duplicate, + ).to(true) + end + + it "doesn't award QTS" do + expect(AwardQTS).not_to receive(:call) + perform + end + + it "changes the state" do + expect { perform }.to change(application_form, :statuses).to( + %w[potential_duplicate_in_dqt], + ) + end + + it "queues another job" do + expect { perform }.to have_enqueued_job(described_class).with( + trs_trn_request, + ) + end + end + end + end + + context "with a complete request" do + let(:dqt_trn_request) { create(:dqt_trn_request, :complete) } + + it "leaves the request as pending" do + expect { perform_rescue_exception }.not_to change( + dqt_trn_request, + :state, + ) + end + + it "leaves the potential duplicate" do + expect { perform_rescue_exception }.not_to change( + dqt_trn_request, + :potential_duplicate, + ) + end + + it "doesn't award QTS" do + expect(AwardQTS).not_to receive(:call) + perform + end + + it "doesn't change the stage" do + expect { perform }.not_to change( + dqt_trn_request.application_form, + :stage, + ) + end + + it "doesn't queue another job" do + expect { perform }.not_to have_enqueued_job(described_class) + end + end + end end end diff --git a/spec/lib/application_form_status_updater_spec.rb b/spec/lib/application_form_status_updater_spec.rb index 26133173b1..15132eb0dc 100644 --- a/spec/lib/application_form_status_updater_spec.rb +++ b/spec/lib/application_form_status_updater_spec.rb @@ -65,10 +65,10 @@ describe "#call" do subject(:call) { described_class.call(application_form:, user:) } - context "with a potential duplicate in DQT" do + context "with a potential duplicate in TRS" do before do application_form.update!(submitted_at: Time.zone.now) - create(:dqt_trn_request, :potential_duplicate, application_form:) + create(:trs_trn_request, :potential_duplicate, application_form:) end include_examples "changes action required by", "assessor" @@ -115,10 +115,10 @@ include_examples "changes statuses", %w[awarded] end - context "with a DQT TRN request" do + context "with a TRS TRN request" do before do application_form.update!(submitted_at: Time.zone.now) - create(:dqt_trn_request, application_form:) + create(:trs_trn_request, application_form:) end include_examples "changes action required by", "assessor" diff --git a/spec/models/trstrn_request_spec.rb b/spec/models/trstrn_request_spec.rb new file mode 100644 index 0000000000..9be92d8e9a --- /dev/null +++ b/spec/models/trstrn_request_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: trs_trn_requests +# +# id :bigint not null, primary key +# potential_duplicate :boolean +# state :string default("initial"), not null +# created_at :datetime not null +# updated_at :datetime not null +# application_form_id :bigint not null +# request_id :uuid not null +# +# Indexes +# +# index_trs_trn_requests_on_application_form_id (application_form_id) +# +# Foreign Keys +# +# fk_rails_... (application_form_id => application_forms.id) +# +require "rails_helper" + +RSpec.describe TRSTRNRequest, type: :model do + subject(:trs_trn_request) { create(:trs_trn_request) } + + describe "associations" do + it { is_expected.to belong_to(:application_form) } + end + + describe "validations" do + it { is_expected.to validate_presence_of(:state) } + + it do + expect(subject).to define_enum_for(:state).with_values( + initial: "initial", + pending: "pending", + complete: "complete", + ).backed_by_column_of_type(:string) + end + end + + it "defaults to the initial state" do + expect(trs_trn_request.initial?).to be true + end +end diff --git a/spec/services/award_qts_spec.rb b/spec/services/award_qts_spec.rb index 925dcbe1a6..c4651fbc0a 100644 --- a/spec/services/award_qts_spec.rb +++ b/spec/services/award_qts_spec.rb @@ -30,7 +30,7 @@ create(:application_form, :awarded_pending_checks, teacher:) end - before { create(:dqt_trn_request, application_form:) } + before { create(:trs_trn_request, application_form:) } it "sets the TRN" do expect { call }.to change(teacher, :trn).to("abcdef") @@ -76,7 +76,7 @@ create(:application_form, :potential_duplicate_in_dqt, teacher:) end - before { create(:dqt_trn_request, application_form:) } + before { create(:trs_trn_request, application_form:) } it "sets the TRN" do expect { call }.to change(teacher, :trn).to("abcdef") @@ -120,7 +120,7 @@ context "with an awarded application form" do let(:application_form) { create(:application_form, :awarded, teacher:) } - before { create(:dqt_trn_request, application_form:) } + before { create(:trs_trn_request, application_form:) } it "doesn't change the TRN" do expect { call }.not_to change(teacher, :trn) diff --git a/spec/services/create_trstrn_request_spec.rb b/spec/services/create_trstrn_request_spec.rb index 62f3bfc482..079a71dc3f 100644 --- a/spec/services/create_trstrn_request_spec.rb +++ b/spec/services/create_trstrn_request_spec.rb @@ -8,8 +8,8 @@ let(:application_form) { create(:application_form, :submitted) } let(:user) { create(:staff) } - it "creates a DQTTRNRequest" do - expect { call }.to change(DQTTRNRequest, :count).by(1) + it "creates a TRSTRNRequest" do + expect { call }.to change(TRSTRNRequest, :count).by(1) end it "changes the status" do @@ -20,7 +20,7 @@ it "schedules an update job" do expect { call }.to have_enqueued_job(UpdateTRSTRNRequestJob).with( - DQTTRNRequest.first, + TRSTRNRequest.first, ) end end diff --git a/spec/services/destroy_application_form_spec.rb b/spec/services/destroy_application_form_spec.rb index 4eef1fc3f1..d3ea0bad5f 100644 --- a/spec/services/destroy_application_form_spec.rb +++ b/spec/services/destroy_application_form_spec.rb @@ -21,6 +21,7 @@ create(:timeline_event, :stage_changed, application_form:) create(:note, application_form:) create(:dqt_trn_request, application_form:) + create(:trs_trn_request, application_form:) assessment = create( @@ -51,6 +52,7 @@ include_examples "deletes model", AssessmentSection include_examples "deletes model", ConsentRequest include_examples "deletes model", DQTTRNRequest + include_examples "deletes model", TRSTRNRequest include_examples "deletes model", Document, 20, 10 include_examples "deletes model", FurtherInformationRequest include_examples "deletes model", FurtherInformationRequestItem, 6, 3 From 27ddb9eb4368946b9fe5fb80ff9a049f88c9b561 Mon Sep 17 00:00:00 2001 From: Hassan Mir Date: Thu, 5 Dec 2024 15:27:26 +0000 Subject: [PATCH 2/4] Generate TRSTRNRequest for old DQTTRNRequest records rake task --- lib/tasks/dqt_to_trs_migration.rake | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 lib/tasks/dqt_to_trs_migration.rake diff --git a/lib/tasks/dqt_to_trs_migration.rake b/lib/tasks/dqt_to_trs_migration.rake new file mode 100644 index 0000000000..2d451c118b --- /dev/null +++ b/lib/tasks/dqt_to_trs_migration.rake @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "ruby-progressbar" + +namespace :dqt_to_trs_migration do + desc "Backfill the trs_trn_requests table with what is within dqt_trn_requests" + task replace_dqt_trn_requests_table_with_trs_trn_requests: :environment do + progressbar = + ProgressBar.create!(name: "Creation", count: DQTTRNRequest.count) + + ActiveRecord::Base.transaction do + DQTTRNRequest.find_each do |dqt_trn_request| + progressbar.increment + + TRSTRNRequest.create!( + potential_duplicate: dqt_trn_request.potential_duplicate, + state: dqt_trn_request.state, + created_at: dqt_trn_request.created_at, + updated_at: dqt_trn_request.updated_at, + application_form_id: dqt_trn_request.application_form_id, + request_id: dqt_trn_request.request_id, + ) + end + end + end +end From 37f0d4d3b9a0929185e0da1a6d735d00403ab7e8 Mon Sep 17 00:00:00 2001 From: Hassan Mir Date: Fri, 6 Dec 2024 09:00:16 +0000 Subject: [PATCH 3/4] Add ruby progressbar to gemfile --- Gemfile | 1 + Gemfile.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index ef514dc14e..d260a23f83 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,7 @@ gem "pundit" gem "rack-attack" gem "rails_semantic_logger" gem "rmagick" +gem "ruby-progressbar" gem "ruby-vips" gem "sentry-rails" gem "sentry-ruby" diff --git a/Gemfile.lock b/Gemfile.lock index 960b10aed0..55c22d3590 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -735,6 +735,7 @@ DEPENDENCIES rspec-rails rspec-retry! rubocop-govuk + ruby-progressbar ruby-vips sentry-rails sentry-ruby From 37629240c06e66fee5f3fd65fc26b04f9476835f Mon Sep 17 00:00:00 2001 From: Hassan Mir Date: Tue, 10 Dec 2024 15:48:42 +0000 Subject: [PATCH 4/4] Remove bang from create! on progressbar for rake task --- lib/tasks/dqt_to_trs_migration.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/dqt_to_trs_migration.rake b/lib/tasks/dqt_to_trs_migration.rake index 2d451c118b..2f41dbb45b 100644 --- a/lib/tasks/dqt_to_trs_migration.rake +++ b/lib/tasks/dqt_to_trs_migration.rake @@ -6,7 +6,7 @@ namespace :dqt_to_trs_migration do desc "Backfill the trs_trn_requests table with what is within dqt_trn_requests" task replace_dqt_trn_requests_table_with_trs_trn_requests: :environment do progressbar = - ProgressBar.create!(name: "Creation", count: DQTTRNRequest.count) + ProgressBar.create(name: "Creation", count: DQTTRNRequest.count) # rubocop:disable Rails/SaveBang ActiveRecord::Base.transaction do DQTTRNRequest.find_each do |dqt_trn_request|