diff --git a/Gemfile b/Gemfile
index 007c43c4dc..d8cf760594 100644
--- a/Gemfile
+++ b/Gemfile
@@ -10,13 +10,11 @@ gem "canonical-rails"
gem "countries"
gem "cssbundling-rails", "~> 1.4"
gem "daemons"
-gem "deepsort"
gem "delayed_cron_job"
gem "delayed_job", "~> 4.1"
gem "delayed_job_active_record"
gem "delayed_job_web"
gem "devise", "~> 4.9"
-gem "diffy"
# gem 'dfe-analytics', github: 'DFE-Digital/dfe-analytics', tag: 'v1.14.2'
# temporary until PR will be accepted
gem "dfe-analytics", github: "slawosz/dfe-analytics", branch: "allow-to-exclude-models"
@@ -49,7 +47,6 @@ gem "rails_semantic_logger"
gem "redis"
gem "rouge"
gem "rubyzip"
-gem "scenic"
gem "secure_headers"
gem "sentry-delayed_job"
gem "sentry-rails"
diff --git a/Gemfile.lock b/Gemfile.lock
index 99a7ac54b9..d615bad005 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -161,7 +161,6 @@ GEM
daemons (1.4.1)
date (3.4.0)
declarative (0.0.20)
- deepsort (0.5.0)
delayed_cron_job (0.9.0)
fugit (>= 1.5)
delayed_job (4.1.13)
@@ -183,7 +182,6 @@ GEM
responders
warden (~> 1.2.3)
diff-lcs (1.5.1)
- diffy (3.4.3)
docile (1.4.0)
dotenv (3.1.3)
dotenv-rails (3.1.3)
@@ -569,9 +567,6 @@ GEM
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
- scenic (1.8.0)
- activerecord (>= 4.0.0)
- railties (>= 4.0.0)
scss_lint (0.59.0)
sass (~> 3.5, >= 3.5.5)
scss_lint-govuk (0.2.0)
@@ -706,14 +701,12 @@ DEPENDENCIES
countries
cssbundling-rails (~> 1.4)
daemons
- deepsort
delayed_cron_job
delayed_job (~> 4.1)
delayed_job_active_record
delayed_job_web
devise (~> 4.9)
dfe-analytics!
- diffy
dotenv-rails
email_validator
factory_bot_rails
@@ -760,7 +753,6 @@ DEPENDENCIES
rswag-specs
rubocop-govuk
rubyzip
- scenic
scss_lint-govuk
secure_headers
selenium-webdriver
diff --git a/app/controllers/npq_separation/migration/parity_checks_controller.rb b/app/controllers/npq_separation/migration/parity_checks_controller.rb
deleted file mode 100644
index 621f6bd2bb..0000000000
--- a/app/controllers/npq_separation/migration/parity_checks_controller.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-class NpqSeparation::Migration::ParityChecksController < SuperAdminController
- def index
- @parity_check_running = Migration::ParityCheck.running?
- @parity_check_started_at = Migration::ParityCheck.started_at
- @parity_check_completed_at = Migration::ParityCheck.completed_at
- @parity_check_completed = Migration::ParityCheck.completed?
- @response_comparisons_by_lead_provider = Migration::ParityCheck::ResponseComparison.by_lead_provider
- @average_response_times_by_path = Migration::ParityCheck::ResponseComparison.response_times_by_path
- end
-
- def create
- Migration::ParityCheck.prepare!
- ParityCheckJob.perform_later
-
- redirect_to npq_separation_migration_parity_checks_path
- end
-
- def response_comparison
- @comparison = Migration::ParityCheck::ResponseComparison.find(params[:id])
- @matching_comparisons = Migration::ParityCheck::ResponseComparison.matching(@comparison)
- @multiple_results = @matching_comparisons.size > 1 || @comparison.page.present?
- end
-end
diff --git a/app/helpers/migration_helper.rb b/app/helpers/migration_helper.rb
deleted file mode 100644
index 3f43220b73..0000000000
--- a/app/helpers/migration_helper.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-module MigrationHelper
- def response_comparison_status_tag(different, equal_text: "equal", different_text: "different")
- if different
- govuk_tag(text: different_text.upcase, colour: "red")
- else
- govuk_tag(text: equal_text.upcase, colour: "green")
- end
- end
-
- def response_comparison_status_code_tag(status_code)
- if status_code <= 299
- govuk_tag(text: status_code, colour: "green")
- elsif status_code <= 399
- govuk_tag(text: status_code, colour: "yellow")
- else
- govuk_tag(text: status_code, colour: "red")
- end
- end
-
- def response_comparison_performance(comparisons)
- comparisons = Array.wrap(comparisons)
- average_performance = (comparisons.sum(&:ecf_response_time_ms).to_f / comparisons.sum(&:npq_response_time_ms)).round(1)
- formatted_performance = average_performance.to_s.chomp(".0")
-
- if average_performance < 1
- tag.strong("🐌 #{formatted_performance}x as fast")
- else
- tag.i("🚀 #{formatted_performance}x faster")
- end
- end
-
- def response_comparison_detail_path(comparisons)
- return unless comparisons.any? { |c| c.different? || c.unexpected? }
-
- response_comparison_npq_separation_migration_parity_checks_path(comparisons.sample.id)
- end
-
- def response_comparison_response_duration_human_readable(comparisons, response_time_attribute)
- comparisons = Array.wrap(comparisons)
- duration_ms = (comparisons.sum(&response_time_attribute).to_f / comparisons.size).round(0)
-
- if duration_ms < 1_000
- "#{duration_ms}ms"
- else
- ActiveSupport::Duration.build(duration_ms / 1_000).inspect
- end
- end
-
- def response_comparison_page_summary(comparison)
- tag.div(class: "govuk-grid-row") do
- tag.div("Page #{comparison.page}", class: "govuk-grid-column-two-thirds") +
- tag.div(class: "govuk-grid-column-one-third govuk-!-text-align-right") do
- tag.span("ECF: ", class: "govuk-!-font-weight-regular govuk-!-font-size-16") +
- response_comparison_status_code_tag(comparison.ecf_response_status_code) +
- tag.span(" NPQ: ", class: "govuk-!-font-weight-regular govuk-!-font-size-16") +
- response_comparison_status_code_tag(comparison.npq_response_status_code)
- end
- end
- end
-
- def contains_duplicate_ids?(comparisons, attribute)
- ids = comparisons.map(&attribute).flatten
- ids.size != ids.uniq.size
- end
-end
diff --git a/app/jobs/parity_check_job.rb b/app/jobs/parity_check_job.rb
deleted file mode 100644
index 41e88fb6da..0000000000
--- a/app/jobs/parity_check_job.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class ParityCheckJob < ApplicationJob
- queue_as :high_priority
-
- def perform
- parity_check = Migration::ParityCheck.new
- parity_check.run!
- end
-end
diff --git a/app/models/migration/ecf/base_record.rb b/app/models/migration/ecf/base_record.rb
deleted file mode 100644
index 2cf31c7887..0000000000
--- a/app/models/migration/ecf/base_record.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-module Migration::Ecf
- class BaseRecord < ApplicationRecord
- self.abstract_class = true
-
- connects_to database: { reading: :ecf, writing: :ecf } unless Rails.env.review?
-
- def readonly?
- # Not to be readonly in test so we can create factories
- !Rails.env.test?
- end
- end
-end
diff --git a/app/models/migration/ecf/npq_lead_provider.rb b/app/models/migration/ecf/npq_lead_provider.rb
deleted file mode 100644
index 33595d2585..0000000000
--- a/app/models/migration/ecf/npq_lead_provider.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-module Migration::Ecf
- class NpqLeadProvider < BaseRecord
- end
-end
diff --git a/app/models/migration/parity_check/response_comparison.rb b/app/models/migration/parity_check/response_comparison.rb
deleted file mode 100644
index 9bd217c469..0000000000
--- a/app/models/migration/parity_check/response_comparison.rb
+++ /dev/null
@@ -1,138 +0,0 @@
-module Migration
- class ParityCheck::ResponseComparison < ApplicationRecord
- attr_accessor :exclude
-
- before_validation :digest_csv_response_bodies, :format_json_response_bodies, :populate_response_body_ids, :clear_response_bodies_when_equal
-
- belongs_to :lead_provider
-
- validates :lead_provider, presence: true
- validates :request_path, presence: true
- validates :page, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
- validates :request_method, inclusion: { in: %w[get post put] }
- validates :ecf_response_status_code, inclusion: { in: 100..599 }
- validates :npq_response_status_code, inclusion: { in: 100..599 }
- validates :ecf_response_body, presence: true, if: -> { different? }
- validates :npq_response_body, presence: true, if: -> { different? }
- validates :ecf_response_time_ms, numericality: { greater_than: 0 }
- validates :npq_response_time_ms, numericality: { greater_than: 0 }
-
- delegate :name, to: :lead_provider, prefix: true
-
- scope :matching, lambda { |comparison|
- where(
- lead_provider: comparison.lead_provider,
- request_path: comparison.request_path,
- request_method: comparison.request_method,
- )
- .order(page: :asc)
- }
-
- class << self
- def by_lead_provider
- includes(:lead_provider)
- .group_by(&:lead_provider_name)
- .transform_values do |comparisons|
- comparisons.group_by(&:description)
- end
- end
-
- def response_times_by_path
- select(:request_path, :request_method, :ecf_response_time_ms, :npq_response_time_ms)
- .group_by(&:description)
- .transform_values do |comparisons|
- {
- ecf: {
- avg: comparisons.sum(&:ecf_response_time_ms) / comparisons.size,
- min: comparisons.min_by(&:ecf_response_time_ms).ecf_response_time_ms,
- max: comparisons.max_by(&:ecf_response_time_ms).ecf_response_time_ms,
- },
- npq: {
- avg: comparisons.sum(&:npq_response_time_ms) / comparisons.size,
- min: comparisons.min_by(&:npq_response_time_ms).npq_response_time_ms,
- max: comparisons.max_by(&:npq_response_time_ms).npq_response_time_ms,
- },
- }
- end
- end
- end
-
- def different?
- ecf_response_status_code != npq_response_status_code || ecf_response_body != npq_response_body
- end
-
- def unexpected?
- [ecf_response_status_code, npq_response_status_code].any? { |code| code != 200 }
- end
-
- def needs_review?
- different? || unexpected?
- end
-
- def description
- "#{request_method.upcase} #{request_path}"
- end
-
- def response_body_diff
- @response_body_diff ||= Diffy::Diff.new(ecf_response_body, npq_response_body, context: 3)
- end
-
- private
-
- def populate_response_body_ids
- self.ecf_response_body_ids = Array.wrap(ecf_response_body_hash&.dig("data") || []).map { |record| record["id"] }
- self.npq_response_body_ids = Array.wrap(npq_response_body_hash&.dig("data") || []).map { |record| record["id"] }
- end
-
- def format_json_response_bodies
- self.ecf_response_body = pretty_format(ecf_response_body_hash) if ecf_response_body_hash
- self.npq_response_body = pretty_format(npq_response_body_hash) if npq_response_body_hash
- end
-
- def pretty_format(hash)
- JSON.pretty_generate(hash)
- end
-
- def ecf_response_body_hash
- @ecf_response_body_hash ||= deep_remove_keys(JSON.parse(ecf_response_body).deep_sort, exclude)
- rescue JSON::ParserError, TypeError
- nil
- end
-
- def npq_response_body_hash
- @npq_response_body_hash ||= deep_remove_keys(JSON.parse(npq_response_body).deep_sort, exclude)
- rescue JSON::ParserError, TypeError
- nil
- end
-
- def digest_csv_response_bodies
- return unless request_path&.include?(".csv")
-
- self.ecf_response_body = Digest::SHA2.hexdigest(ecf_response_body) if ecf_response_body
- self.npq_response_body = Digest::SHA2.hexdigest(npq_response_body) if npq_response_body
- end
-
- def clear_response_bodies_when_equal
- return if different?
-
- assign_attributes(ecf_response_body: nil, npq_response_body: nil)
- end
-
- def deep_remove_keys(hash, keys_to_remove)
- return hash if keys_to_remove.blank?
-
- case hash
- when Hash
- hash.each_with_object({}) do |(key, value), result|
- next if key.in?(keys_to_remove)
-
- result[key] = deep_remove_keys(value, keys_to_remove)
- end
- when Array
- hash.map { |item| deep_remove_keys(item, keys_to_remove) }
- else
- hash
- end
- end
- end
-end
diff --git a/app/services/migration/parity_check.rb b/app/services/migration/parity_check.rb
deleted file mode 100644
index 8983d3afac..0000000000
--- a/app/services/migration/parity_check.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-module Migration
- class ParityCheck
- class UnsupportedEnvironmentError < RuntimeError; end
- class EndpointsFileNotFoundError < RuntimeError; end
- class NotPreparedError < RuntimeError; end
-
- attr_reader :endpoints_file_path
-
- class << self
- def prepare!
- Rails.cache.write(:parity_check_started_at, Time.zone.now)
- Rails.cache.write(:parity_check_completed_at, nil)
-
- # We want this to be fast, so we're not bothering with callbacks.
- Migration::ParityCheck::ResponseComparison.delete_all
- end
-
- def running?
- started_at && !completed_at
- end
-
- def completed?
- completed_at.present?
- end
-
- def started_at
- Rails.cache.read(:parity_check_started_at)
- end
-
- def completed_at
- Rails.cache.read(:parity_check_completed_at)
- end
- end
-
- def initialize(endpoints_file_path: "config/parity_check_endpoints.yml")
- @endpoints_file_path = endpoints_file_path
- end
-
- def run!
- raise UnsupportedEnvironmentError, "The parity check functionality is disabled for this environment" unless enabled?
-
- lead_providers.each(&method(:call_endpoints))
-
- finalise!
- end
-
- private
-
- def prepared?
- self.class.started_at.present?
- end
-
- def call_endpoints(lead_provider)
- raise NotPreparedError, "You must call prepare! before running the parity check" unless prepared?
-
- endpoints.each do |method, paths|
- paths.each do |path, options|
- client = Client.new(lead_provider:, method:, path:, options:)
-
- client.make_requests do |ecf_result, npq_result, formatted_path, page|
- save_comparison!(lead_provider:, path: formatted_path, method:, page:, ecf_result:, npq_result:, options:)
- end
- end
- end
- end
-
- def finalise!
- Rails.cache.write(:parity_check_completed_at, Time.zone.now)
- end
-
- def save_comparison!(lead_provider:, path:, method:, page:, ecf_result:, npq_result:, options:)
- Migration::ParityCheck::ResponseComparison.create!({
- lead_provider:,
- request_path: path,
- request_method: method,
- ecf_response_status_code: ecf_result[:response].code,
- npq_response_status_code: npq_result[:response].code,
- ecf_response_body: ecf_result[:response].body,
- npq_response_body: npq_result[:response].body,
- ecf_response_time_ms: ecf_result[:response_ms],
- npq_response_time_ms: npq_result[:response_ms],
- exclude: options[:exclude],
- page:,
- })
- end
-
- def endpoints
- file = Rails.root.join(endpoints_file_path)
-
- raise EndpointsFileNotFoundError, "Endpoints file not found: #{endpoints_file_path}" unless File.exist?(file)
-
- YAML.load_file(file).with_indifferent_access
- end
-
- def enabled?
- Rails.application.config.npq_separation[:parity_check][:enabled]
- end
-
- def lead_providers
- @lead_providers ||= LeadProvider.all
- end
- end
-end
diff --git a/app/services/migration/parity_check/client.rb b/app/services/migration/parity_check/client.rb
deleted file mode 100644
index 96ee352740..0000000000
--- a/app/services/migration/parity_check/client.rb
+++ /dev/null
@@ -1,316 +0,0 @@
-module Migration
- class ParityCheck::Client
- class UnsupportedIdOption < RuntimeError; end
-
- attr_reader :lead_provider, :method, :path, :options, :page
-
- PAGINATION_PER_PAGE = 10
-
- def initialize(lead_provider:, method:, path:, options:)
- @lead_provider = lead_provider
- @method = method
- @path = path
- @options = options || {}
- @page = 1 if paginate?
- end
-
- def make_requests(&block)
- loop do
- ecf_result = timed_response { send("#{method}_request", app: :ecf) }
- npq_result = timed_response { send("#{method}_request", app: :npq) }
-
- block.call(ecf_result, npq_result, formatted_path, page)
-
- break unless next_page?(ecf_result, npq_result)
-
- @page += 1
- end
- end
-
- # These are public for ease of testing
- def post_declaration_payload
- application = lead_provider.applications
- .includes(:user)
- .left_joins(:declarations)
- .where(training_status: :active, declarations: { id: nil })
- .accepted
- .order("RANDOM()")
- .first
-
- participant_id = application.user.ecf_id
- course_identifier = application.course.identifier
- declaration_date = 1.day.ago.rfc3339
-
- {
- type: "participant-declaration",
- attributes: {
- participant_id:,
- declaration_type: :started,
- declaration_date:,
- course_identifier:,
- },
- }
- end
-
- def post_participant_outcome_payload
- participant = User.includes(applications: :course).find_by(ecf_id: path_id)
-
- {
- type: "npq-outcome-confirmation",
- attributes: {
- course_identifier: participant.applications.accepted.first.course.identifier,
- state: :passed,
- completion_date: 1.day.ago.rfc3339,
- },
- }
- end
-
- def put_participant_resume_payload
- participant = User.includes(applications: :course).find_by(ecf_id: path_id)
-
- {
- type: "participant-resume",
- attributes: {
- course_identifier: participant.applications.accepted.first.course.identifier,
- },
- }
- end
-
- def put_participant_defer_payload
- participant = User.includes(applications: :course).find_by(ecf_id: path_id)
-
- {
- type: "participant-defer",
- attributes: {
- course_identifier: participant.applications.accepted.first.course.identifier,
- reason: Participants::Defer::DEFERRAL_REASONS.sample,
- },
- }
- end
-
- def put_participant_withdraw_payload
- participant = User.includes(applications: :course).find_by(ecf_id: path_id)
-
- {
- type: "participant-withdraw",
- attributes: {
- course_identifier: participant.applications.accepted.first.course.identifier,
- reason: Participants::Withdraw::WITHDRAWAL_REASONS.sample,
- },
- }
- end
-
- private
-
- def next_page?(ecf_response, npq_response)
- return false unless paginate?
- return false unless responses_match?(ecf_response, npq_response)
-
- pages_remain?(ecf_response, npq_response)
- end
-
- def responses_match?(ecf_response, npq_response)
- ecf_response[:response].code == npq_response[:response].code &&
- ecf_response[:response].body == npq_response[:response].body
- end
-
- def pages_remain?(ecf_response, npq_response)
- [ecf_response[:response].body, npq_response[:response].body].any? do |body|
- JSON.parse(body)["data"]&.size == PAGINATION_PER_PAGE
- rescue JSON::ParserError
- false
- end
- end
-
- def paginate?
- options[:paginate]
- end
-
- def timed_response(&request)
- response = nil
- response_ms = Benchmark.realtime { response = request.call } * 1_000
-
- { response:, response_ms: }
- end
-
- def get_request(app:)
- HTTParty.get(url(app:), query:, headers:)
- end
-
- def post_request(app:)
- HTTParty.post(url(app:), body:, query:, headers:)
- end
-
- def put_request(app:)
- HTTParty.put(url(app:), body:, query:, headers:)
- end
-
- def body
- @body ||= begin
- return {} unless options.key?(:payload)
-
- data = options[:payload].is_a?(Hash) ? options[:payload] : send(options[:payload])
-
- { data: }.to_json
- end
- end
-
- def token_provider
- @token_provider ||= Migration::ParityCheck::TokenProvider.new
- end
-
- def query
- return unless paginate?
-
- { page: { page:, per_page: PAGINATION_PER_PAGE } }
- end
-
- def headers
- {
- "Authorization" => "Bearer #{token_provider.token(lead_provider:)}",
- "Accept" => "application/json",
- "Content-Type" => "application/json",
- }
- end
-
- def url(app:)
- Rails.application.config.npq_separation[:parity_check]["#{app}_url".to_sym] + formatted_path
- end
-
- def formatted_path
- @formatted_path ||= begin
- return path unless path.include?(":id")
-
- path.sub(":id", path_id)
- end
- end
-
- def path_id
- return nil unless options[:id]
-
- raise UnsupportedIdOption, "Unsupported id option: #{options[:id]}" unless respond_to?(options[:id], true)
-
- send(options[:id]).to_s
- end
-
- def application_ecf_id
- lead_provider.applications.order("RANDOM()").limit(1).pick(:ecf_id)
- end
-
- def declaration_ecf_id
- Declaration.where(lead_provider:).order("RANDOM()").limit(1).pick(:ecf_id)
- end
-
- def participant_outcome_ecf_id
- ParticipantOutcome
- .includes(declaration: { application: :user })
- .where(declaration: { lead_provider: })
- .order("RANDOM()")
- .limit(1)
- .pick("users.ecf_id")
- end
-
- def participant_ecf_id
- User
- .includes(:applications)
- .where(applications: { lead_provider:, lead_provider_approval_status: :accepted })
- .order("RANDOM()")
- .limit(1)
- .pick(:ecf_id)
- end
-
- def statement_ecf_id
- lead_provider.statements.order("RANDOM()").limit(1).pick(:ecf_id)
- end
-
- def application_ecf_id_for_accept_with_funded_place
- lead_provider
- .applications
- .eligible_for_funding
- .where(lead_provider_approval_status: :pending)
- .where.not(user_id: Application.group(:user_id).having("COUNT(*) > 1").pluck(:user_id))
- .order("RANDOM()")
- .limit(1)
- .pick(:ecf_id)
- end
-
- def application_ecf_id_for_accept_without_funded_place
- lead_provider
- .applications
- .where(lead_provider_approval_status: :pending, eligible_for_funding: false)
- .where.not(user_id: Application.group(:user_id).having("COUNT(*) > 1").pluck(:user_id))
- .order("RANDOM()")
- .limit(1)
- .pick(:ecf_id)
- end
-
- def application_ecf_id_for_reject
- lead_provider
- .applications
- .where(lead_provider_approval_status: :pending)
- .order("RANDOM()")
- .limit(1)
- .pick(:ecf_id)
- end
-
- def participant_ecf_id_for_create_outcome
- User
- .includes(:applications, :declarations)
- .where(applications: { lead_provider:, lead_provider_approval_status: :accepted })
- .where(declarations: { declaration_type: :completed })
- .order("RANDOM()")
- .limit(1)
- .pick(:ecf_id)
- end
-
- def application_ecf_id_for_change_from_funded_place
- lead_provider
- .applications
- .accepted
- .where(funded_place: true)
- .pick(:ecf_id)
- end
-
- def declaration_ecf_id_for_void
- Declaration
- .where(state: Declaration::CHANGEABLE_STATES, lead_provider:)
- .pick(:ecf_id)
- end
-
- def declaration_ecf_id_for_clawback
- Declaration
- .includes(:statement_items)
- .where(lead_provider:, state: :paid)
- .where.not(id: StatementItem.where(declaration_id: Declaration.select(:id)).where(state: StatementItem::REFUNDABLE_STATES).select(:declaration_id))
- .pick(:ecf_id)
- end
-
- def participant_ecf_id_for_resume
- User
- .includes(:applications)
- .where(applications: { lead_provider:, lead_provider_approval_status: :accepted, training_status: %i[deferred withdrawn] })
- .order("RANDOM()")
- .limit(1)
- .pick(:ecf_id)
- end
-
- def participant_ecf_id_for_defer
- User
- .includes(:applications, :declarations)
- .where(applications: { lead_provider:, lead_provider_approval_status: :accepted, training_status: :active })
- .where.not(declarations: { id: nil })
- .order("RANDOM()")
- .limit(1)
- .pick(:ecf_id)
- end
-
- def participant_ecf_id_for_withdraw
- User
- .includes(:applications, :declarations)
- .where(applications: { lead_provider:, lead_provider_approval_status: :accepted, training_status: %i[deferred active], declarations: { declaration_type: :started } })
- .order("RANDOM()")
- .limit(1)
- .pick(:ecf_id)
- end
- end
-end
diff --git a/app/services/migration/parity_check/token_provider.rb b/app/services/migration/parity_check/token_provider.rb
deleted file mode 100644
index 20c7661593..0000000000
--- a/app/services/migration/parity_check/token_provider.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module Migration
- class ParityCheck::TokenProvider
- class UnsupportedEnvironmentError < RuntimeError; end
-
- def generate!
- raise UnsupportedEnvironmentError, "The parity check functionality is disabled for this environment" unless enabled?
-
- known_tokens_by_lead_provider_ecf_id.each do |ecf_id, token|
- lead_provider = LeadProvider.find_by!(ecf_id:)
- create_with_known_token!(token:, lead_provider:) if lead_provider
- end
- end
-
- def token(lead_provider:)
- raise UnsupportedEnvironmentError, "The parity check functionality is disabled for this environment" unless enabled?
-
- known_tokens_by_lead_provider_ecf_id[lead_provider.ecf_id]
- end
-
- private
-
- def known_tokens_by_lead_provider_ecf_id
- JSON.parse(ENV["PARITY_CHECK_KEYS"].to_s)
- rescue JSON::ParserError
- {}
- end
-
- def create_with_known_token!(token:, lead_provider:)
- APIToken.create_with_known_token!(token, lead_provider:)
- end
-
- def enabled?
- Rails.application.config.npq_separation[:parity_check][:enabled]
- end
- end
-end
diff --git a/app/views/npq_separation/migration/parity_checks/_completed_parity_check.html.erb b/app/views/npq_separation/migration/parity_checks/_completed_parity_check.html.erb
deleted file mode 100644
index ef2599fead..0000000000
--- a/app/views/npq_separation/migration/parity_checks/_completed_parity_check.html.erb
+++ /dev/null
@@ -1,137 +0,0 @@
-
Completed parity check
-
-
- The latest parity check was completed <%= tag.strong(time_ago_in_words(@parity_check_completed_at)) %> ago.
- The parity check took <%= tag.strong(ActiveSupport::Duration.build((@parity_check_completed_at - @parity_check_started_at).to_i).inspect) %> to complete.
-
-
-
-
-<%= govuk_accordion do |accordion|
- @response_comparisons_by_lead_provider.each do |lead_provider_name, comparisons_by_description|
- accordion.with_section(heading_text: lead_provider_name, expanded: comparisons_by_description.values.flatten.any?(&:needs_review?)) do
- govuk_task_list do |task_list|
- comparisons_by_description.each do |description, comparisons|
- task_list.with_item do |item|
- item.with_title(text: description, hint: response_comparison_performance(comparisons), href: response_comparison_detail_path(comparisons))
- if comparisons.any?(&:unexpected?)
- item.with_status(text: response_comparison_status_tag(true, different_text: "unexpected"))
- else
- item.with_status(text: response_comparison_status_tag(comparisons.any?(&:different?)))
- end
- end
- end
- end
- end
- end
-end %>
-
-
-
-
diff --git a/app/views/npq_separation/migration/parity_checks/_multiple_comparisons_summary.html.erb b/app/views/npq_separation/migration/parity_checks/_multiple_comparisons_summary.html.erb
deleted file mode 100644
index 50c0d06581..0000000000
--- a/app/views/npq_separation/migration/parity_checks/_multiple_comparisons_summary.html.erb
+++ /dev/null
@@ -1,50 +0,0 @@
-<%= govuk_table do |table|
- table.with_caption(size: "m", text: "Overview (#{pluralize(comparisons.size, "page")})")
-
- table.with_head do |head|
- head.with_row do |row|
- row.with_cell(text: "Metric")
- row.with_cell(text: "ECF")
- row.with_cell(text: "NPQ")
- row.with_cell(text: "Comparison")
- end
- end
-
- table.with_body do |body|
- body.with_row do |row|
- row.with_cell(text: "Equality check")
- row.with_cell(text: "-")
- row.with_cell(text: "-")
- row.with_cell(text: response_comparison_status_tag(comparisons.any?(&:different?)))
- end
-
- body.with_row do |row|
- row.with_cell(text: "Average response time")
- row.with_cell(text: response_comparison_response_duration_human_readable(comparisons, :ecf_response_time_ms))
- row.with_cell(text: response_comparison_response_duration_human_readable(comparisons, :npq_response_time_ms))
- row.with_cell(text: response_comparison_performance(comparisons))
- end
-
- body.with_row do |row|
- row.with_cell(text: "ID duplicates check")
-
- ecf_duplicates = contains_duplicate_ids?(comparisons, :ecf_response_body_ids)
- npq_duplicates = contains_duplicate_ids?(comparisons, :npq_response_body_ids)
-
- row.with_cell(text: response_comparison_status_tag(ecf_duplicates, equal_text: "no", different_text: "yes"))
- row.with_cell(text: response_comparison_status_tag(npq_duplicates, equal_text: "no", different_text: "yes"))
- row.with_cell(text: response_comparison_status_tag(ecf_duplicates || npq_duplicates, equal_text: "unique", different_text: "duplicates"))
- end
-
- body.with_row do |row|
- row.with_cell(text: "ID equality check")
-
- ecf_ids = comparisons.map(&:ecf_response_body_ids).flatten.sort
- npq_ids = comparisons.map(&:npq_response_body_ids).flatten.sort
-
- row.with_cell(text: number_with_delimiter(ecf_ids.size))
- row.with_cell(text: number_with_delimiter(npq_ids.size))
- row.with_cell(text: response_comparison_status_tag(ecf_ids != npq_ids))
- end
- end
-end %>
diff --git a/app/views/npq_separation/migration/parity_checks/_response_body_diff.html.erb b/app/views/npq_separation/migration/parity_checks/_response_body_diff.html.erb
deleted file mode 100644
index d94f0acce2..0000000000
--- a/app/views/npq_separation/migration/parity_checks/_response_body_diff.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= response_body_diff.to_s.presence ? response_body_diff.to_s(:html).html_safe : tag.p("No difference", class: "govuk-body") %>
diff --git a/app/views/npq_separation/migration/parity_checks/_running_parity_check.erb b/app/views/npq_separation/migration/parity_checks/_running_parity_check.erb
deleted file mode 100644
index b0cdf1be31..0000000000
--- a/app/views/npq_separation/migration/parity_checks/_running_parity_check.erb
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- A parity check is currently in-progress
-
-
It was started <%= tag.strong(time_ago_in_words(@parity_check_started_at)) %> ago.
-
-
-
diff --git a/app/views/npq_separation/migration/parity_checks/_single_comparison_summary.html.erb b/app/views/npq_separation/migration/parity_checks/_single_comparison_summary.html.erb
deleted file mode 100644
index 25378bec08..0000000000
--- a/app/views/npq_separation/migration/parity_checks/_single_comparison_summary.html.erb
+++ /dev/null
@@ -1,28 +0,0 @@
-<%= govuk_table do |table|
- table.with_caption(size: "m", text: "Response diff")
-
- table.with_head do |head|
- head.with_row do |row|
- row.with_cell(text: "Metric")
- row.with_cell(text: "ECF")
- row.with_cell(text: "NPQ")
- row.with_cell(text: "Comparison")
- end
- end
-
- table.with_body do |body|
- body.with_row do |row|
- row.with_cell(text: "Response time")
- row.with_cell(text: response_comparison_response_duration_human_readable(comparison, :ecf_response_time_ms))
- row.with_cell(text: response_comparison_response_duration_human_readable(comparison, :npq_response_time_ms))
- row.with_cell(text: response_comparison_performance(comparison))
- end
-
- body.with_row do |row|
- row.with_cell(text: "Status code")
- row.with_cell(text: response_comparison_status_code_tag(comparison.ecf_response_status_code))
- row.with_cell(text: response_comparison_status_code_tag(comparison.npq_response_status_code))
- row.with_cell(text: response_comparison_status_tag(comparison.ecf_response_status_code != comparison.npq_response_status_code))
- end
- end
- end %>
diff --git a/app/views/npq_separation/migration/parity_checks/index.html.erb b/app/views/npq_separation/migration/parity_checks/index.html.erb
deleted file mode 100644
index 0f158712ee..0000000000
--- a/app/views/npq_separation/migration/parity_checks/index.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<% content_for :title, "Parity Checks" %>
-
-<% if @parity_check_running %>
- <%= render(partial: "running_parity_check") %>
-<% end %>
-
-Parity Checks
-
-You can compare ECF and NPQ API responses using the parity check tool.
-
-<%= button_to "Run parity check", { action: :create }, class: "govuk-button govuk-button--warning", disabled: @parity_check_running %>
-
-<% if @parity_check_completed %>
- <%= render(partial: "completed_parity_check") %>
-<% end %>
diff --git a/app/views/npq_separation/migration/parity_checks/response_comparison.html.erb b/app/views/npq_separation/migration/parity_checks/response_comparison.html.erb
deleted file mode 100644
index 8ef069896f..0000000000
--- a/app/views/npq_separation/migration/parity_checks/response_comparison.html.erb
+++ /dev/null
@@ -1,27 +0,0 @@
-<% content_for(:head) do %>
-
-<% end %>
-
-<%= govuk_back_link(href: url_for(:back)) %>
-
-<%= @comparison.lead_provider_name %>
-<%= @comparison.description %>
-
-<% if @multiple_results %>
- <%= render(partial: "multiple_comparisons_summary", locals: { comparisons: @matching_comparisons }) %>
-<% end %>
-
-<% if @matching_comparisons.any?(&:needs_review?) %>
- <% unless @multiple_results %>
- <%= render(partial: "single_comparison_summary", locals: { comparison: @comparison }) %>
- <%= render(partial: "response_body_diff", locals: { response_body_diff: @comparison.response_body_diff }) %>
- <% else %>
- <%= govuk_accordion do |accordion|
- @matching_comparisons.select(&:needs_review?).each do |comparison|
- accordion.with_section(heading_text: response_comparison_page_summary(comparison), expanded: comparison.needs_review?) do
- render(partial: "response_body_diff", locals: { response_body_diff: comparison.response_body_diff })
- end
- end
- end %>
- <% end %>
-<% end %>
diff --git a/config/database.yml b/config/database.yml
index 1069f0e11a..1d3a49c3a7 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -13,64 +13,35 @@ default_primary: &default_primary
port: "<%= ENV['DB_PORT'] %>"
url: <%= ENV.fetch("DATABASE_URL", "postgres://localhost:5432") %>
-default_ecf: &default_ecf
- <<: *shared_db_settings
- migrations_paths: db/ecf_migrate # Set to ensure its different but not used.
- database: "<%= ENV['ECF_DB_DATABASE'] %>"
- username: "<%= ENV['ECF_DB_USERNAME'] %>"
- password: "<%= ENV['ECF_DB_PASSWORD'] %>"
- host: "<%= ENV['ECF_DB_HOST'] %>"
- port: "<%= ENV['ECF_DB_PORT'] %>"
- url: <%= ENV.fetch("ECF_DATABASE_URL", "postgres://localhost:5432") %>
-
development:
primary:
<<: *default_primary
database: npq_registration_development
- ecf:
- <<: *default_ecf
- database: early_careers_framework_development
review:
primary:
<<: *default_primary
- # The ECF database is not used in review
test:
primary:
<<: *default_primary
database: npq_registration_test<%= ENV['TEST_ENV_NUMBER'] %>
- ecf:
- <<: *default_ecf
- <<: *default_primary # We share the database in the test environment
- database: early_careers_framework_test<%= ENV['TEST_ENV_NUMBER'] %>
staging:
primary:
<<: *default_primary
- ecf:
- <<: *default_ecf
-
sandbox:
primary:
<<: *default_primary
- ecf:
- <<: *default_ecf
migration:
primary:
<<: *default_primary
- ecf:
- <<: *default_ecf
separation:
primary:
<<: *default_primary
- ecf:
- <<: *default_ecf
production:
primary:
<<: *default_primary
- ecf:
- <<: *default_ecf
diff --git a/config/initializers/dfe_analytics.rb b/config/initializers/dfe_analytics.rb
index 6b5f3b35b2..6e27534c43 100644
--- a/config/initializers/dfe_analytics.rb
+++ b/config/initializers/dfe_analytics.rb
@@ -53,9 +53,6 @@
# config.user_identifier = proc { |user| user&.id
config.user_identifier = proc { |user| user&.id if user.respond_to?(:id) }
- # if part of any model name is migration, it wont be loaded
- config.excluded_models_proc = proc { |x| x.to_s =~ /Migration::/ }
-
config.entity_table_checks_enabled = true
config.excluded_paths = ["/healthcheck"]
end
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index d0e7f8036c..2df12184dd 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -1,8 +1,5 @@
# frozen_string_literal: true
-# Disable in migration environment
-Rack::Attack.enabled = !Rails.env.migration?
-
# Throttle general requests by IP
class Rack::Attack
PROTECTED_ROUTES = [
diff --git a/config/parity_check_endpoints.yml b/config/parity_check_endpoints.yml
deleted file mode 100644
index 6ff167220c..0000000000
--- a/config/parity_check_endpoints.yml
+++ /dev/null
@@ -1,462 +0,0 @@
-get:
- "/api/v1/npq-applications":
- paginate: true
- "/api/v2/npq-applications":
- paginate: true
- "/api/v3/npq-applications":
- paginate: true
-
- "/api/v1/npq-applications?filter[cohort]=2023":
- paginate: true
- "/api/v2/npq-applications?filter[cohort]=2023":
- paginate: true
- "/api/v3/npq-applications?filter[cohort]=2023":
- paginate: true
-
- "/api/v1/npq-applications?filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v2/npq-applications?filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v3/npq-applications?filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
-
- "/api/v1/npq-applications?filter[cohort]=2023&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v2/npq-applications?filter[cohort]=2023&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v3/npq-applications?filter[cohort]=2023&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
-
- "/api/v3/npq-applications?filter[participant_id]=:id":
- paginate: true
- id: application_ecf_id
- "/api/v3/npq-applications?filter[participant_id]=:id&filter[updated_since]=2020-11-13T11:21:55Z":
- paginate: true
- id: application_ecf_id
- "/api/v3/npq-applications?filter[participant_id]=:id&filter[cohort]=2023":
- paginate: true
- id: application_ecf_id
- "/api/v3/npq-applications?filter[participant_id]=:id&filter[cohort]=2023&filter[updated_since]=2020-11-13T11:21:55Z":
- paginate: true
- id: application_ecf_id
-
- "/api/v1/npq-applications/:id":
- id: application_ecf_id
- "/api/v2/npq-applications/:id":
- id: application_ecf_id
- "/api/v3/npq-applications/:id":
- id: application_ecf_id
-
- "/api/v1/participant-declarations?filter[type]=npq":
- paginate: true
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v2/participant-declarations?filter[type]=npq":
- paginate: true
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v3/participant-declarations?filter[type]=npq":
- paginate: true
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
-
- "/api/v1/participant-declarations?filter[type]=npq&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v2/participant-declarations?filter[type]=npq&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v3/participant-declarations?filter[type]=npq&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
-
- "/api/v1/participant-declarations?filter[type]=npq&filter[participant_id]=:id":
- paginate: true
- id: participant_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v2/participant-declarations?filter[type]=npq&filter[participant_id]=:id":
- paginate: true
- id: participant_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v3/participant-declarations?filter[type]=npq&filter[participant_id]=:id":
- paginate: true
- id: participant_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
-
- "/api/v1/participant-declarations?filter[type]=npq&filter[participant_id]=:id&filter[updated_since]=2020-11-13T11:21:55Z":
- paginate: true
- id: participant_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v2/participant-declarations?filter[type]=npq&filter[participant_id]=:id&filter[updated_since]=2020-11-13T11:21:55Z":
- paginate: true
- id: participant_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v3/participant-declarations?filter[type]=npq&filter[participant_id]=:id&filter[updated_since]=2020-11-13T11:21:55Z":
- paginate: true
- id: participant_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
-
- "/api/v1/participant-declarations/:id":
- id: declaration_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v2/participant-declarations/:id":
- id: declaration_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
- "/api/v3/participant-declarations/:id":
- id: declaration_ecf_id
- exclude:
- - delivery_partner_id
- - mentor_id
- - evidence_held
-
- "/api/v1/participants/npq/outcomes":
- paginate: true
- "/api/v2/participants/npq/outcomes":
- paginate: true
- "/api/v3/participants/npq/outcomes":
- paginate: true
-
- "/api/v1/participants/npq/outcomes?filter[created_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v2/participants/npq/outcomes?filter[created_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v3/participants/npq/outcomes?filter[created_since]=2023-11-13T11:21:55Z":
- paginate: true
-
- "/api/v1/participants/npq/:id/outcomes":
- id: participant_outcome_ecf_id
- "/api/v2/participants/npq/:id/outcomes":
- id: participant_outcome_ecf_id
- "/api/v3/participants/npq/:id/outcomes":
- id: participant_outcome_ecf_id
-
- "/api/v1/participants/npq":
- paginate: true
- "/api/v2/participants/npq":
- paginate: true
- "/api/v3/participants/npq":
- paginate: true
-
- "/api/v1/participants/npq?filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v2/participants/npq?filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v3/participants/npq?filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v3/participants/npq?filter[training_status]=active":
- paginate: true
- "/api/v3/participants/npq?filter[training_status]=active&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- "/api/v3/participants/npq?filter[from_participant_id]=:id":
- paginate: true
- id: participant_ecf_id
- "/api/v3/participants/npq?filter[from_participant_id]=:id&filter[training_status]=active":
- paginate: true
- id: participant_ecf_id
- "/api/v3/participants/npq?filter[from_participant_id]=:id&filter[updated_since]=2020-11-13T11:21:55Z":
- paginate: true
- id: participant_ecf_id
- "/api/v3/participants/npq?filter[from_participant_id]=:id&filter[updated_since]=2020-11-13T11:21:55Z&filter[training_status]=active":
- paginate: true
- id: participant_ecf_id
-
- "/api/v1/participants/npq/:id":
- id: participant_ecf_id
- "/api/v2/participants/npq/:id":
- id: participant_ecf_id
- "/api/v3/participants/npq/:id":
- id: participant_ecf_id
-
- "/api/v3/statements?filter[type]=npq":
- paginate: true
- exclude:
- - type
- "/api/v3/statements?filter[type]=npq&filter[cohort]=2022,2023":
- paginate: true
- exclude:
- - type
- "/api/v3/statements?filter[type]=npq&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- exclude:
- - type
- "/api/v3/statements?filter[type]=npq&filter[cohort]=2022,2023&filter[updated_since]=2023-11-13T11:21:55Z":
- paginate: true
- exclude:
- - type
-
- "/api/v3/statements/:id":
- id: statement_ecf_id
- exclude:
- - type
-
-post:
- "/api/v1/npq-applications/:id/accept#with_funded_place":
- exclude:
- - updated_at
- id: application_ecf_id_for_accept_with_funded_place
- payload:
- type: "npq-application-accept"
- attributes:
- funded_place: true
- "/api/v2/npq-applications/:id/accept#with_funded_place":
- exclude:
- - updated_at
- id: application_ecf_id_for_accept_with_funded_place
- payload:
- type: "npq-application-accept"
- attributes:
- funded_place: true
- "/api/v3/npq-applications/:id/accept#with_funded_place":
- exclude:
- - updated_at
- id: application_ecf_id_for_accept_with_funded_place
- payload:
- type: "npq-application-accept"
- attributes:
- funded_place: true
-
- "/api/v1/npq-applications/:id/accept#without_funded_place":
- exclude:
- - updated_at
- id: application_ecf_id_for_accept_without_funded_place
- payload:
- type: "npq-application-accept"
- attributes:
- funded_place: false
- "/api/v2/npq-applications/:id/accept#without_funded_place":
- exclude:
- - updated_at
- id: application_ecf_id_for_accept_without_funded_place
- payload:
- type: "npq-application-accept"
- attributes:
- funded_place: false
- "/api/v3/npq-applications/:id/accept#without_funded_place":
- exclude:
- - updated_at
- id: application_ecf_id_for_accept_without_funded_place
- payload:
- type: "npq-application-accept"
- attributes:
- funded_place: false
-
- "/api/v1/npq-applications/:id/reject":
- exclude:
- - updated_at
- id: application_ecf_id_for_reject
- "/api/v2/npq-applications/:id/reject":
- exclude:
- - updated_at
- id: application_ecf_id_for_reject
- "/api/v3/npq-applications/:id/reject":
- exclude:
- - updated_at
- id: application_ecf_id_for_reject
-
- "/api/v1/participant-declarations":
- exclude:
- - id
- - updated_at
- - created_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- payload: post_declaration_payload
- "/api/v2/participant-declarations":
- exclude:
- - id
- - updated_at
- - created_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- payload: post_declaration_payload
- "/api/v3/participant-declarations":
- exclude:
- - id
- - updated_at
- - created_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- payload: post_declaration_payload
-
- "/api/v1/participants/npq/:id/outcomes":
- exclude:
- - id
- - created_at
- - updated_at
- id: participant_ecf_id_for_create_outcome
- payload: post_participant_outcome_payload
- "/api/v2/participants/npq/:id/outcomes":
- exclude:
- - id
- - created_at
- - updated_at
- id: participant_ecf_id_for_create_outcome
- payload: post_participant_outcome_payload
- "/api/v3/participants/npq/:id/outcomes":
- exclude:
- - id
- - created_at
- - updated_at
- id: participant_ecf_id_for_create_outcome
- payload: post_participant_outcome_payload
-
-put:
- "/api/v1/npq-applications/:id/change-funded-place":
- exclude:
- - updated_at
- id: application_ecf_id_for_change_from_funded_place
- payload:
- type: "npq-application-change-funded-place"
- attributes:
- funded_place: false
- "/api/v2/npq-applications/:id/change-funded-place":
- exclude:
- - updated_at
- id: application_ecf_id_for_change_from_funded_place
- payload:
- type: "npq-application-change-funded-place"
- attributes:
- funded_place: false
- "/api/v3/npq-applications/:id/change-funded-place":
- exclude:
- - updated_at
- id: application_ecf_id_for_change_from_funded_place
- payload:
- type: "npq-application-change-funded-place"
- attributes:
- funded_place: false
-
- "/api/v1/participant-declarations/:id/void#void":
- exclude:
- - updated_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- id: declaration_ecf_id_for_void
- "/api/v2/participant-declarations/:id/void#void":
- exclude:
- - updated_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- id: declaration_ecf_id_for_void
- "/api/v3/participant-declarations/:id/void#void":
- exclude:
- - updated_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- id: declaration_ecf_id_for_void
-
- "/api/v1/participant-declarations/:id/void#clawback":
- exclude:
- - updated_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- id: declaration_ecf_id_for_clawback
- "/api/v2/participant-declarations/:id/void#clawback":
- exclude:
- - updated_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- id: declaration_ecf_id_for_clawback
- "/api/v3/participant-declarations/:id/void#clawback":
- exclude:
- - updated_at
- - mentor_id
- - evidence_held
- - delivery_partner_id
- id: declaration_ecf_id_for_clawback
-
- "/api/v1/participants/npq/:id/resume":
- exclude:
- - updated_at
- id: participant_ecf_id_for_resume
- payload: put_participant_resume_payload
- "/api/v2/participants/npq/:id/resume":
- exclude:
- - updated_at
- id: participant_ecf_id_for_resume
- payload: put_participant_resume_payload
- "/api/v3/participants/npq/:id/resume":
- exclude:
- - updated_at
- id: participant_ecf_id_for_resume
- payload: put_participant_resume_payload
-
- "/api/v1/participants/npq/:id/defer":
- exclude:
- - updated_at
- id: participant_ecf_id_for_defer
- payload: put_participant_defer_payload
- "/api/v2/participants/npq/:id/defer":
- exclude:
- - updated_at
- - date
- id: participant_ecf_id_for_defer
- payload: put_participant_defer_payload
- "/api/v3/participants/npq/:id/defer":
- exclude:
- - updated_at
- id: participant_ecf_id_for_defer
- payload: put_participant_defer_payload
-
- "/api/v1/participants/npq/:id/withdraw":
- exclude:
- - updated_at
- id: participant_ecf_id_for_withdraw
- payload: put_participant_withdraw_payload
- "/api/v2/participants/npq/:id/withdraw":
- exclude:
- - updated_at
- id: participant_ecf_id_for_withdraw
- payload: put_participant_withdraw_payload
- "/api/v3/participants/npq/:id/withdraw":
- exclude:
- - updated_at
- id: participant_ecf_id_for_withdraw
- payload: put_participant_withdraw_payload
diff --git a/config/routes.rb b/config/routes.rb
index 5fab8ac522..e600098ddd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -265,12 +265,6 @@
resources :admins, only: %i[index]
end
end
-
- namespace :migration, constraints: -> { Feature.ecf_api_disabled? } do
- resources :parity_checks, only: %i[index create] do
- get "response_comparisons/:id", on: :collection, action: :response_comparison, as: :response_comparison
- end
- end
end
resource :csp_reports, only: %i[create]
diff --git a/db/ecf_schema.rb b/db/ecf_schema.rb
deleted file mode 100644
index 523d992ab6..0000000000
--- a/db/ecf_schema.rb
+++ /dev/null
@@ -1,1497 +0,0 @@
-# This file is auto-generated from the current state of the database. Instead
-# of editing this file, please use the migrations feature of Active Record to
-# incrementally modify your database, and then regenerate this schema definition.
-#
-# This file is the source Rails uses to define your schema when running `bin/rails
-# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
-# be faster and is potentially less error prone than running all of your
-# migrations from scratch. Old migrations may fail to apply correctly if those
-# migrations use external dependencies or application code.
-#
-# It's strongly recommended that you check this file into your version control system.
-
-ActiveRecord::Schema[7.1].define(version: 2024_11_01_133851) do
- # These are extensions that must be enabled in order to support this database
- enable_extension "citext"
- enable_extension "fuzzystrmatch"
- enable_extension "pgcrypto"
- enable_extension "plpgsql"
- enable_extension "uuid-ossp"
-
- create_table "additional_school_emails", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "school_id", null: false
- t.string "email_address", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["email_address", "school_id"], name: "index_additional_school_emails_on_email_address_and_school_id", unique: true
- t.index ["school_id"], name: "index_additional_school_emails_on_school_id"
- end
-
- create_table "admin_profiles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.datetime "discarded_at", precision: nil
- t.boolean "super_user", default: false
- t.index ["discarded_at"], name: "index_admin_profiles_on_discarded_at"
- t.index ["user_id"], name: "index_admin_profiles_on_user_id"
- end
-
- create_table "analytics_appropriate_bodies", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "appropriate_body_id"
- t.string "name"
- t.string "body_type"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
-
- create_table "analytics_inductions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "induction_record_id"
- t.string "external_id"
- t.uuid "participant_profile_id"
- t.uuid "induction_programme_id"
- t.string "induction_programme_type"
- t.string "school_name"
- t.string "school_urn"
- t.string "schedule_id"
- t.uuid "mentor_id"
- t.uuid "appropriate_body_id"
- t.string "appropriate_body_name"
- t.datetime "start_date", precision: nil
- t.datetime "end_date", precision: nil
- t.string "induction_status"
- t.string "training_status"
- t.boolean "school_transfer"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "cohort_id"
- t.uuid "user_id"
- t.string "participant_type"
- t.datetime "induction_record_created_at", precision: nil
- t.uuid "partnership_id"
- t.index ["induction_record_id"], name: "index_analytics_inductions_on_induction_record_id", unique: true
- end
-
- create_table "analytics_participants", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "user_id"
- t.datetime "user_created_at", precision: nil
- t.integer "real_time_attempts"
- t.boolean "real_time_success"
- t.datetime "validation_submitted_at", precision: nil
- t.boolean "trn_verified"
- t.string "school_urn"
- t.string "school_name"
- t.string "establishment_phase_name"
- t.string "participant_type"
- t.string "participant_profile_id"
- t.string "cohort"
- t.string "mentor_id"
- t.boolean "nino_entered"
- t.boolean "manually_validated"
- t.boolean "eligible_for_funding"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "active", default: true
- t.string "training_status"
- t.boolean "sparsity"
- t.boolean "pupil_premium"
- t.string "schedule_identifier"
- t.string "external_id"
- t.index ["participant_profile_id"], name: "index_analytics_participants_on_participant_profile_id"
- end
-
- create_table "analytics_partnerships", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "partnership_id"
- t.uuid "school_id"
- t.string "school_name"
- t.string "school_urn"
- t.uuid "lead_provider_id"
- t.string "lead_provider_name"
- t.uuid "cohort_id"
- t.string "cohort"
- t.uuid "delivery_partner_id"
- t.string "delivery_partner_name"
- t.datetime "challenged_at", precision: nil
- t.string "challenge_reason"
- t.datetime "challenge_deadline", precision: nil
- t.boolean "pending"
- t.uuid "report_id"
- t.boolean "relationship"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
-
- create_table "analytics_school_cohorts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "school_cohort_id"
- t.uuid "school_id"
- t.string "school_name"
- t.string "school_urn"
- t.uuid "cohort_id"
- t.string "cohort"
- t.string "induction_programme_choice"
- t.string "default_induction_programme_training_choice"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "appropriate_body_id"
- end
-
- create_table "analytics_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "name"
- t.string "urn", null: false
- t.datetime "nomination_email_opened_at", precision: nil
- t.boolean "induction_tutor_nominated"
- t.datetime "tutor_nominated_time", precision: nil
- t.boolean "induction_tutor_signed_in"
- t.string "induction_programme_choice"
- t.boolean "in_partnership"
- t.datetime "partnership_time", precision: nil
- t.string "partnership_challenge_reason"
- t.string "partnership_challenge_time"
- t.string "lead_provider"
- t.string "delivery_partner"
- t.string "chosen_cip"
- t.string "school_type_name"
- t.integer "school_phase_type"
- t.string "school_phase_name"
- t.integer "school_status_code"
- t.string "school_status_name"
- t.string "postcode"
- t.string "administrative_district_code"
- t.string "administrative_district_name"
- t.boolean "active_participants"
- t.boolean "pupil_premium"
- t.boolean "sparsity"
- t.index ["urn"], name: "index_analytics_schools_on_urn", unique: true
- end
-
- create_table "api_request_audits", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "path"
- t.jsonb "body"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
-
- create_table "api_requests", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "request_path"
- t.integer "status_code"
- t.jsonb "request_headers"
- t.jsonb "request_body"
- t.jsonb "response_body"
- t.string "request_method"
- t.jsonb "response_headers"
- t.uuid "cpd_lead_provider_id"
- t.string "user_description"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["cpd_lead_provider_id"], name: "index_api_requests_on_cpd_lead_provider_id"
- end
-
- create_table "api_tokens", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "lead_provider_id"
- t.string "hashed_token", null: false
- t.datetime "last_used_at", precision: nil
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "type", default: "ApiToken"
- t.boolean "private_api_access", default: false
- t.uuid "cpd_lead_provider_id"
- t.index ["cpd_lead_provider_id"], name: "index_api_tokens_on_cpd_lead_provider_id"
- t.index ["hashed_token"], name: "index_api_tokens_on_hashed_token", unique: true
- t.index ["lead_provider_id"], name: "index_api_tokens_on_lead_provider_id"
- end
-
- create_table "appropriate_bodies", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "name", null: false
- t.string "body_type", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "disable_from_year"
- t.boolean "listed", default: false, null: false
- t.integer "listed_for_school_type_codes", default: [], array: true
- t.boolean "selectable_by_schools", default: true, null: false
- t.index ["body_type", "name"], name: "index_appropriate_bodies_on_body_type_and_name", unique: true
- end
-
- create_table "appropriate_body_profiles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id", null: false
- t.uuid "appropriate_body_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["appropriate_body_id"], name: "index_appropriate_body_profiles_on_appropriate_body_id"
- t.index ["user_id"], name: "index_appropriate_body_profiles_on_user_id"
- end
-
- create_table "archive_relics", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "object_type", null: false
- t.string "object_id", null: false
- t.string "display_name", null: false
- t.string "reason", null: false
- t.jsonb "data"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index "((data -> 'meta'::text))", name: "index_archive_relics_on_data_meta", using: :gin
- t.index ["display_name"], name: "index_archive_relics_on_display_name"
- t.index ["object_id"], name: "index_archive_relics_on_object_id"
- t.index ["object_type"], name: "index_archive_relics_on_object_type"
- t.index ["reason"], name: "index_archive_relics_on_reason"
- end
-
- create_table "call_off_contracts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "version", default: "0.0.1", null: false
- t.jsonb "raw"
- t.decimal "uplift_target"
- t.decimal "uplift_amount"
- t.integer "recruitment_target"
- t.decimal "set_up_fee"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "lead_provider_id", default: -> { "gen_random_uuid()" }, null: false
- t.integer "revised_target"
- t.uuid "cohort_id", null: false
- t.decimal "monthly_service_fee"
- t.index ["cohort_id"], name: "index_call_off_contracts_on_cohort_id"
- t.index ["lead_provider_id"], name: "index_call_off_contracts_on_lead_provider_id"
- end
-
- create_table "cohorts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "start_year", limit: 2, null: false
- t.datetime "registration_start_date", precision: nil
- t.datetime "academic_year_start_date", precision: nil
- t.datetime "npq_registration_start_date", precision: nil
- t.date "automatic_assignment_period_end_date"
- t.datetime "payments_frozen_at"
- t.index ["start_year"], name: "index_cohorts_on_start_year", unique: true
- end
-
- create_table "cohorts_lead_providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "lead_provider_id", null: false
- t.uuid "cohort_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["cohort_id", "lead_provider_id"], name: "index_cohorts_lead_providers_on_cohort_id_and_lead_provider_id"
- t.index ["lead_provider_id", "cohort_id"], name: "index_cohorts_lead_providers_on_lead_provider_id_and_cohort_id"
- end
-
- create_table "completion_candidates", id: false, force: :cascade do |t|
- t.uuid "participant_profile_id"
- t.index ["participant_profile_id"], name: "index_completion_candidates_on_participant_profile_id", unique: true
- end
-
- create_table "continue_training_cohort_change_errors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.text "message"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_profile_id"], name: "continue_training_error_participant_profile_id"
- end
-
- create_table "core_induction_programmes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "name", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
-
- create_table "cpd_lead_providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.text "name", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
-
- create_table "data_stage_school_changes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "data_stage_school_id", null: false
- t.json "attribute_changes"
- t.string "status", default: "changed", null: false
- t.boolean "handled", default: false, null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["data_stage_school_id"], name: "index_data_stage_school_changes_on_data_stage_school_id"
- t.index ["status"], name: "index_data_stage_school_changes_on_status"
- end
-
- create_table "data_stage_school_links", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "data_stage_school_id", null: false
- t.string "link_urn", null: false
- t.string "link_type", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["data_stage_school_id", "link_urn"], name: "data_stage_school_links_uniq_idx", unique: true
- t.index ["data_stage_school_id"], name: "index_data_stage_school_links_on_data_stage_school_id"
- end
-
- create_table "data_stage_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "urn", null: false
- t.string "name", null: false
- t.string "ukprn"
- t.integer "school_phase_type"
- t.string "school_phase_name"
- t.integer "school_type_code"
- t.string "school_type_name"
- t.integer "school_status_code"
- t.string "school_status_name"
- t.string "administrative_district_code"
- t.string "administrative_district_name"
- t.string "address_line1", null: false
- t.string "address_line2"
- t.string "address_line3"
- t.string "postcode", null: false
- t.string "primary_contact_email"
- t.string "secondary_contact_email"
- t.string "school_website"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "section_41_approved"
- t.string "la_code"
- t.index ["urn"], name: "index_data_stage_schools_on_urn", unique: true
- end
-
- create_table "declaration_states", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_declaration_id", null: false
- t.string "state", default: "submitted", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "state_reason"
- t.index ["participant_declaration_id"], name: "index_declaration_states_on_participant_declaration_id"
- end
-
- create_table "deleted_duplicates", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.jsonb "data"
- t.uuid "primary_participant_profile_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["primary_participant_profile_id"], name: "index_deleted_duplicates_on_primary_participant_profile_id"
- end
-
- create_table "delivery_partner_profiles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id", null: false
- t.uuid "delivery_partner_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["delivery_partner_id"], name: "index_delivery_partner_profiles_on_delivery_partner_id"
- t.index ["user_id"], name: "index_delivery_partner_profiles_on_user_id"
- end
-
- create_table "delivery_partners", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "name", null: false
- t.datetime "discarded_at", precision: nil
- t.index ["discarded_at"], name: "index_delivery_partners_on_discarded_at"
- end
-
- create_table "district_sparsities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "local_authority_district_id", null: false
- t.integer "start_year", limit: 2, null: false
- t.integer "end_year", limit: 2
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["local_authority_district_id"], name: "index_district_sparsities_on_local_authority_district_id"
- end
-
- create_table "ecf_ineligible_participants", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "trn"
- t.string "reason", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["trn"], name: "index_ecf_ineligible_participants_on_trn", unique: true
- end
-
- create_table "ecf_participant_eligibilities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.boolean "qts"
- t.boolean "active_flags"
- t.boolean "previous_participation"
- t.boolean "previous_induction"
- t.boolean "manually_validated", default: false
- t.string "status", default: "manual_check", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "reason", default: "none", null: false
- t.boolean "different_trn"
- t.boolean "no_induction"
- t.boolean "exempt_from_induction"
- t.index ["participant_profile_id"], name: "index_ecf_participant_eligibilities_on_participant_profile_id", unique: true
- end
-
- create_table "ecf_participant_validation_data", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.string "full_name"
- t.date "date_of_birth"
- t.string "trn"
- t.string "nino"
- t.boolean "api_failure", default: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_profile_id"], name: "index_ecf_participant_validation_data_on_participant_profile_id", unique: true
- end
-
- create_table "ecf_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "name"
- t.string "urn"
- t.datetime "nomination_email_opened_at", precision: nil
- t.boolean "induction_tutor_nominated"
- t.datetime "tutor_nominated_time", precision: nil
- t.boolean "induction_tutor_signed_in"
- t.string "induction_programme_choice"
- t.boolean "in_partnership"
- t.datetime "partnership_time", precision: nil
- t.string "partnership_challenge_reason"
- t.string "partnership_challenge_time"
- t.string "lead_provider"
- t.string "delivery_partner"
- t.string "chosen_cip"
- t.string "school_type_name"
- t.integer "school_phase_type"
- t.string "school_phase_name"
- t.integer "school_status_code"
- t.string "school_status_name"
- t.string "postcode"
- t.string "administrative_district_code"
- t.string "administrative_district_name"
- t.boolean "active_participants"
- t.boolean "pupil_premium"
- t.boolean "sparsity"
- t.index ["urn"], name: "index_ecf_schools_on_urn", unique: true
- end
-
- create_table "email_associations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "email_id", null: false
- t.string "object_type", null: false
- t.uuid "object_id", null: false
- t.string "name"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["email_id"], name: "index_email_associations_on_email_id"
- t.index ["object_type", "object_id"], name: "index_email_associations_on_object"
- end
-
- create_table "email_schedules", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "mailer_name", null: false
- t.date "scheduled_at", null: false
- t.string "status", default: "queued", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "emails_sent_count"
- end
-
- create_table "emails", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "from"
- t.string "to", array: true
- t.uuid "template_id"
- t.integer "template_version"
- t.string "uri"
- t.jsonb "personalisation"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "status", default: "submitted", null: false
- t.datetime "delivered_at", precision: nil
- t.string "tags", default: [], null: false, array: true
- end
-
- create_table "event_logs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "owner_type", null: false
- t.uuid "owner_id", null: false
- t.string "event", null: false
- t.json "data", default: {}
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["owner_type", "owner_id"], name: "index_event_logs_on_owner"
- end
-
- create_table "feature_selected_objects", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "object_type", null: false
- t.uuid "object_id", null: false
- t.uuid "feature_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["feature_id"], name: "index_feature_selected_objects_on_feature_id"
- t.index ["object_id", "feature_id", "object_type"], name: "unique_selected_object", unique: true
- t.index ["object_type", "object_id"], name: "index_feature_selected_objects_on_object"
- end
-
- create_table "features", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "name", null: false
- t.boolean "active", default: false, null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["name"], name: "index_features_on_name", unique: true
- end
-
- create_table "finance_adjustments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "statement_id", null: false
- t.string "payment_type", null: false
- t.decimal "amount", default: "0.0", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["statement_id"], name: "index_finance_adjustments_on_statement_id"
- end
-
- create_table "finance_profiles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["user_id"], name: "index_finance_profiles_on_user_id"
- end
-
- create_table "friendly_id_slugs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "slug", null: false
- t.integer "sluggable_id", null: false
- t.string "sluggable_type", limit: 50
- t.string "scope"
- t.datetime "created_at", precision: nil
- t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
- t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
- t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id"
- end
-
- create_table "induction_coordinator_profiles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.datetime "discarded_at", precision: nil
- t.datetime "reminder_email_sent_at", precision: nil
- t.index ["discarded_at"], name: "index_induction_coordinator_profiles_on_discarded_at"
- t.index ["user_id"], name: "index_induction_coordinator_profiles_on_user_id"
- end
-
- create_table "induction_coordinator_profiles_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "induction_coordinator_profile_id", null: false
- t.uuid "school_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["induction_coordinator_profile_id"], name: "index_icp_schools_on_icp"
- t.index ["school_id"], name: "index_icp_schools_on_schools"
- end
-
- create_table "induction_programmes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "school_cohort_id", null: false
- t.uuid "partnership_id"
- t.uuid "core_induction_programme_id"
- t.string "training_programme", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "delivery_partner_to_be_confirmed", default: false
- t.index ["core_induction_programme_id"], name: "index_induction_programmes_on_core_induction_programme_id"
- t.index ["partnership_id"], name: "index_induction_programmes_on_partnership_id"
- t.index ["school_cohort_id"], name: "index_induction_programmes_on_school_cohort_id"
- end
-
- create_table "induction_records", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "induction_programme_id", null: false
- t.uuid "participant_profile_id", null: false
- t.uuid "schedule_id", null: false
- t.datetime "start_date", precision: nil, null: false
- t.datetime "end_date", precision: nil
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "training_status", default: "active", null: false
- t.uuid "preferred_identity_id"
- t.string "induction_status", default: "active", null: false
- t.uuid "mentor_profile_id"
- t.boolean "school_transfer", default: false, null: false
- t.uuid "appropriate_body_id"
- t.index ["appropriate_body_id"], name: "index_induction_records_on_appropriate_body_id"
- t.index ["created_at"], name: "index_induction_records_on_created_at"
- t.index ["end_date"], name: "index_induction_records_on_end_date"
- t.index ["induction_programme_id"], name: "index_induction_records_on_induction_programme_id"
- t.index ["mentor_profile_id"], name: "index_induction_records_on_mentor_profile_id"
- t.index ["participant_profile_id"], name: "index_induction_records_on_participant_profile_id"
- t.index ["preferred_identity_id"], name: "index_induction_records_on_preferred_identity_id"
- t.index ["schedule_id"], name: "index_induction_records_on_schedule_id"
- t.index ["start_date"], name: "index_induction_records_on_start_date"
- end
-
- create_table "lead_provider_cips", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "lead_provider_id", null: false
- t.uuid "cohort_id", null: false
- t.uuid "core_induction_programme_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["cohort_id"], name: "index_lead_provider_cips_on_cohort_id"
- t.index ["core_induction_programme_id"], name: "index_lead_provider_cips_on_core_induction_programme_id"
- t.index ["lead_provider_id"], name: "index_lead_provider_cips_on_lead_provider_id"
- end
-
- create_table "lead_provider_profiles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id", null: false
- t.uuid "lead_provider_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.datetime "discarded_at", precision: nil
- t.index ["discarded_at"], name: "index_lead_provider_profiles_on_discarded_at"
- t.index ["lead_provider_id"], name: "index_lead_provider_profiles_on_lead_provider_id"
- t.index ["user_id"], name: "index_lead_provider_profiles_on_user_id"
- end
-
- create_table "lead_providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "name", null: false
- t.uuid "cpd_lead_provider_id"
- t.boolean "vat_chargeable", default: true
- t.index ["cpd_lead_provider_id"], name: "index_lead_providers_on_cpd_lead_provider_id"
- end
-
- create_table "local_authorities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "code"
- t.string "name"
- t.index ["code"], name: "index_local_authorities_on_code", unique: true
- end
-
- create_table "local_authority_districts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "code"
- t.string "name"
- t.index ["code"], name: "index_local_authority_districts_on_code", unique: true
- end
-
- create_table "milestones", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.text "name", null: false
- t.date "milestone_date"
- t.date "payment_date", null: false
- t.uuid "schedule_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.date "start_date"
- t.string "declaration_type"
- t.index ["schedule_id"], name: "index_milestones_on_schedule_id"
- end
-
- create_table "networks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "name", null: false
- t.string "group_type"
- t.string "group_type_code"
- t.string "group_id"
- t.string "group_uid"
- t.string "secondary_contact_email"
- end
-
- create_table "nomination_emails", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "token", null: false
- t.string "notify_status"
- t.string "sent_to", null: false
- t.datetime "sent_at", precision: nil
- t.datetime "opened_at", precision: nil
- t.uuid "school_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "partnership_notification_email_id"
- t.string "notify_id"
- t.datetime "delivered_at", precision: nil
- t.index ["notify_id"], name: "index_nomination_emails_on_notify_id"
- t.index ["partnership_notification_email_id"], name: "index_nomination_emails_on_partnership_notification_email_id"
- t.index ["school_id"], name: "index_nomination_emails_on_school_id"
- t.index ["token"], name: "index_nomination_emails_on_token", unique: true
- end
-
- create_table "npq_application_exports", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.date "start_date", null: false
- t.date "end_date", null: false
- t.uuid "user_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["user_id"], name: "index_npq_application_exports_on_user_id"
- end
-
- create_table "npq_applications", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "npq_lead_provider_id", null: false
- t.uuid "npq_course_id", null: false
- t.date "date_of_birth"
- t.text "teacher_reference_number"
- t.boolean "teacher_reference_number_verified", default: false
- t.text "school_urn"
- t.text "headteacher_status"
- t.boolean "active_alert", default: false
- t.boolean "eligible_for_funding", default: false, null: false
- t.text "funding_choice"
- t.text "nino"
- t.text "lead_provider_approval_status", default: "pending", null: false
- t.text "school_ukprn"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "participant_identity_id"
- t.boolean "works_in_school"
- t.string "employer_name"
- t.string "employment_role"
- t.boolean "targeted_support_funding_eligibility", default: false
- t.uuid "cohort_id"
- t.boolean "targeted_delivery_funding_eligibility", default: false
- t.boolean "works_in_nursery"
- t.boolean "works_in_childcare"
- t.string "kind_of_nursery"
- t.string "private_childcare_provider_urn"
- t.string "funding_eligiblity_status_code"
- t.text "teacher_catchment"
- t.text "teacher_catchment_country"
- t.string "employment_type"
- t.string "teacher_catchment_iso_country_code", limit: 3
- t.string "itt_provider"
- t.boolean "lead_mentor", default: false
- t.string "notes"
- t.boolean "primary_establishment", default: false
- t.integer "number_of_pupils", default: 0
- t.boolean "tsf_primary_eligibility", default: false
- t.boolean "tsf_primary_plus_eligibility", default: false
- t.uuid "eligible_for_funding_updated_by_id"
- t.datetime "eligible_for_funding_updated_at"
- t.boolean "funded_place"
- t.string "referred_by_return_to_teaching_adviser"
- t.index ["cohort_id"], name: "index_npq_applications_on_cohort_id"
- t.index ["npq_course_id"], name: "index_npq_applications_on_npq_course_id"
- t.index ["npq_lead_provider_id"], name: "index_npq_applications_on_npq_lead_provider_id"
- t.index ["participant_identity_id"], name: "index_npq_applications_on_participant_identity_id"
- end
-
- create_table "npq_contracts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.jsonb "raw"
- t.string "version", default: "0.0.1"
- t.uuid "npq_lead_provider_id", null: false
- t.integer "recruitment_target"
- t.string "course_identifier"
- t.integer "service_fee_installments"
- t.integer "service_fee_percentage", default: 40
- t.decimal "per_participant"
- t.integer "number_of_payment_periods"
- t.integer "output_payment_percentage", default: 60
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "cohort_id", null: false
- t.decimal "monthly_service_fee", default: "0.0"
- t.decimal "targeted_delivery_funding_per_participant", default: "100.0"
- t.boolean "special_course", default: false, null: false
- t.integer "funding_cap"
- t.index ["cohort_id"], name: "index_npq_contracts_on_cohort_id"
- t.index ["npq_lead_provider_id"], name: "index_npq_contracts_on_npq_lead_provider_id"
- end
-
- create_table "npq_courses", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.text "name", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.text "identifier"
- end
-
- create_table "npq_lead_providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.text "name", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "cpd_lead_provider_id"
- t.boolean "vat_chargeable", default: true
- t.index ["cpd_lead_provider_id"], name: "index_npq_lead_providers_on_cpd_lead_provider_id"
- end
-
- create_table "participant_appropriate_body_dqt_checks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.string "appropriate_body_name"
- t.string "dqt_appropriate_body_name"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "dqt_induction_status"
- end
-
- create_table "participant_bands", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "call_off_contract_id", null: false
- t.integer "min"
- t.integer "max"
- t.decimal "per_participant"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "output_payment_percentage", default: 60
- t.integer "service_fee_percentage", default: 40
- t.index ["call_off_contract_id"], name: "index_participant_bands_on_call_off_contract_id"
- end
-
- create_table "participant_declaration_attempts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "declaration_type"
- t.datetime "declaration_date", precision: nil
- t.uuid "user_id"
- t.string "course_identifier"
- t.string "evidence_held"
- t.uuid "cpd_lead_provider_id"
- t.uuid "participant_declaration_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_declaration_id"], name: "index_declaration_attempts_on_declarations"
- end
-
- create_table "participant_declarations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "declaration_type"
- t.datetime "declaration_date", precision: nil
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "user_id", null: false
- t.string "course_identifier"
- t.string "evidence_held"
- t.string "type", default: "ParticipantDeclaration::ECF"
- t.uuid "cpd_lead_provider_id"
- t.string "state", default: "submitted", null: false
- t.uuid "participant_profile_id"
- t.uuid "superseded_by_id"
- t.boolean "sparsity_uplift"
- t.boolean "pupil_premium_uplift"
- t.uuid "delivery_partner_id"
- t.uuid "mentor_user_id"
- t.uuid "cohort_id", null: false
- t.index ["cohort_id"], name: "index_participant_declarations_on_cohort_id"
- t.index ["cpd_lead_provider_id", "participant_profile_id", "declaration_type", "course_identifier", "state"], name: "unique_declaration_index", unique: true, where: "((state)::text = ANY (ARRAY[('submitted'::character varying)::text, ('eligible'::character varying)::text, ('payable'::character varying)::text, ('paid'::character varying)::text]))"
- t.index ["cpd_lead_provider_id"], name: "index_participant_declarations_on_cpd_lead_provider_id"
- t.index ["declaration_type"], name: "index_participant_declarations_on_declaration_type"
- t.index ["delivery_partner_id"], name: "index_participant_declarations_on_delivery_partner_id"
- t.index ["mentor_user_id"], name: "index_participant_declarations_on_mentor_user_id"
- t.index ["participant_profile_id"], name: "index_participant_declarations_on_participant_profile_id"
- t.index ["superseded_by_id"], name: "superseded_by_index"
- t.index ["user_id"], name: "index_participant_declarations_on_user_id"
- end
-
- create_table "participant_id_changes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id", null: false
- t.uuid "from_participant_id", null: false
- t.uuid "to_participant_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["from_participant_id"], name: "index_participant_id_changes_on_from_participant_id"
- t.index ["to_participant_id"], name: "index_participant_id_changes_on_to_participant_id"
- t.index ["user_id"], name: "index_participant_id_changes_on_user_id"
- end
-
- create_table "participant_identities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id", null: false
- t.citext "email", null: false
- t.uuid "external_identifier", null: false
- t.string "origin", default: "ecf", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["email"], name: "index_participant_identities_on_email", unique: true
- t.index ["external_identifier"], name: "index_participant_identities_on_external_identifier", unique: true
- t.index ["user_id"], name: "index_participant_identities_on_user_id"
- end
-
- create_table "participant_outcome_api_requests", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "request_path"
- t.integer "status_code"
- t.jsonb "request_headers"
- t.jsonb "request_body"
- t.jsonb "response_body"
- t.jsonb "response_headers"
- t.uuid "participant_outcome_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_outcome_id"], name: "index_participant_outcome_api_requests_on_participant_outcome"
- end
-
- create_table "participant_outcomes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "state", null: false
- t.date "completion_date", null: false
- t.uuid "participant_declaration_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "qualified_teachers_api_request_successful"
- t.datetime "sent_to_qualified_teachers_api_at", precision: nil
- t.index ["created_at"], name: "index_participant_outcomes_on_created_at"
- t.index ["participant_declaration_id"], name: "index_declaration"
- t.index ["sent_to_qualified_teachers_api_at"], name: "index_participant_outcomes_on_sent_to_qualified_teachers_api_at"
- t.index ["state"], name: "index_participant_outcomes_on_state"
- end
-
- create_table "participant_profile_completion_date_inconsistencies", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.date "dqt_value"
- t.date "participant_value"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_profile_id"], name: "idx_on_participant_profile_id_b55d68e537", unique: true
- end
-
- create_table "participant_profile_schedules", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.uuid "schedule_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_profile_id"], name: "index_participant_profile_schedules_on_participant_profile_id"
- t.index ["schedule_id"], name: "index_participant_profile_schedules_on_schedule_id"
- end
-
- create_table "participant_profile_states", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.text "state", default: "active"
- t.text "reason"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "cpd_lead_provider_id"
- t.index ["cpd_lead_provider_id"], name: "index_participant_profile_states_on_cpd_lead_provider_id"
- t.index ["participant_profile_id", "cpd_lead_provider_id"], name: "index_on_profile_and_lead_provider"
- t.index ["participant_profile_id", "state", "cpd_lead_provider_id"], name: "index_on_profile_and_state_and_lead_provider"
- t.index ["participant_profile_id"], name: "index_participant_profile_states_on_participant_profile_id"
- end
-
- create_table "participant_profiles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "type", null: false
- t.uuid "school_id"
- t.uuid "core_induction_programme_id"
- t.uuid "cohort_id"
- t.uuid "mentor_profile_id"
- t.boolean "sparsity_uplift", default: false, null: false
- t.boolean "pupil_premium_uplift", default: false, null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.text "status", default: "active", null: false
- t.uuid "school_cohort_id"
- t.uuid "teacher_profile_id"
- t.uuid "schedule_id", null: false
- t.uuid "npq_course_id"
- t.text "school_urn"
- t.text "school_ukprn"
- t.datetime "request_for_details_sent_at", precision: nil
- t.string "training_status", default: "active", null: false
- t.string "profile_duplicity", default: "single", null: false
- t.uuid "participant_identity_id"
- t.string "notes"
- t.date "induction_start_date"
- t.date "induction_completion_date"
- t.date "mentor_completion_date"
- t.string "mentor_completion_reason"
- t.boolean "cohort_changed_after_payments_frozen", default: false
- t.index ["cohort_id"], name: "index_participant_profiles_on_cohort_id"
- t.index ["core_induction_programme_id"], name: "index_participant_profiles_on_core_induction_programme_id"
- t.index ["mentor_profile_id"], name: "index_participant_profiles_on_mentor_profile_id"
- t.index ["npq_course_id"], name: "index_participant_profiles_on_npq_course_id"
- t.index ["participant_identity_id"], name: "index_participant_profiles_on_participant_identity_id"
- t.index ["schedule_id"], name: "index_participant_profiles_on_schedule_id"
- t.index ["school_cohort_id"], name: "index_participant_profiles_on_school_cohort_id"
- t.index ["school_id"], name: "index_participant_profiles_on_school_id"
- t.index ["teacher_profile_id"], name: "index_participant_profiles_on_teacher_profile_id"
- end
-
- create_table "partnership_csv_uploads", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "lead_provider_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "delivery_partner_id", null: false
- t.uuid "cohort_id"
- t.string "uploaded_urns", array: true
- t.index ["cohort_id"], name: "index_partnership_csv_uploads_on_cohort_id"
- t.index ["delivery_partner_id"], name: "index_partnership_csv_uploads_on_delivery_partner_id"
- t.index ["lead_provider_id"], name: "index_partnership_csv_uploads_on_lead_provider_id"
- end
-
- create_table "partnership_notification_emails", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "token", null: false
- t.string "sent_to", null: false
- t.string "email_type", null: false
- t.string "notify_id"
- t.string "notify_status"
- t.datetime "delivered_at", precision: nil
- t.uuid "partnership_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["notify_id"], name: "index_partnership_notification_emails_on_notify_id"
- t.index ["partnership_id"], name: "index_partnership_notification_emails_on_partnership_id"
- t.index ["token"], name: "index_partnership_notification_emails_on_token", unique: true
- end
-
- create_table "partnerships", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.uuid "school_id", null: false
- t.uuid "lead_provider_id", null: false
- t.uuid "cohort_id", null: false
- t.uuid "delivery_partner_id"
- t.datetime "challenged_at", precision: nil
- t.string "challenge_reason"
- t.datetime "challenge_deadline", precision: nil
- t.boolean "pending", default: false, null: false
- t.uuid "report_id"
- t.boolean "relationship", default: false, null: false
- t.index ["cohort_id"], name: "index_partnerships_on_cohort_id"
- t.index ["delivery_partner_id"], name: "index_partnerships_on_delivery_partner_id"
- t.index ["lead_provider_id"], name: "index_partnerships_on_lead_provider_id"
- t.index ["pending"], name: "index_partnerships_on_pending"
- t.index ["school_id", "lead_provider_id", "delivery_partner_id", "cohort_id"], name: "unique_partnerships", unique: true
- t.index ["school_id"], name: "index_partnerships_on_school_id"
- end
-
- create_table "privacy_policies", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.integer "major_version", null: false
- t.integer "minor_version", null: false
- t.text "html", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["major_version", "minor_version"], name: "index_privacy_policies_on_major_version_and_minor_version", unique: true
- end
-
- create_table "privacy_policy_acceptances", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "privacy_policy_id"
- t.uuid "user_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["privacy_policy_id", "user_id"], name: "single-acceptance", unique: true
- end
-
- create_table "profile_validation_decisions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.string "validation_step", null: false
- t.boolean "approved"
- t.text "note"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_profile_id", "validation_step"], name: "unique_validation_step", unique: true
- t.index ["participant_profile_id"], name: "index_profile_validation_decisions_on_participant_profile_id"
- end
-
- create_table "provider_relationships", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "lead_provider_id", null: false
- t.uuid "delivery_partner_id", null: false
- t.uuid "cohort_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.datetime "discarded_at", precision: nil
- t.index ["cohort_id"], name: "index_provider_relationships_on_cohort_id"
- t.index ["delivery_partner_id"], name: "index_provider_relationships_on_delivery_partner_id"
- t.index ["discarded_at"], name: "index_provider_relationships_on_discarded_at"
- t.index ["lead_provider_id"], name: "index_provider_relationships_on_lead_provider_id"
- end
-
- create_table "pupil_premiums", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "school_id", null: false
- t.integer "start_year", limit: 2, null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "pupil_premium_incentive", default: false, null: false
- t.boolean "sparsity_incentive", default: false, null: false
- t.index ["school_id"], name: "index_pupil_premiums_on_school_id"
- end
-
- create_table "schedule_milestones", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "name", null: false
- t.uuid "schedule_id", null: false
- t.uuid "milestone_id", null: false
- t.string "declaration_type", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["milestone_id"], name: "index_schedule_milestones_on_milestone_id"
- t.index ["schedule_id"], name: "index_schedule_milestones_on_schedule_id"
- end
-
- create_table "schedules", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.text "name", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "schedule_identifier"
- t.string "type", default: "Finance::Schedule::ECF"
- t.uuid "cohort_id"
- t.text "identifier_alias"
- t.index ["cohort_id"], name: "index_schedules_on_cohort_id"
- end
-
- create_table "school_cohorts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "induction_programme_choice", null: false
- t.uuid "school_id", null: false
- t.uuid "cohort_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "estimated_teacher_count"
- t.integer "estimated_mentor_count"
- t.uuid "core_induction_programme_id"
- t.boolean "opt_out_of_updates", default: false, null: false
- t.uuid "default_induction_programme_id"
- t.uuid "appropriate_body_id"
- t.index ["appropriate_body_id"], name: "index_school_cohorts_on_appropriate_body_id"
- t.index ["cohort_id"], name: "index_school_cohorts_on_cohort_id"
- t.index ["core_induction_programme_id"], name: "index_school_cohorts_on_core_induction_programme_id"
- t.index ["default_induction_programme_id"], name: "index_school_cohorts_on_default_induction_programme_id"
- t.index ["school_id", "cohort_id"], name: "index_school_cohorts_on_school_id_and_cohort_id", unique: true
- t.index ["school_id"], name: "index_school_cohorts_on_school_id"
- t.index ["updated_at"], name: "index_school_cohorts_on_updated_at"
- end
-
- create_table "school_links", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "school_id", null: false
- t.string "link_urn", null: false
- t.string "link_type", null: false
- t.string "link_reason", default: "simple", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["school_id"], name: "index_school_links_on_school_id"
- end
-
- create_table "school_local_authorities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "school_id", null: false
- t.uuid "local_authority_id", null: false
- t.integer "start_year", limit: 2, null: false
- t.integer "end_year", limit: 2
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["local_authority_id"], name: "index_school_local_authorities_on_local_authority_id"
- t.index ["school_id"], name: "index_school_local_authorities_on_school_id"
- end
-
- create_table "school_local_authority_districts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "school_id", null: false
- t.uuid "local_authority_district_id", null: false
- t.integer "start_year", limit: 2, null: false
- t.integer "end_year", limit: 2
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["local_authority_district_id"], name: "index_schools_lads_on_lad_id"
- t.index ["school_id"], name: "index_school_local_authority_districts_on_school_id"
- end
-
- create_table "school_mentors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.uuid "school_id", null: false
- t.uuid "preferred_identity_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.date "remove_from_school_on"
- t.index ["participant_profile_id", "school_id"], name: "index_school_mentors_on_participant_profile_id_and_school_id", unique: true
- t.index ["participant_profile_id"], name: "index_school_mentors_on_participant_profile_id"
- t.index ["preferred_identity_id"], name: "index_school_mentors_on_preferred_identity_id"
- t.index ["school_id"], name: "index_school_mentors_on_school_id"
- end
-
- create_table "schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.string "urn", null: false
- t.string "name", null: false
- t.integer "school_type_code"
- t.string "address_line1", null: false
- t.string "address_line2"
- t.string "address_line3"
- t.string "postcode", null: false
- t.uuid "network_id"
- t.string "domains", default: [], null: false, array: true
- t.string "school_type_name"
- t.string "ukprn"
- t.integer "school_phase_type"
- t.string "school_phase_name"
- t.string "school_website"
- t.integer "school_status_code"
- t.string "school_status_name"
- t.string "secondary_contact_email"
- t.string "primary_contact_email"
- t.string "administrative_district_code"
- t.string "administrative_district_name"
- t.string "slug"
- t.boolean "section_41_approved", default: false, null: false
- t.index ["name"], name: "index_schools_on_name"
- t.index ["network_id"], name: "index_schools_on_network_id"
- t.index ["school_type_code", "school_status_code"], name: "index_schools_on_school_type_code_and_school_status_code"
- t.index ["section_41_approved", "school_status_code"], name: "index_schools_on_section_41_approved_and_school_status_code", where: "section_41_approved"
- t.index ["slug"], name: "index_schools_on_slug", unique: true
- t.index ["urn"], name: "index_schools_on_urn", unique: true
- end
-
- create_table "sessions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "session_id", null: false
- t.jsonb "data"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["session_id"], name: "index_sessions_on_session_id", unique: true
- t.index ["updated_at"], name: "index_sessions_on_updated_at"
- end
-
- create_table "statement_line_items", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "statement_id", null: false
- t.uuid "participant_declaration_id", null: false
- t.text "state", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_declaration_id", "statement_id", "state"], name: "unique_declaration_statement_state", unique: true
- t.index ["statement_id", "participant_declaration_id", "state"], name: "unique_statement_declaration_state", unique: true
- end
-
- create_table "statements", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.text "type", null: false
- t.text "name", null: false
- t.uuid "cpd_lead_provider_id", null: false
- t.date "deadline_date"
- t.date "payment_date"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.decimal "original_value"
- t.uuid "cohort_id", null: false
- t.boolean "output_fee", default: true
- t.string "contract_version", default: "0.0.1"
- t.decimal "reconcile_amount", default: "0.0", null: false
- t.datetime "marked_as_paid_at"
- t.index ["cohort_id"], name: "index_statements_on_cohort_id"
- t.index ["cpd_lead_provider_id"], name: "index_statements_on_cpd_lead_provider_id"
- end
-
- create_table "support_queries", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "user_id"
- t.integer "zendesk_ticket_id"
- t.string "subject", null: false
- t.string "message", null: false
- t.jsonb "additional_information", default: {}, null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["subject"], name: "index_support_queries_on_subject"
- t.index ["user_id", "subject"], name: "index_support_queries_on_user_id_and_subject"
- t.index ["user_id"], name: "index_support_queries_on_user_id"
- end
-
- create_table "sync_dqt_induction_start_date_errors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.uuid "participant_profile_id", null: false
- t.text "message"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["participant_profile_id"], name: "dqt_sync_participant_profile_id"
- end
-
- create_table "teacher_profiles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "trn"
- t.uuid "school_id"
- t.uuid "user_id", null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["school_id"], name: "index_teacher_profiles_on_school_id"
- t.index ["user_id"], name: "index_teacher_profiles_on_user_id", unique: true
- end
-
- create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "full_name", null: false
- t.citext "email", default: "", null: false
- t.string "login_token"
- t.datetime "login_token_valid_until", precision: nil
- t.datetime "remember_created_at", precision: nil
- t.datetime "last_sign_in_at", precision: nil
- t.datetime "current_sign_in_at", precision: nil
- t.inet "current_sign_in_ip"
- t.inet "last_sign_in_ip"
- t.integer "sign_in_count", default: 0, null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.datetime "discarded_at", precision: nil
- t.string "get_an_identity_id"
- t.string "archived_email"
- t.datetime "archived_at", precision: nil
- t.index ["discarded_at"], name: "index_users_on_discarded_at"
- t.index ["email"], name: "index_users_on_email", unique: true
- t.index ["get_an_identity_id"], name: "index_users_on_get_an_identity_id", unique: true
- end
-
- create_table "validation_errors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "form_object", null: false
- t.uuid "user_id"
- t.string "request_path", null: false
- t.jsonb "details"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.index ["form_object"], name: "index_validation_errors_on_form_object"
- end
-
- create_table "versions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
- t.string "item_type", null: false
- t.string "event", null: false
- t.string "whodunnit"
- t.json "object"
- t.json "object_changes"
- t.datetime "created_at", precision: nil
- t.uuid "item_id", default: -> { "gen_random_uuid()" }, null: false
- t.string "reason"
- t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id"
- end
-
- add_foreign_key "additional_school_emails", "schools"
- add_foreign_key "admin_profiles", "users"
- add_foreign_key "api_requests", "cpd_lead_providers"
- add_foreign_key "api_tokens", "cpd_lead_providers"
- add_foreign_key "api_tokens", "lead_providers", on_delete: :cascade
- add_foreign_key "appropriate_body_profiles", "appropriate_bodies"
- add_foreign_key "appropriate_body_profiles", "users"
- add_foreign_key "call_off_contracts", "lead_providers"
- add_foreign_key "cohorts_lead_providers", "cohorts"
- add_foreign_key "cohorts_lead_providers", "lead_providers"
- add_foreign_key "completion_candidates", "participant_profiles", on_delete: :cascade
- add_foreign_key "continue_training_cohort_change_errors", "participant_profiles"
- add_foreign_key "data_stage_school_changes", "data_stage_schools"
- add_foreign_key "data_stage_school_links", "data_stage_schools"
- add_foreign_key "deleted_duplicates", "participant_profiles", column: "primary_participant_profile_id"
- add_foreign_key "delivery_partner_profiles", "delivery_partners"
- add_foreign_key "delivery_partner_profiles", "users"
- add_foreign_key "district_sparsities", "local_authority_districts"
- add_foreign_key "ecf_participant_eligibilities", "participant_profiles"
- add_foreign_key "ecf_participant_validation_data", "participant_profiles"
- add_foreign_key "email_associations", "emails"
- add_foreign_key "feature_selected_objects", "features"
- add_foreign_key "finance_adjustments", "statements"
- add_foreign_key "finance_profiles", "users"
- add_foreign_key "induction_coordinator_profiles", "users"
- add_foreign_key "induction_programmes", "core_induction_programmes"
- add_foreign_key "induction_programmes", "partnerships"
- add_foreign_key "induction_programmes", "school_cohorts"
- add_foreign_key "induction_records", "induction_programmes"
- add_foreign_key "induction_records", "participant_profiles"
- add_foreign_key "induction_records", "schedules"
- add_foreign_key "lead_provider_cips", "cohorts"
- add_foreign_key "lead_provider_cips", "core_induction_programmes"
- add_foreign_key "lead_provider_cips", "lead_providers"
- add_foreign_key "lead_provider_profiles", "lead_providers"
- add_foreign_key "lead_provider_profiles", "users"
- add_foreign_key "lead_providers", "cpd_lead_providers"
- add_foreign_key "milestones", "schedules"
- add_foreign_key "nomination_emails", "partnership_notification_emails"
- add_foreign_key "nomination_emails", "schools"
- add_foreign_key "npq_application_exports", "users"
- add_foreign_key "npq_applications", "npq_courses"
- add_foreign_key "npq_applications", "npq_lead_providers"
- add_foreign_key "npq_applications", "participant_identities"
- add_foreign_key "npq_applications", "users", column: "eligible_for_funding_updated_by_id"
- add_foreign_key "npq_lead_providers", "cpd_lead_providers"
- add_foreign_key "participant_bands", "call_off_contracts"
- add_foreign_key "participant_declaration_attempts", "participant_declarations"
- add_foreign_key "participant_declarations", "participant_declarations", column: "superseded_by_id"
- add_foreign_key "participant_declarations", "participant_profiles"
- add_foreign_key "participant_declarations", "users"
- add_foreign_key "participant_declarations", "users", column: "mentor_user_id"
- add_foreign_key "participant_id_changes", "users"
- add_foreign_key "participant_identities", "users"
- add_foreign_key "participant_outcome_api_requests", "participant_outcomes"
- add_foreign_key "participant_outcomes", "participant_declarations"
- add_foreign_key "participant_profile_completion_date_inconsistencies", "participant_profiles"
- add_foreign_key "participant_profile_schedules", "participant_profiles"
- add_foreign_key "participant_profile_schedules", "schedules"
- add_foreign_key "participant_profile_states", "participant_profiles"
- add_foreign_key "participant_profiles", "cohorts"
- add_foreign_key "participant_profiles", "core_induction_programmes"
- add_foreign_key "participant_profiles", "npq_courses"
- add_foreign_key "participant_profiles", "participant_identities"
- add_foreign_key "participant_profiles", "participant_profiles", column: "mentor_profile_id"
- add_foreign_key "participant_profiles", "schedules"
- add_foreign_key "participant_profiles", "school_cohorts"
- add_foreign_key "participant_profiles", "schools"
- add_foreign_key "participant_profiles", "teacher_profiles"
- add_foreign_key "partnership_notification_emails", "partnerships"
- add_foreign_key "partnerships", "cohorts"
- add_foreign_key "partnerships", "delivery_partners"
- add_foreign_key "partnerships", "lead_providers"
- add_foreign_key "partnerships", "schools"
- add_foreign_key "profile_validation_decisions", "participant_profiles"
- add_foreign_key "provider_relationships", "cohorts"
- add_foreign_key "provider_relationships", "delivery_partners"
- add_foreign_key "provider_relationships", "lead_providers"
- add_foreign_key "pupil_premiums", "schools"
- add_foreign_key "schedule_milestones", "milestones"
- add_foreign_key "schedule_milestones", "schedules"
- add_foreign_key "schedules", "cohorts"
- add_foreign_key "school_cohorts", "cohorts"
- add_foreign_key "school_cohorts", "core_induction_programmes"
- add_foreign_key "school_cohorts", "induction_programmes", column: "default_induction_programme_id"
- add_foreign_key "school_cohorts", "schools"
- add_foreign_key "school_local_authorities", "local_authorities"
- add_foreign_key "school_local_authorities", "schools"
- add_foreign_key "school_local_authority_districts", "local_authority_districts"
- add_foreign_key "school_local_authority_districts", "schools"
- add_foreign_key "school_mentors", "participant_identities", column: "preferred_identity_id"
- add_foreign_key "school_mentors", "participant_profiles"
- add_foreign_key "school_mentors", "schools"
- add_foreign_key "schools", "networks"
- add_foreign_key "support_queries", "users"
- add_foreign_key "sync_dqt_induction_start_date_errors", "participant_profiles"
- add_foreign_key "teacher_profiles", "schools"
- add_foreign_key "teacher_profiles", "users"
-
- create_view "ecf_duplicates", sql_definition: <<-SQL
- SELECT participant_identities.user_id,
- participant_identities.external_identifier,
- participant_profiles.id,
- participant_profiles.created_at,
- participant_profiles.updated_at,
- first_value(participant_profiles.id) OVER (PARTITION BY participant_identities.user_id ORDER BY
- CASE
- WHEN (((latest_induction_records.training_status)::text = 'active'::text) AND ((latest_induction_records.induction_status)::text = 'active'::text)) THEN 1
- WHEN (((latest_induction_records.training_status)::text = 'active'::text) AND ((latest_induction_records.induction_status)::text <> 'active'::text)) THEN 2
- WHEN (((latest_induction_records.training_status)::text <> 'active'::text) AND ((latest_induction_records.induction_status)::text = 'active'::text)) THEN 3
- ELSE 4
- END, COALESCE(declarations.count, (0)::bigint) DESC, participant_profiles.created_at DESC) AS primary_participant_profile_id,
- CASE participant_profiles.type
- WHEN 'ParticipantProfile::Mentor'::text THEN 'mentor'::text
- ELSE 'ect'::text
- END AS profile_type,
- duplicates.count AS duplicate_profile_count,
- latest_induction_records.id AS latest_induction_record_id,
- latest_induction_records.induction_status,
- latest_induction_records.training_status,
- latest_induction_records.start_date,
- latest_induction_records.end_date,
- latest_induction_records.school_transfer,
- latest_induction_records.school_id,
- latest_induction_records.school_name,
- latest_induction_records.lead_provider_name AS provider_name,
- latest_induction_records.training_programme,
- schedules.schedule_identifier,
- cohorts.start_year AS cohort,
- teacher_profiles.trn AS teacher_profile_trn,
- teacher_profiles.id AS teacher_profile_id,
- COALESCE(declarations.count, (0)::bigint) AS declaration_count,
- row_number() OVER (PARTITION BY participant_identities.user_id ORDER BY
- CASE
- WHEN (((latest_induction_records.training_status)::text = 'active'::text) AND ((latest_induction_records.induction_status)::text = 'active'::text)) THEN 1
- WHEN (((latest_induction_records.training_status)::text = 'active'::text) AND ((latest_induction_records.induction_status)::text <> 'active'::text)) THEN 2
- WHEN (((latest_induction_records.training_status)::text <> 'active'::text) AND ((latest_induction_records.induction_status)::text = 'active'::text)) THEN 3
- ELSE 4
- END) AS participant_profile_status
- FROM (((((((participant_profiles
- LEFT JOIN ( SELECT induction_records.id,
- induction_records.induction_programme_id,
- induction_records.participant_profile_id,
- induction_records.schedule_id,
- induction_records.start_date,
- induction_records.end_date,
- induction_records.created_at,
- induction_records.updated_at,
- induction_records.training_status,
- induction_records.preferred_identity_id,
- induction_records.induction_status,
- induction_records.mentor_profile_id,
- induction_records.school_transfer,
- induction_records.appropriate_body_id,
- partnerships.lead_provider_id,
- schools.id AS school_id,
- schools.name AS school_name,
- lead_providers.name AS lead_provider_name,
- induction_programmes.training_programme,
- row_number() OVER (PARTITION BY induction_records.participant_profile_id ORDER BY induction_records.created_at DESC) AS induction_record_sort_order
- FROM (((((induction_records
- JOIN induction_programmes ON ((induction_programmes.id = induction_records.induction_programme_id)))
- LEFT JOIN partnerships ON ((partnerships.id = induction_programmes.partnership_id)))
- LEFT JOIN lead_providers ON ((lead_providers.id = partnerships.lead_provider_id)))
- LEFT JOIN school_cohorts ON ((school_cohorts.id = induction_programmes.school_cohort_id)))
- LEFT JOIN schools ON ((schools.id = school_cohorts.school_id)))) latest_induction_records ON (((latest_induction_records.participant_profile_id = participant_profiles.id) AND (latest_induction_records.induction_record_sort_order = 1))))
- JOIN participant_identities ON ((participant_identities.id = participant_profiles.participant_identity_id)))
- JOIN ( SELECT participant_identities_1.user_id,
- count(*) AS count
- FROM (participant_profiles participant_profiles_1
- JOIN participant_identities participant_identities_1 ON ((participant_identities_1.id = participant_profiles_1.participant_identity_id)))
- WHERE ((participant_profiles_1.type)::text = ANY (ARRAY[('ParticipantProfile::ECT'::character varying)::text, ('ParticipantProfile::Mentor'::character varying)::text]))
- GROUP BY participant_profiles_1.type, participant_identities_1.user_id) duplicates ON ((duplicates.user_id = participant_identities.user_id)))
- LEFT JOIN teacher_profiles ON ((teacher_profiles.id = participant_profiles.teacher_profile_id)))
- LEFT JOIN schedules ON ((latest_induction_records.schedule_id = schedules.id)))
- LEFT JOIN cohorts ON ((schedules.cohort_id = cohorts.id)))
- LEFT JOIN ( SELECT participant_declarations.participant_profile_id,
- count(*) AS count
- FROM participant_declarations
- GROUP BY participant_declarations.participant_profile_id) declarations ON ((participant_profiles.id = declarations.participant_profile_id)))
- WHERE ((participant_identities.user_id IN ( SELECT participant_identities_1.user_id
- FROM (participant_profiles participant_profiles_1
- JOIN participant_identities participant_identities_1 ON ((participant_identities_1.id = participant_profiles_1.participant_identity_id)))
- WHERE ((participant_profiles_1.type)::text = ANY (ARRAY[('ParticipantProfile::ECT'::character varying)::text, ('ParticipantProfile::Mentor'::character varying)::text]))
- GROUP BY participant_profiles_1.type, participant_identities_1.user_id
- HAVING (count(*) > 1))) AND ((participant_profiles.type)::text = ANY (ARRAY[('ParticipantProfile::ECT'::character varying)::text, ('ParticipantProfile::Mentor'::character varying)::text])))
- ORDER BY participant_identities.external_identifier, (row_number() OVER (PARTITION BY participant_identities.user_id ORDER BY
- CASE
- WHEN (((latest_induction_records.training_status)::text = 'active'::text) AND ((latest_induction_records.induction_status)::text = 'active'::text)) THEN 1
- WHEN (((latest_induction_records.training_status)::text = 'active'::text) AND ((latest_induction_records.induction_status)::text <> 'active'::text)) THEN 2
- WHEN (((latest_induction_records.training_status)::text <> 'active'::text) AND ((latest_induction_records.induction_status)::text = 'active'::text)) THEN 3
- ELSE 4
- END)), participant_profiles.created_at DESC;
- SQL
-end
diff --git a/spec/factories/migration/ecf/npq_lead_providers.rb b/spec/factories/migration/ecf/npq_lead_providers.rb
deleted file mode 100644
index 72584c3e87..0000000000
--- a/spec/factories/migration/ecf/npq_lead_providers.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :ecf_migration_npq_lead_provider, class: "Migration::Ecf::NpqLeadProvider" do
- sequence(:name) { |n| "NPQ Lead Provider #{n}" }
- end
-end
diff --git a/spec/factories/migration/parity_check/response_comparisons.rb b/spec/factories/migration/parity_check/response_comparisons.rb
deleted file mode 100644
index af4572f873..0000000000
--- a/spec/factories/migration/parity_check/response_comparisons.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :response_comparison, class: "Migration::ParityCheck::ResponseComparison" do
- lead_provider
- request_path { "/path" }
- request_method { "get" }
- ecf_response_time_ms { 100 }
- npq_response_time_ms { 50 }
- equal
-
- trait :equal do
- ecf_response_status_code { 200 }
- npq_response_status_code { 200 }
- end
-
- trait :different do
- ecf_response_status_code { 200 }
- npq_response_status_code { 200 }
- ecf_response_body { "response1" }
- npq_response_body { "response2" }
- end
-
- trait :unexpected do
- ecf_response_status_code { 500 }
- npq_response_status_code { 500 }
- end
-
- after :create do |response_comparison|
- create(:ecf_migration_npq_lead_provider, id: response_comparison.lead_provider.ecf_id) unless Migration::Ecf::NpqLeadProvider.exists?(response_comparison.lead_provider.ecf_id)
- end
- end
-end
diff --git a/spec/features/parity_check_spec.rb b/spec/features/parity_check_spec.rb
deleted file mode 100644
index 6867b3fc95..0000000000
--- a/spec/features/parity_check_spec.rb
+++ /dev/null
@@ -1,219 +0,0 @@
-require "rails_helper"
-
-RSpec.feature "Parity check", :ecf_api_disabled, :in_memory_rails_cache, :rack_test_driver, type: :feature do
- include Helpers::AdminLogin
-
- before do
- create_matching_ecf_lead_providers
-
- stub_request(:get, %r{http://(npq|ecf).example.com/api/.*})
- stub_request(:post, %r{http://(npq|ecf).example.com/api/.*})
- stub_request(:put, %r{http://(npq|ecf).example.com/api/.*})
-
- LeadProvider.find_each do |lead_provider|
- create(:statement, lead_provider:)
- create(:participant_outcome, declaration: create(:declaration, lead_provider:))
- create(:application, lead_provider:, eligible_for_funding: false)
- create(:declaration, :completed, application: create(:application, :accepted, lead_provider:))
- create(:application, :accepted, lead_provider:, funded_place: true)
- create(:declaration, :payable, lead_provider:)
- create(:declaration, :paid, lead_provider:)
- create(:application, :with_declaration, :accepted, :active, lead_provider:)
- create(:application, :with_declaration, :accepted, :deferred, lead_provider:)
- end
- end
-
- context "when not authenticated" do
- scenario "viewing the parity checks page" do
- visit npq_separation_migration_parity_checks_path
-
- expect(page).to have_current_path(sign_in_path)
- end
- end
-
- context "when authenticated" do
- before { sign_in_as(create(:super_admin)) }
-
- scenario "viewing the parity checks prior to running one" do
- visit npq_separation_migration_parity_checks_path
-
- expect(page).to have_button("Run parity check")
-
- expect(page).not_to have_content("A parity check is currently in-progress")
- expect(page).not_to have_content("Completed parity check")
- end
-
- scenario "running a parity check" do
- visit npq_separation_migration_parity_checks_path
-
- click_button "Run parity check"
-
- expect(page).to have_button("Run parity check", disabled: true)
-
- expect(page).to have_content("A parity check is currently in-progress")
- expect(page).to have_content("It was started less than a minute ago.")
-
- expect(page).not_to have_content("Completed parity check")
- end
-
- scenario "viewing the completed parity check" do
- visit npq_separation_migration_parity_checks_path
-
- perform_enqueued_jobs do
- click_button "Run parity check"
- end
-
- expect(page).not_to have_content("A parity check is currently in-progress")
- expect(page).not_to have_content("It was started less than a minute ago.")
-
- expect(page).to have_content("Completed parity check")
-
- expect(page).to have_content("The latest parity check was completed less than a minute ago.")
- expect(page).to have_text(/The parity check took (.*) to complete./)
-
- expect(page).to have_css("#response-time-chart")
-
- Migration::ParityCheck::ResponseComparison.by_lead_provider.each do |lead_provider_name, comparisons_by_description|
- within("##{lead_provider_name.parameterize}-section") do
- expect(page).to have_text(lead_provider_name)
-
- comparisons_by_description.each_key do |description|
- expect(page).to have_text(description)
- expect(page).not_to have_link
- expect(page).to have_text("EQUAL")
- end
- end
- end
- end
-
- scenario "viewing the completed parity check when it contains differences" do
- visit npq_separation_migration_parity_checks_path
-
- perform_enqueued_jobs do
- click_button "Run parity check"
- end
-
- different_comparison = create(:response_comparison, :different)
-
- # Reload to display the different comparison created manually
- visit current_path
-
- within("##{different_comparison.lead_provider_name.parameterize}-section") do
- details_path = response_comparison_npq_separation_migration_parity_checks_path(different_comparison)
- expect(page).to have_link(different_comparison.description, href: details_path)
- expect(page).to have_text("DIFFERENT")
- end
- end
-
- scenario "viewing the completed parity check when it contains unexpected response status codes" do
- visit npq_separation_migration_parity_checks_path
-
- perform_enqueued_jobs do
- click_button "Run parity check"
- end
-
- unexpected_comparison = create(:response_comparison, :unexpected)
-
- # Reload to display the unexpected comparison created manually
- visit current_path
-
- within("##{unexpected_comparison.lead_provider_name.parameterize}-section") do
- details_path = response_comparison_npq_separation_migration_parity_checks_path(unexpected_comparison)
- expect(page).to have_link(unexpected_comparison.description, href: details_path)
- expect(page).to have_text("UNEXPECTED")
- end
- end
-
- scenario "viewing the details of a response comparison" do
- visit npq_separation_migration_parity_checks_path
-
- perform_enqueued_jobs do
- click_button "Run parity check"
- end
-
- different_comparison = create(:response_comparison, :different)
-
- # Reload to display the different comparison created manually
- visit current_path
-
- click_link(different_comparison.description)
-
- expect(page).to have_css(".govuk-caption-xl", text: different_comparison.lead_provider_name)
- expect(page).to have_css("h1", text: different_comparison.description)
-
- expect(page).to have_text("Response diff")
-
- within("tbody tr:first") do
- expect(page).to have_text("Response time")
- expect(page).to have_text(/\d(ms)/)
- expect(page).to have_text("🚀 2x faster")
- end
-
- within("tbody tr:last") do
- expect(page).to have_text("Status code")
- expect(page).to have_text("200")
- expect(page).to have_text("200")
- expect(page).to have_text("EQUAL")
- end
-
- expect(page).to have_css(".diff", text: "response1 response2")
- end
-
- scenario "viewing the details of a response comparison when there are multiple pages" do
- visit npq_separation_migration_parity_checks_path
-
- perform_enqueued_jobs do
- click_button "Run parity check"
- end
-
- different_comparison = create(:response_comparison, :different, page: 1)
- lead_provider = different_comparison.lead_provider
- body = %({ "data": [{ "id": "1" }, { "id": "1" }] })
- create(:response_comparison, :unexpected, npq_response_body: body, ecf_response_body: body, page: 2, lead_provider:)
- create(:response_comparison, :equal, page: 3, lead_provider:)
-
- # Reload to display the different comparison created manually
- visit current_path
-
- click_link(different_comparison.description)
-
- expect(page).to have_css(".govuk-caption-xl", text: different_comparison.lead_provider_name)
- expect(page).to have_css("h1", text: different_comparison.description)
-
- expect(page).to have_text("Overview (3 pages)")
-
- within("tbody tr:first") do
- expect(page).to have_text("Equality check")
- expect(page).to have_text("DIFFERENT")
- end
-
- within("tbody tr:nth-of-type(2)") do
- expect(page).to have_text("Average response time")
- expect(page).to have_text("100ms")
- expect(page).to have_text("50ms")
- expect(page).to have_text("🚀 2x faster")
- end
-
- within("tbody tr:nth-of-type(3)") do
- expect(page).to have_text("ID duplicates check")
- expect(page).to have_text("YES")
- expect(page).to have_text("YES")
- expect(page).to have_text("DUPLICATES")
- end
-
- within("tbody tr:nth-of-type(4)") do
- expect(page).to have_text("ID equality check")
- expect(page).to have_text("2")
- expect(page).to have_text("2")
- expect(page).to have_text("EQUAL")
- end
-
- expect(page).to have_css(".govuk-grid-row", text: "Page 1\nECF: 200 NPQ: 200")
-
- expect(page).to have_css(".govuk-grid-row", text: "Page 2\nECF: 500 NPQ: 500")
- expect(page).to have_text("No difference")
-
- expect(page).not_to have_text("Page 3")
- end
- end
-end
diff --git a/spec/helpers/migration_helper_spec.rb b/spec/helpers/migration_helper_spec.rb
deleted file mode 100644
index 21147f237e..0000000000
--- a/spec/helpers/migration_helper_spec.rb
+++ /dev/null
@@ -1,197 +0,0 @@
-require "rails_helper"
-
-RSpec.describe MigrationHelper, type: :helper do
- around do |example|
- freeze_time { example.run }
- end
-
- describe ".response_comparison_status_tag" do
- subject { helper.response_comparison_status_tag(different) }
-
- context "when not different" do
- let(:different) { false }
-
- it { is_expected.to have_css("strong.govuk-tag.govuk-tag--green", text: "EQUAL") }
-
- context "when equal_text is specified" do
- subject { helper.response_comparison_status_tag(different, equal_text: "yes") }
-
- it { is_expected.to have_css("strong.govuk-tag.govuk-tag--green", text: "YES") }
- end
- end
-
- context "when different" do
- let(:different) { true }
-
- it { is_expected.to have_css("strong.govuk-tag.govuk-tag--red", text: "DIFFERENT") }
-
- context "when different_text is specified" do
- subject { helper.response_comparison_status_tag(different, different_text: "no") }
-
- it { is_expected.to have_css("strong.govuk-tag.govuk-tag--red", text: "NO") }
- end
- end
- end
-
- describe ".response_comparison_performance" do
- let(:comparisons) { [response_comparison1, response_comparison2] }
-
- subject { helper.response_comparison_performance(comparisons) }
-
- context "when NPQ is slower" do
- let(:response_comparison1) { build(:response_comparison, npq_response_time_ms: 2, ecf_response_time_ms: 1) }
- let(:response_comparison2) { build(:response_comparison, npq_response_time_ms: 2, ecf_response_time_ms: 1) }
-
- it { is_expected.to have_css("strong", text: "🐌 0.5x as fast") }
- end
-
- context "when NPQ is faster" do
- let(:response_comparison1) { build(:response_comparison, npq_response_time_ms: 1, ecf_response_time_ms: 2) }
- let(:response_comparison2) { build(:response_comparison, npq_response_time_ms: 1, ecf_response_time_ms: 2) }
-
- it { is_expected.to have_css("i", text: "🚀 2x faster") }
- end
-
- context "when passed a single comparison" do
- let(:comparisons) { create(:response_comparison, npq_response_time_ms: 1, ecf_response_time_ms: 3) }
-
- it { is_expected.to have_css("i", text: "🚀 3x faster") }
- end
- end
-
- describe ".response_comparison_detail_path" do
- subject { helper.response_comparison_detail_path([response_comparison1, response_comparison2]) }
-
- context "when at least one is different" do
- let(:response_comparison1) { create(:response_comparison, :different) }
- let(:response_comparison2) { create(:response_comparison, :equal) }
-
- it { is_expected.to include(%r{/npq-separation/migration/parity_checks/response_comparisons/(#{response_comparison1.id}|#{response_comparison2.id})}) }
- end
-
- context "when at least one is unexpected" do
- let(:response_comparison1) { create(:response_comparison, :unexpected) }
- let(:response_comparison2) { create(:response_comparison, :equal) }
-
- it { is_expected.to include(%r{/npq-separation/migration/parity_checks/response_comparisons/(#{response_comparison1.id}|#{response_comparison2.id})}) }
- end
-
- context "when all are equal" do
- let(:response_comparison1) { build(:response_comparison, :equal) }
- let(:response_comparison2) { build(:response_comparison, :equal) }
-
- it { is_expected.to be_nil }
- end
- end
-
- describe ".response_comparison_response_duration_human_readable" do
- subject { helper.response_comparison_response_duration_human_readable(comparisons, :ecf_response_time_ms) }
-
- context "when the average duration is less than 1 second" do
- let(:comparisons) do
- [
- create(:response_comparison, ecf_response_time_ms: 600),
- create(:response_comparison, ecf_response_time_ms: 400),
- ]
- end
-
- it { is_expected.to eq("500ms") }
- end
-
- context "when the duration is 1 second" do
- let(:comparisons) do
- [
- create(:response_comparison, ecf_response_time_ms: 1_000),
- create(:response_comparison, ecf_response_time_ms: 1_000),
- ]
- end
-
- it { is_expected.to eq("1 second") }
- end
-
- context "when the duration is more than 1 second" do
- let(:comparisons) do
- [
- create(:response_comparison, ecf_response_time_ms: 2_000),
- create(:response_comparison, ecf_response_time_ms: 2_200),
- ]
- end
-
- it { is_expected.to eq("2 seconds") }
- end
-
- context "when passed a single comparison" do
- let(:comparisons) { create(:response_comparison, ecf_response_time_ms: 1_000) }
-
- it { is_expected.to eq("1 second") }
- end
- end
-
- describe ".response_comparison_status_code_tag" do
- subject { helper.response_comparison_status_code_tag(status_code) }
-
- context "when the status code is less than or equal to 299" do
- let(:status_code) { 200 }
-
- it { is_expected.to have_css("strong.govuk-tag.govuk-tag--green", text: "200") }
- end
-
- context "when the status code is less than or equal to 399" do
- let(:status_code) { 302 }
-
- it { is_expected.to have_css("strong.govuk-tag.govuk-tag--yellow", text: "302") }
- end
-
- context "when the status code is greater than 399" do
- let(:status_code) { 500 }
-
- it { is_expected.to have_css("strong.govuk-tag.govuk-tag--red", text: "500") }
- end
- end
-
- describe ".response_comparison_page_summary" do
- let(:comparison) { create(:response_comparison, :different, page: 1) }
-
- subject { helper.response_comparison_page_summary(comparison) }
-
- it { is_expected.to have_css(".govuk-grid-row .govuk-grid-column-two-thirds", text: "Page 1") }
- it { is_expected.to have_css(".govuk-grid-row .govuk-grid-column-one-third", text: "ECF: 200 NPQ: 200") }
- end
-
- describe ".contains_duplicate_ids?" do
- subject { helper.contains_duplicate_ids?(comparisons, :npq_response_body_ids) }
-
- context "when the comparisons contain duplicates across all comparisons" do
- let(:comparisons) do
- [
- create(:response_comparison, :different, npq_response_body: %({ "data": [{ "id": "1" }, { "id": "2" }, { "id": "3" }] })),
- create(:response_comparison, :different, npq_response_body: %({ "data": [{ "id": "1" }, { "id": "4" }, { "id": "5" }] })),
- ]
- end
-
- it { is_expected.to be(true) }
- end
-
- context "when the comparisons contain duplicates in individual comparisons" do
- let(:comparisons) do
- [
- create(:response_comparison, :different, npq_response_body: %({ "data": [{ "id": "1" }, { "id": "1" }, { "id": "3" }] })),
- create(:response_comparison, :different, npq_response_body: %({ "data": [{ "id": "4" }, { "id": "5" }, { "id": "6" }] })),
- ]
- end
-
- it { is_expected.to be(true) }
- end
-
- context "when the comparisons do not contain duplicates" do
- let(:comparisons) do
- [
- create(:response_comparison, :different, npq_response_body: %({ "data": [{ "id": "1" }, { "id": "2" }, { "id": "3" }] })),
- create(:response_comparison, :different, npq_response_body: %({ "data": [{ "id": "4" }, { "id": "5" }, { "id": "6" }] })),
- ]
- end
-
- it { is_expected.to be(false) }
- end
- end
-end
diff --git a/spec/jobs/parity_check_job_spec.rb b/spec/jobs/parity_check_job_spec.rb
deleted file mode 100644
index ef455daa2a..0000000000
--- a/spec/jobs/parity_check_job_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require "rails_helper"
-
-RSpec.describe ParityCheckJob do
- let(:instance) { described_class.new }
-
- describe "#perform" do
- subject(:perform_parity_check) { instance.perform }
-
- let(:parity_check_double) { instance_double(Migration::ParityCheck, run!: nil) }
-
- before { allow(Migration::ParityCheck).to receive(:new).and_return(parity_check_double) }
-
- it "triggers a parity check" do
- perform_parity_check
- expect(parity_check_double).to have_received(:run!).once
- end
- end
-
- describe "#perform_later" do
- it "schedules job in migration queue" do
- expect {
- described_class.perform_later
- }.to have_enqueued_job(described_class).on_queue("high_priority")
- end
- end
-end
diff --git a/spec/models/migration/ecf/base_record_spec.rb b/spec/models/migration/ecf/base_record_spec.rb
deleted file mode 100644
index b2316fd9c3..0000000000
--- a/spec/models/migration/ecf/base_record_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require "rails_helper"
-
-class TestEcfModel < Migration::Ecf::BaseRecord
- self.table_name = "cohorts"
-end
-
-RSpec.describe Migration::Ecf::BaseRecord, type: :model do
- subject(:instance) { TestEcfModel.new }
-
- before { allow(Rails).to receive(:env) { environment.inquiry } }
-
- describe "#readonly?" do
- context "when in test environment" do
- let(:environment) { "test" }
-
- it { is_expected.not_to be_readonly }
- end
-
- context "when in non-test environment" do
- let(:environment) { "production" }
-
- it { is_expected.to be_readonly }
- end
- end
-end
diff --git a/spec/models/migration/parity_check/response_comparison_spec.rb b/spec/models/migration/parity_check/response_comparison_spec.rb
deleted file mode 100644
index 1254bccab4..0000000000
--- a/spec/models/migration/parity_check/response_comparison_spec.rb
+++ /dev/null
@@ -1,343 +0,0 @@
-require "rails_helper"
-
-RSpec.describe Migration::ParityCheck::ResponseComparison, type: :model do
- describe "relationships" do
- it { is_expected.to belong_to(:lead_provider) }
- end
-
- describe "delegations" do
- it { is_expected.to delegate_method(:name).to(:lead_provider).with_prefix }
- end
-
- describe "validations" do
- it { is_expected.to validate_presence_of(:lead_provider) }
- it { is_expected.to validate_presence_of(:request_path) }
- it { is_expected.to validate_inclusion_of(:request_method).in_array(%w[get post put]) }
- it { is_expected.to validate_inclusion_of(:ecf_response_status_code).in_range(100..599) }
- it { is_expected.to validate_inclusion_of(:npq_response_status_code).in_range(100..599) }
- it { is_expected.to validate_numericality_of(:ecf_response_time_ms).is_greater_than(0) }
- it { is_expected.to validate_numericality_of(:npq_response_time_ms).is_greater_than(0) }
- it { is_expected.to validate_numericality_of(:page).only_integer.is_greater_than(0).allow_nil }
-
- context "when the response comparison is equal" do
- subject { create(:response_comparison, :equal) }
-
- it { is_expected.not_to validate_presence_of(:ecf_response_body) }
- it { is_expected.not_to validate_presence_of(:npq_response_body) }
- end
-
- context "when the response comparison is different" do
- subject { create(:response_comparison, :different) }
-
- it { is_expected.to validate_presence_of(:ecf_response_body) }
- it { is_expected.to validate_presence_of(:npq_response_body) }
- end
- end
-
- describe "before_validation" do
- it "nullifies the response bodies when the response comparison is equal" do
- response_comparison = build(:response_comparison, :equal, ecf_response_body: "response", npq_response_body: "response")
- response_comparison.valid?
-
- expect(response_comparison.ecf_response_body).to be_nil
- expect(response_comparison.npq_response_body).to be_nil
- end
-
- it "converts CSV response bodies to a hexdigest" do
- response_comparison = build(:response_comparison, request_path: "/path.csv", ecf_response_body: "ecf_response", npq_response_body: "npq_response")
- response_comparison.valid?
-
- expect(response_comparison.ecf_response_body).to eq("051e069f1405735e3c348cae238eaee95a20c898e950e23c63f118df1518327e")
- expect(response_comparison.npq_response_body).to eq("bdbfa59f301e06b4598976d83df868f62b2c1ce3aad679ee9034370fdaf9ea31")
- end
-
- it "performs a deep sort on JSON response bodies" do
- response_comparison = build(:response_comparison, ecf_response_body: %({ "foo": "bar", "baz": [2, 1] }), npq_response_body: %({ "foo": "baz", "bar": [5, 1, 4] }))
- response_comparison.valid?
-
- expect(response_comparison.ecf_response_body).to eq(
- <<~JSON.strip,
- {
- "baz": [
- 1,
- 2
- ],
- "foo": "bar"
- }
- JSON
- )
-
- expect(response_comparison.npq_response_body).to eq(
- <<~JSON.strip,
- {
- "bar": [
- 1,
- 4,
- 5
- ],
- "foo": "baz"
- }
- JSON
- )
- end
-
- it "removes excluded attributes" do
- response_comparison = build(:response_comparison, exclude: %w[baz], ecf_response_body: %({ "foo": "bar", "baz": "qux"}), npq_response_body: %({ "foo": "baz", "qux": [{ "baz": ["qux"], "foo": "bar" }] }))
- response_comparison.valid?
-
- expect(response_comparison.ecf_response_body).to eq(
- <<~JSON.strip,
- {
- "foo": "bar"
- }
- JSON
- )
-
- expect(response_comparison.npq_response_body).to eq(
- <<~JSON.strip,
- {
- "foo": "baz",
- "qux": [
- {
- "foo": "bar"
- }
- ]
- }
- JSON
- )
- end
-
- describe "populating response body ids" do
- let(:instance) { build(:response_comparison, :different, ecf_response_body: response_body, npq_response_body: response_body) }
-
- before { instance.valid? }
-
- context "when the response body contains multiple results" do
- let(:response_body) { %({ "data": [{ "id": "1" }, { "id": "2" }] }) }
-
- it { expect(instance.ecf_response_body_ids).to contain_exactly("1", "2") }
- it { expect(instance.npq_response_body_ids).to contain_exactly("1", "2") }
- end
-
- context "when the response body contains a single result" do
- let(:response_body) { %({ "data": { "id": "1" } }) }
-
- it { expect(instance.ecf_response_body_ids).to contain_exactly("1") }
- it { expect(instance.npq_response_body_ids).to contain_exactly("1") }
- end
-
- context "when the response body is not in the expected format" do
- let(:response_body) { %({ "foo": { "id": "1" } }) }
-
- it { expect(instance.ecf_response_body_ids).to be_empty }
- it { expect(instance.npq_response_body_ids).to be_empty }
- end
-
- context "when the response body is not JSON" do
- let(:response_body) { %(Error!) }
-
- it { expect(instance.ecf_response_body_ids).to be_empty }
- it { expect(instance.npq_response_body_ids).to be_empty }
- end
-
- context "when the response body is nil" do
- let(:response_body) { nil }
-
- it { expect(instance.ecf_response_body_ids).to be_empty }
- it { expect(instance.npq_response_body_ids).to be_empty }
- end
- end
- end
-
- describe "scopes" do
- describe ".matching" do
- let(:comparison) { create(:response_comparison, page: 1, lead_provider: matching_comparison.lead_provider) }
- let(:matching_comparison) { create(:response_comparison, page: 2) }
-
- before do
- # Matches apart from the lead provider
- comparison.dup.update!(lead_provider: create(:lead_provider))
-
- # Matches apart from the request path
- comparison.dup.update!(request_path: "other/path")
-
- # Matches apart from the request method
- comparison.dup.update!(request_method: :post)
- end
-
- subject { described_class.matching(comparison) }
-
- it { is_expected.to eq([comparison, matching_comparison]) }
- end
- end
-
- describe ".by_lead_provider" do
- let!(:response_comparison1) { create(:response_comparison, page: 1) }
- let!(:response_comparison2) { create(:response_comparison) }
- let!(:response_comparison3) { create(:response_comparison, lead_provider: response_comparison1.lead_provider, page: 2) }
-
- subject(:by_lead_provider) { described_class.by_lead_provider }
-
- it "groups the response comparisons by lead provider name and then description" do
- expect(by_lead_provider).to eq(
- response_comparison1.lead_provider_name => {
- response_comparison1.description => [response_comparison1, response_comparison3],
- },
- response_comparison2.lead_provider_name => {
- response_comparison2.description => [response_comparison2],
- },
- )
- end
- end
-
- describe "#description" do
- subject { build(:response_comparison, request_method: "get", request_path: "/path").description }
-
- it { is_expected.to eq("GET /path") }
- end
-
- describe "#response_body_diff" do
- let(:instance) { create(:response_comparison, :different) }
-
- subject(:diff) { instance.response_body_diff }
-
- it { is_expected.to be_a(Diffy::Diff) }
-
- it {
- expect(diff.to_s(:text)).to eq(
- <<~DIFF,
- -response1
- \\ No newline at end of file
- +response2
- \\ No newline at end of file
- DIFF
- )
- }
-
- context "when the response bodies are JSON" do
- let(:instance) { create(:response_comparison, :different, ecf_response_body: %({ "baz": "baz", "foo": "bar" }), npq_response_body: %({ "foo": "bar", "baz": "qux" })) }
-
- it {
- expect(diff.to_s(:text)).to eq(
- <<~DIFF,
- {
- - "baz": "baz",
- + "baz": "qux",
- "foo": "bar"
- }
- \\ No newline at end of file
- DIFF
- )
- }
- end
- end
-
- describe "#response_times_by_path" do
- before do
- create(:response_comparison, request_path: "/path1", ecf_response_time_ms: 100, npq_response_time_ms: 200)
- create(:response_comparison, request_path: "/path1", ecf_response_time_ms: 200, npq_response_time_ms: 200)
- create(:response_comparison, request_path: "/path2", ecf_response_time_ms: 50, npq_response_time_ms: 100)
- end
-
- subject(:result) { described_class.response_times_by_path }
-
- it "returns the response times grouped by request path" do
- expect(result).to eq({
- "GET /path1" => {
- ecf: {
- avg: 150,
- min: 100,
- max: 200,
- },
- npq: {
- avg: 200,
- min: 200,
- max: 200,
- },
- },
- "GET /path2" => {
- ecf: {
- avg: 50,
- min: 50,
- max: 50,
- },
- npq: {
- avg: 100,
- min: 100,
- max: 100,
- },
- },
- })
- end
- end
-
- describe "#different?" do
- subject(:instance) { build(:response_comparison) }
-
- context "when the status codes and response bodies are equal" do
- before { instance.assign_attributes(ecf_response_status_code: 200, npq_response_status_code: 200, ecf_response_body: "response", npq_response_body: "response") }
-
- it { is_expected.not_to be_different }
- end
-
- context "when the status codes are equal and the response bodies are both nil" do
- before { instance.assign_attributes(ecf_response_status_code: 200, npq_response_status_code: 200, ecf_response_body: nil, npq_response_body: nil) }
-
- it { is_expected.not_to be_different }
- end
-
- context "when the status codes are equal but the response bodies are different" do
- before { instance.assign_attributes(ecf_response_status_code: 200, npq_response_status_code: 200, ecf_response_body: "response1", npq_response_body: "response2") }
-
- it { is_expected.to be_different }
- end
-
- context "when the response bodies are equal but the status codes are different" do
- before { instance.assign_attributes(ecf_response_status_code: 201, npq_response_status_code: 202, ecf_response_body: "response", npq_response_body: "response") }
-
- it { is_expected.to be_different }
- end
- end
-
- describe "#unexpected?" do
- subject(:instance) { build(:response_comparison) }
-
- context "when the status codes are 200" do
- before { instance.assign_attributes(ecf_response_status_code: 200, npq_response_status_code: 200) }
-
- it { is_expected.not_to be_unexpected }
- end
-
- context "when neither status code is 200" do
- before { instance.assign_attributes(ecf_response_status_code: 201, npq_response_status_code: 422) }
-
- it { is_expected.to be_unexpected }
- end
-
- context "when one status code is not 200" do
- before { instance.assign_attributes(ecf_response_status_code: 200, npq_response_status_code: 422) }
-
- it { is_expected.to be_unexpected }
- end
- end
-
- describe "#needs_review?" do
- context "when different" do
- subject(:instance) { build(:response_comparison, :different) }
-
- it { is_expected.to be_needs_review }
- end
-
- context "when unexpected" do
- subject(:instance) { build(:response_comparison, :unexpected) }
-
- it { is_expected.to be_needs_review }
- end
-
- context "when equal" do
- subject(:instance) { build(:response_comparison, :equal) }
-
- it { is_expected.not_to be_needs_review }
- end
- end
-end
diff --git a/spec/requests/npq_separation/migration/parity_checks_controller_spec.rb b/spec/requests/npq_separation/migration/parity_checks_controller_spec.rb
deleted file mode 100644
index f0351ed7ae..0000000000
--- a/spec/requests/npq_separation/migration/parity_checks_controller_spec.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require "rails_helper"
-
-RSpec.describe NpqSeparation::Migration::ParityChecksController, :ecf_api_disabled, type: :request do
- include Helpers::NPQSeparationAdminLogin
-
- before do
- allow(Migration::ParityCheck).to receive(:prepare!)
- allow(ParityCheckJob).to receive(:perform_later)
- sign_in_as_admin(super_admin:)
- end
-
- describe("index") do
- let(:make_request) { get(npq_separation_migration_parity_checks_path) }
-
- before { make_request }
-
- context "when not signed in as a super admin" do
- let(:super_admin) { false }
-
- it { expect(response).to redirect_to(sign_in_path) }
-
- it "asks the user to sign in as an admin" do
- follow_redirect!
- expect(response.body).to include("Sign in with your administrator account")
- end
- end
-
- context "when signed in as a super admin" do
- let(:super_admin) { true }
-
- it { expect(response).to be_successful }
- end
- end
-
- describe("create") do
- let(:make_request) { post(npq_separation_migration_parity_checks_path) }
-
- before { make_request }
-
- context "when not signed in as a super admin" do
- let(:super_admin) { false }
-
- it { expect(response).to redirect_to(sign_in_path) }
-
- it "asks the user to sign in as an admin" do
- follow_redirect!
- expect(response.body).to include("Sign in with your administrator account")
- end
- end
-
- context "when signed in as a super admin" do
- let(:super_admin) { true }
-
- it "triggers a parity check" do
- expect(response).to redirect_to(npq_separation_migration_parity_checks_path)
- expect(Migration::ParityCheck).to have_received(:prepare!)
- expect(ParityCheckJob).to have_received(:perform_later)
- end
- end
- end
-
- describe("response_comparison") do
- let(:comparison) { create(:response_comparison, :different) }
- let(:make_request) { get(response_comparison_npq_separation_migration_parity_checks_path(comparison)) }
-
- before { make_request }
-
- context "when not signed in as a super admin" do
- let(:super_admin) { false }
-
- it { expect(response).to redirect_to(sign_in_path) }
-
- it "asks the user to sign in as an admin" do
- follow_redirect!
- expect(response.body).to include("Sign in with your administrator account")
- end
- end
-
- context "when signed in as a super admin" do
- let(:super_admin) { true }
-
- it { expect(response).to be_successful }
-
- context "when the response comparison is not found", :exceptions_app do
- let(:comparison) { OpenStruct.new(id: -1) }
-
- it { expect(response).to be_not_found }
- end
- end
- end
-end
diff --git a/spec/services/migration/parity_check/client_spec.rb b/spec/services/migration/parity_check/client_spec.rb
deleted file mode 100644
index 2fd7298405..0000000000
--- a/spec/services/migration/parity_check/client_spec.rb
+++ /dev/null
@@ -1,658 +0,0 @@
-require "rails_helper"
-
-RSpec.describe Migration::ParityCheck::Client do
- let(:lead_provider) { create(:lead_provider) }
- let(:path) { "/api/path" }
- let(:method) { :get }
- let(:options) { {} }
- let(:instance) { described_class.new(lead_provider:, method:, path:, options:) }
- let(:ecf_url) { "http://ecf.example.com" }
- let(:npq_url) { "http://npq.example.com" }
- let(:keys) { { lead_provider.ecf_id => SecureRandom.uuid } }
-
- before do
- allow(Rails.application.config).to receive(:npq_separation) do
- {
- parity_check: {
- enabled: true,
- ecf_url:,
- npq_url:,
- },
- }
- end
-
- allow(ENV).to receive(:[]).and_call_original
- allow(ENV).to receive(:[]).with("PARITY_CHECK_KEYS").and_return(keys.to_json)
- end
-
- describe "#initialize" do
- it { expect(instance.lead_provider).to eq(lead_provider) }
- it { expect(instance.path).to eq(path) }
- it { expect(instance.method).to eq(method) }
- it { expect(instance.options).to eq(options) }
- it { expect(instance.page).to be_nil }
-
- context "when options is nil" do
- let(:options) { nil }
-
- it { expect(instance.options).to eq({}) }
- end
-
- context "when paginate is true" do
- let(:options) { { paginate: true } }
-
- it { expect(instance.page).to eq(1) }
- end
- end
-
- shared_examples "makes valid requests and yields the results" do
- it "makes a request to each service" do
- instance.make_requests {}
-
- expect(ecf_requests.count).to eq(1)
- expect(npq_requests.count).to eq(1)
- end
-
- it "makes requests with the correct path and headers" do
- instance.make_requests {}
-
- requests.each do |request|
- expect(request.uri.path).to eq(path)
- expect(request.headers["Accept"]).to eq("application/json")
- expect(request.headers["Content-Type"]).to eq("application/json")
- end
- end
-
- it "makes requests with a valid authorization token for the lead provider" do
- instance.make_requests {}
-
- ecf_token = ecf_requests.first.headers["Authorization"].partition("Bearer ").last
- expect(ecf_token).to eq(keys[lead_provider.ecf_id])
-
- npq_token = npq_requests.first.headers["Authorization"].partition("Bearer ").last
- expect(npq_token).to eq(keys[lead_provider.ecf_id])
- end
-
- it "yields the result of each request to the block" do
- instance.make_requests do |ecf_result, npq_result, formatted_path, page|
- expect(ecf_result[:response].code).to eq(200)
- expect(ecf_result[:response].body).to eq("ecf_response_body")
- expect(ecf_result[:response_ms]).to be >= 0
-
- expect(npq_result[:response].code).to eq(201)
- expect(npq_result[:response].body).to eq("npq_response_body")
- expect(npq_result[:response_ms]).to be >= 0
-
- expect(formatted_path).to eq(path)
- expect(page).to be_nil
- end
- end
- end
-
- describe "#make_requests" do
- let(:requests) { WebMock::RequestRegistry.instance.requested_signatures.hash.keys }
- let(:ecf_requests) { requests.select { |r| r.uri.host.include?("ecf") } }
- let(:npq_requests) { requests.select { |r| r.uri.host.include?("npq") } }
-
- context "when making a POST request" do
- let(:method) { :post }
- let(:options) { { payload: { type: "type", attributes: { "key": "value" } } } }
- let(:body) { { data: options[:payload] } }
-
- before do
- stub_request(:post, "#{ecf_url}#{path}").with(body:).to_return(status: 200, body: "ecf_response_body")
- stub_request(:post, "#{npq_url}#{path}").with(body:).to_return(status: 201, body: "npq_response_body")
- end
-
- include_context "makes valid requests and yields the results"
-
- context "when the payload is not specified" do
- let(:options) { {} }
- let(:body) { {} }
-
- include_context "makes valid requests and yields the results"
- end
-
- context "when the payload is a method instead of a hash" do
- let(:options) { { payload: :post_declaration_payload } }
-
- before do
- create(:application, :accepted, lead_provider:)
-
- stub_request(:post, "#{ecf_url}#{path}").to_return(status: 200, body: "ecf_response_body")
- stub_request(:post, "#{npq_url}#{path}").to_return(status: 201, body: "npq_response_body")
- end
-
- include_context "makes valid requests and yields the results"
-
- it "uses the method to get the payload" do
- instance.make_requests {}
-
- requests.each do |request|
- body = JSON.parse(request.body)
-
- expect(body).to include({
- "data" => {
- "type" => "participant-declaration",
- "attributes" => a_hash_including({
- "declaration_type" => "started",
- }),
- },
- })
- end
- end
- end
- end
-
- context "when making a PUT request" do
- let(:method) { :put }
- let(:options) { { payload: { type: "type", attributes: { "key": "value" } } } }
- let(:body) { { data: options[:payload] } }
-
- before do
- stub_request(:put, "#{ecf_url}#{path}").with(body:).to_return(status: 200, body: "ecf_response_body")
- stub_request(:put, "#{npq_url}#{path}").with(body:).to_return(status: 201, body: "npq_response_body")
- end
-
- include_context "makes valid requests and yields the results"
-
- context "when the payload is not specified" do
- let(:options) { {} }
- let(:body) { {} }
-
- include_context "makes valid requests and yields the results"
- end
-
- context "when the payload is a method instead of a hash" do
- let(:options) { { id: :participant_ecf_id_for_resume, payload: :put_participant_resume_payload } }
-
- before do
- create(:application, :accepted, :deferred, lead_provider:)
-
- stub_request(:put, "#{ecf_url}#{path}").to_return(status: 200, body: "ecf_response_body")
- stub_request(:put, "#{npq_url}#{path}").to_return(status: 201, body: "npq_response_body")
- end
-
- include_context "makes valid requests and yields the results"
-
- it "uses the method to get the payload" do
- instance.make_requests {}
-
- requests.each do |request|
- body = JSON.parse(request.body)
-
- expect(body).to include({
- "data" => {
- "type" => "participant-resume",
- "attributes" => a_hash_including({
- "course_identifier" => /.*/,
- }),
- },
- })
- end
- end
- end
- end
-
- context "when making a GET request" do
- let(:method) { :get }
-
- before do
- stub_request(:get, "#{ecf_url}#{path}").to_return(status: 200, body: "ecf_response_body")
- stub_request(:get, "#{npq_url}#{path}").to_return(status: 201, body: "npq_response_body")
- end
-
- include_context "makes valid requests and yields the results"
-
- context "when paginate is true" do
- let(:options) { { paginate: true } }
-
- before { stub_const("Migration::ParityCheck::Client::PAGINATION_PER_PAGE", 2) }
-
- context "when there is only one page of results" do
- before do
- stub_request(:get, "#{ecf_url}#{path}")
- .with(query: { page: { page: 1, per_page: 2 } })
- .to_return(status: 200, body: { data: [1] }.to_json)
-
- stub_request(:get, "#{npq_url}#{path}")
- .with(query: { page: { page: 1, per_page: 2 } })
- .to_return(status: 200, body: { data: [1] }.to_json)
- end
-
- it "makes a single request to each service for the first page" do
- instance.make_requests {}
-
- expect(ecf_requests.count).to eq(1)
- expect(URI.decode_uri_component(ecf_requests.first.uri.query)).to eq("page[page]=1&page[per_page]=2")
-
- expect(npq_requests.count).to eq(1)
- expect(URI.decode_uri_component(npq_requests.first.uri.query)).to eq("page[page]=1&page[per_page]=2")
- end
-
- it "yields the result of each request to the block" do
- instance.make_requests do |ecf_result, npq_result, formatted_path, page|
- expect(ecf_result[:response].code).to eq(200)
- expect(ecf_result[:response].body).to eq({ data: [1] }.to_json)
- expect(ecf_result[:response_ms]).to be >= 0
-
- expect(npq_result[:response].code).to eq(200)
- expect(npq_result[:response].body).to eq({ data: [1] }.to_json)
- expect(npq_result[:response_ms]).to be >= 0
-
- expect(formatted_path).to eq(path)
- expect(page).to eq(1)
- end
- end
- end
-
- context "when there are two pages of results and the responses from the first page match" do
- before do
- stub_request(:get, "#{ecf_url}#{path}")
- .with(query: { page: { page: 1, per_page: 2 } })
- .to_return(status: 200, body: { data: [1, 2] }.to_json)
- stub_request(:get, "#{ecf_url}#{path}")
- .with(query: { page: { page: 2, per_page: 2 } })
- .to_return(status: 200, body: { data: [3] }.to_json)
-
- stub_request(:get, "#{npq_url}#{path}")
- .with(query: { page: { page: 1, per_page: 2 } })
- .to_return(status: 200, body: { data: [1, 2] }.to_json)
- stub_request(:get, "#{npq_url}#{path}")
- .with(query: { page: { page: 2, per_page: 2 } })
- .to_return(status: 200, body: { data: [3] }.to_json)
- end
-
- it "makes a single request to each service for all pages" do
- instance.make_requests {}
-
- expect(ecf_requests.count).to eq(2)
- expect(URI.decode_uri_component(ecf_requests.first.uri.query)).to eq("page[page]=1&page[per_page]=2")
- expect(URI.decode_uri_component(ecf_requests.last.uri.query)).to eq("page[page]=2&page[per_page]=2")
-
- expect(npq_requests.count).to eq(2)
- expect(URI.decode_uri_component(npq_requests.first.uri.query)).to eq("page[page]=1&page[per_page]=2")
- expect(URI.decode_uri_component(npq_requests.last.uri.query)).to eq("page[page]=2&page[per_page]=2")
- end
-
- it "yields the result of each request to the block" do
- expected_page = 0
-
- instance.make_requests do |ecf_result, npq_result, formatted_path, page|
- expected_page += 1
- expect(page).to eq(expected_page)
- expect(formatted_path).to eq(path)
-
- case page
- when 1
- expect(ecf_result[:response].body).to eq({ data: [1, 2] }.to_json)
- expect(npq_result[:response].body).to eq({ data: [1, 2] }.to_json)
- when 2
- expect(ecf_result[:response].body).to eq({ data: [3] }.to_json)
- expect(npq_result[:response].body).to eq({ data: [3] }.to_json)
- end
- end
-
- expect(expected_page).to eq(2)
- end
- end
-
- context "when there are two pages of results and the first page responses do not match" do
- before do
- stub_request(:get, "#{ecf_url}#{path}")
- .with(query: { page: { page: 1, per_page: 2 } })
- .to_return(status: 200, body: { data: [1, 2] }.to_json)
- stub_request(:get, "#{npq_url}#{path}")
- .with(query: { page: { page: 1, per_page: 2 } })
- .to_return(status: 200, body: { data: [3] }.to_json)
- end
-
- it "stops at the first page of responses" do
- instance.make_requests {}
-
- expect(ecf_requests.count).to eq(1)
- expect(URI.decode_uri_component(ecf_requests.first.uri.query)).to eq("page[page]=1&page[per_page]=2")
-
- expect(npq_requests.count).to eq(1)
- expect(URI.decode_uri_component(npq_requests.first.uri.query)).to eq("page[page]=1&page[per_page]=2")
- end
- end
-
- context "when a page of results does not return JSON" do
- before do
- stub_request(:get, "#{ecf_url}#{path}")
- .with(query: { page: { page: 1, per_page: 2 } })
- .to_return(status: 200, body: { data: [1] }.to_json)
- stub_request(:get, "#{npq_url}#{path}")
- .with(query: { page: { page: 1, per_page: 2 } })
- .to_return(status: 200, body: "error")
- end
-
- it "treats it as if there are no more pages" do
- instance.make_requests {}
-
- expect(ecf_requests.count).to eq(1)
- expect(npq_requests.count).to eq(1)
- end
- end
- end
- end
-
- context "when using id substitution" do
- let(:options) { { id: } }
- let(:path) { "/path/:id" }
-
- before do
- stub_request(:get, "#{ecf_url}/path/#{resource.ecf_id}")
- stub_request(:get, "#{npq_url}/path/#{resource.ecf_id}")
- end
-
- context "when using an unsupported id" do
- let(:id) { "not_recognised" }
- let(:resource) { OpenStruct.new(ecf_id: "123") }
-
- it { expect { instance.make_requests {} }.to raise_error described_class::UnsupportedIdOption, "Unsupported id option: not_recognised" }
- end
-
- context "when using statement_ecf_id" do
- let(:id) { "statement_ecf_id" }
- let(:resource) { create(:statement, lead_provider:) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using application_ecf_id" do
- let(:id) { "application_ecf_id" }
- let(:resource) { create(:application, lead_provider:) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using declaration_ecf_id" do
- let(:id) { "declaration_ecf_id" }
- let(:resource) { create(:declaration, lead_provider:) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using participant_outcome_ecf_id" do
- let(:id) { "participant_outcome_ecf_id" }
- let(:declaration) { create(:declaration, lead_provider:) }
- let(:resource) { create(:participant_outcome, declaration:).declaration.application.user }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using participant_ecf_id" do
- let(:id) { "participant_ecf_id" }
- let(:resource) { create(:application, :accepted, lead_provider:).user }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using application_ecf_id_for_accept_with_funded_place" do
- let(:id) { "application_ecf_id_for_accept_with_funded_place" }
- let(:resource) { create(:application, lead_provider:, eligible_for_funding: true) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using application_ecf_id_for_accept_without_funded_place" do
- let(:id) { "application_ecf_id_for_accept_without_funded_place" }
- let(:resource) { create(:application, lead_provider:, eligible_for_funding: false) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using application_ecf_id_for_reject" do
- let(:id) { "application_ecf_id_for_reject" }
- let(:resource) { create(:application, lead_provider:) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using participant_ecf_id_for_create_outcome" do
- let(:id) { "participant_ecf_id_for_create_outcome" }
- let(:application) { create(:application, :accepted, lead_provider:) }
- let(:declaration) { create(:declaration, :completed, application:) }
- let(:resource) { declaration.application.user }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using application_ecf_id_for_change_from_funded_place" do
- let(:id) { "application_ecf_id_for_change_from_funded_place" }
- let(:resource) { create(:application, :accepted, lead_provider:, funded_place: true) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using declaration_ecf_id_for_void" do
- let(:id) { "declaration_ecf_id_for_void" }
- let(:resource) { create(:declaration, :submitted, lead_provider:) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using declaration_ecf_id_for_clawback" do
- let(:id) { "declaration_ecf_id_for_clawback" }
- let(:resource) { create(:declaration, :paid, lead_provider:) }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using participant_ecf_id_for_resume" do
- let(:id) { "participant_ecf_id_for_resume" }
- let(:resource) { create(:application, :accepted, :deferred, lead_provider:).user }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using participant_ecf_id_for_defer" do
- let(:id) { "participant_ecf_id_for_defer" }
- let(:resource) { create(:application, :with_declaration, :accepted, :active, lead_provider:).user }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
-
- context "when using participant_ecf_id_for_withdraw" do
- let(:id) { "participant_ecf_id_for_withdraw" }
- let(:resource) { create(:application, :with_declaration, :accepted, :deferred, lead_provider:).user }
-
- it "evaluates the id option and substitutes it into the path" do
- instance.make_requests do |_, _, formatted_path|
- expect(formatted_path).to eq("/path/#{resource.ecf_id}")
- end
-
- expect(requests.count).to eq(2)
- end
- end
- end
-
- context "when using dynamic payloads substitution" do
- describe "#post_declaration_payload" do
- it "returns a valid payload for the lead provider" do
- freeze_time do
- application = create(:application, :accepted, lead_provider:)
- payload = instance.post_declaration_payload
-
- expect(payload).to include({
- type: "participant-declaration",
- attributes: {
- declaration_type: :started,
- participant_id: application.user.ecf_id,
- course_identifier: application.course.identifier,
- declaration_date: 1.day.ago.rfc3339,
- },
- })
- end
- end
- end
-
- describe "#post_participant_outcome_payload" do
- let(:application) { create(:application, :accepted, lead_provider:) }
- let!(:declaration) { create(:declaration, :completed, application:) }
- let(:path) { "/api/v1/participants/npq/:id/outcomes" }
- let(:options) { { id: :participant_ecf_id_for_create_outcome } }
-
- it "returns a valid payload for the lead provider" do
- freeze_time do
- payload = instance.post_participant_outcome_payload
-
- expect(payload).to include({
- type: "npq-outcome-confirmation",
- attributes: {
- state: :passed,
- course_identifier: declaration.application.course.identifier,
- completion_date: 1.day.ago.rfc3339,
- },
- })
- end
- end
- end
-
- describe "#put_participant_resume_payload" do
- let!(:application) { create(:application, :accepted, :deferred, lead_provider:) }
- let(:path) { "/api/v1/participants/npq/:id/resume" }
- let(:options) { { id: :participant_ecf_id_for_resume } }
-
- it "returns a valid payload for the lead provider" do
- freeze_time do
- payload = instance.put_participant_resume_payload
-
- expect(payload).to include({
- type: "participant-resume",
- attributes: {
- course_identifier: application.course.identifier,
- },
- })
- end
- end
- end
-
- describe "#put_participant_defer_payload" do
- let!(:application) { create(:application, :with_declaration, :accepted, lead_provider:) }
- let(:path) { "/api/v1/participants/npq/:id/defer" }
- let(:options) { { id: :participant_ecf_id_for_defer } }
-
- it "returns a valid payload for the lead provider" do
- freeze_time do
- payload = instance.put_participant_defer_payload
-
- expect(payload).to include({
- type: "participant-defer",
- attributes: {
- course_identifier: application.course.identifier,
- reason: satisfy { |value| value.in?(Participants::Defer::DEFERRAL_REASONS) },
- },
- })
- end
- end
- end
-
- describe "#participant_ecf_id_for_withdraw" do
- let!(:application) { create(:application, :with_declaration, :accepted, lead_provider:) }
- let(:path) { "/api/v1/participants/npq/:id/withdraw" }
- let(:options) { { id: :participant_ecf_id_for_withdraw } }
-
- it "returns a valid payload for the lead provider" do
- freeze_time do
- payload = instance.put_participant_withdraw_payload
-
- expect(payload).to include({
- type: "participant-withdraw",
- attributes: {
- course_identifier: application.course.identifier,
- reason: satisfy { |value| value.in?(Participants::Withdraw::WITHDRAWAL_REASONS) },
- },
- })
- end
- end
- end
- end
- end
-end
diff --git a/spec/services/migration/parity_check/token_provider_spec.rb b/spec/services/migration/parity_check/token_provider_spec.rb
deleted file mode 100644
index 9a4398fc66..0000000000
--- a/spec/services/migration/parity_check/token_provider_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require "rails_helper"
-
-RSpec.describe Migration::ParityCheck::TokenProvider do
- before do
- create_list(:lead_provider, 3)
-
- allow(Rails.application.config).to receive(:npq_separation).and_return({
- parity_check: {
- enabled:,
- },
- })
-
- allow(ENV).to receive(:[]).and_call_original
- allow(ENV).to receive(:[]).with("PARITY_CHECK_KEYS").and_return(keys.to_json) if keys
- end
-
- let(:instance) { described_class.new }
-
- describe "#generate!" do
- subject(:generate) { instance.generate! }
-
- context "when the parity check is enabled" do
- let(:enabled) { true }
-
- context "when the keys are not present" do
- let(:keys) { nil }
-
- it { expect { generate }.not_to change(APIToken, :count) }
- end
-
- context "when the keys are present" do
- let(:keys) do
- LeadProvider.all.each_with_object({}) do |lead_provider, hash|
- hash[lead_provider.ecf_id] = SecureRandom.uuid
- end
- end
-
- it { expect { generate }.to change(APIToken, :count).by(LeadProvider.count) }
-
- it "generates valid tokens for each lead provider" do
- generate
-
- LeadProvider.find_each do |lead_provider|
- token = instance.token(lead_provider:)
- expect(APIToken.find_by_unhashed_token(token).lead_provider).to eq(lead_provider)
- end
- end
- end
- end
-
- context "when the parity check is disabled" do
- let(:enabled) { false }
- let(:keys) { {} }
-
- it { expect { generate }.to raise_error(described_class::UnsupportedEnvironmentError, "The parity check functionality is disabled for this environment") }
- end
- end
-
- describe "#token" do
- let(:lead_provider) { create(:lead_provider) }
-
- subject(:token) { instance.token(lead_provider:) }
-
- context "when the parity check is enabled" do
- let(:enabled) { true }
-
- context "when the keys are not present" do
- let(:keys) { nil }
-
- it { is_expected.to be_nil }
- end
-
- context "when the keys are present" do
- let(:keys) do
- { lead_provider.ecf_id => "token" }
- end
-
- it { is_expected.to eq("token") }
- end
- end
-
- context "when the parity check is disabled" do
- let(:enabled) { false }
- let(:keys) { {} }
-
- it { expect { token }.to raise_error(described_class::UnsupportedEnvironmentError, "The parity check functionality is disabled for this environment") }
- end
- end
-end
diff --git a/spec/services/migration/parity_check_spec.rb b/spec/services/migration/parity_check_spec.rb
deleted file mode 100644
index 27ad57415a..0000000000
--- a/spec/services/migration/parity_check_spec.rb
+++ /dev/null
@@ -1,254 +0,0 @@
-require "rails_helper"
-
-RSpec.describe Migration::ParityCheck, :in_memory_rails_cache do
- let(:endpoints_file_path) { "spec/fixtures/files/parity_check/no_endpoints.yml" }
- let(:instance) { described_class.new(endpoints_file_path:) }
- let(:enabled) { true }
-
- before do
- create_matching_ecf_lead_providers
-
- allow(Rails.application.config).to receive(:npq_separation) do
- {
- parity_check: {
- enabled:,
- },
- }
- end
- end
-
- describe ".prepare!" do
- subject(:prepare) { described_class.prepare! }
-
- it "destroys existing response comparisons from previous runs" do
- existing_response_comparison = create(:response_comparison)
-
- prepare
-
- expect { existing_response_comparison.reload }.to raise_error(ActiveRecord::RecordNotFound)
- end
-
- it "resets the started/completed timestamps" do
- travel_to(5.days.ago) do
- described_class.prepare!
- instance.run!
- end
-
- prepare
-
- expect(described_class.started_at).to be_within(5.seconds).of(Time.zone.now)
- expect(described_class.completed_at).to be_nil
- end
- end
-
- describe ".running?" do
- it "returns false when the parity check has not been started" do
- expect(described_class).not_to be_running
- end
-
- it "returns true when the parity check has been started" do
- described_class.prepare!
- expect(described_class).to be_running
- end
-
- it "returns false when a partiy check has completed" do
- described_class.prepare!
- instance.run!
- expect(described_class).not_to be_running
- end
- end
-
- describe ".completed?" do
- it "returns false when the parity check has not been started" do
- expect(described_class).not_to be_completed
- end
-
- it "returns false when the parity check is running" do
- described_class.prepare!
- expect(described_class).not_to be_completed
- end
-
- it "returns true when a partiy check has completed" do
- described_class.prepare!
- instance.run!
- expect(described_class).to be_completed
- end
- end
-
- describe ".started_at" do
- it "returns the started timestamp" do
- described_class.prepare!
- expect(described_class.started_at).to be_within(5.seconds).of(Time.zone.now)
- end
- end
-
- describe ".completed_at" do
- it "returns the completed timestamp" do
- described_class.prepare!
- travel_to(1.day.from_now) do
- instance.run!
- expect(described_class.completed_at).to be_within(5.seconds).of(Time.zone.now)
- end
- end
- end
-
- describe "#run!" do
- subject(:run) { instance.run! }
-
- it { expect { run }.to raise_error(described_class::NotPreparedError, "You must call prepare! before running the parity check") }
-
- context "when prepared" do
- before { described_class.prepare! }
-
- it "sets the completed timestamp" do
- run
-
- expect(described_class.completed_at).to be_within(5.seconds).of(Time.zone.now)
- end
-
- context "when the endpoints_file_path is not found" do
- let(:endpoints_file_path) { "missing.yml" }
-
- it { expect { run }.to raise_error(described_class::EndpointsFileNotFoundError, "Endpoints file not found: #{endpoints_file_path}") }
- end
-
- context "when the parity check is disabled" do
- let(:enabled) { false }
-
- it { expect { run }.to raise_error(described_class::UnsupportedEnvironmentError, "The parity check functionality is disabled for this environment") }
- end
-
- context "when there are GET endpoints" do
- let(:endpoints_file_path) { "spec/fixtures/files/parity_check/get_endpoints.yml" }
-
- it "calls the client for each lead provider with the correct options" do
- client_double = instance_double(Migration::ParityCheck::Client, make_requests: nil)
- allow(Migration::ParityCheck::Client).to receive(:new) { client_double }
-
- run
-
- LeadProvider.find_each do |lead_provider|
- expect(Migration::ParityCheck::Client).to have_received(:new).with(
- lead_provider:,
- method: "get",
- path: "/api/v3/statements",
- options: { paginate: true, exclude: %w[attribute] },
- )
- end
- expect(client_double).to have_received(:make_requests).exactly(LeadProvider.count).times
- end
-
- it "saves response comparisons for each endpoint and lead provider" do
- client_double = instance_double(Migration::ParityCheck::Client)
- ecf_result_dpuble = { response: instance_double(HTTParty::Response, body: %({ "foo": "bar", "attribute": "excluded" }), code: 200), response_ms: 100 }
- npq_result_double = { response: instance_double(HTTParty::Response, body: "npq_response_body", code: 201), response_ms: 150 }
- allow(client_double).to receive(:make_requests).and_yield(ecf_result_dpuble, npq_result_double, "/formatted/path", 1)
- allow(Migration::ParityCheck::Client).to receive(:new) { client_double }
-
- expect { run }.to change(Migration::ParityCheck::ResponseComparison, :count).by(LeadProvider.count)
-
- expect(Migration::ParityCheck::ResponseComparison.all).to all(have_attributes({
- lead_provider: an_instance_of(LeadProvider),
- request_path: "/formatted/path",
- request_method: "get",
- ecf_response_status_code: 200,
- npq_response_status_code: 201,
- ecf_response_body: %({\n \"foo\": \"bar\"\n}),
- npq_response_body: "npq_response_body",
- ecf_response_time_ms: 100,
- npq_response_time_ms: 150,
- page: 1,
- }))
- end
- end
-
- context "when there are POST endpoints" do
- let(:endpoints_file_path) { "spec/fixtures/files/parity_check/post_endpoints.yml" }
-
- it "calls the client for each lead provider with the correct options" do
- client_double = instance_double(Migration::ParityCheck::Client, make_requests: nil)
- allow(Migration::ParityCheck::Client).to receive(:new) { client_double }
-
- run
-
- LeadProvider.find_each do |lead_provider|
- expect(Migration::ParityCheck::Client).to have_received(:new).with(
- lead_provider:,
- method: "post",
- path: "/api/v1/npq-applications/:id/accept",
- options: { id: "application_ecf_id_for_accept_with_funded_place", payload: { type: "npq-application-accept", attributes: { funded_place: true } } },
- )
- end
- expect(client_double).to have_received(:make_requests).exactly(LeadProvider.count).times
- end
-
- it "saves response comparisons for each endpoint and lead provider" do
- client_double = instance_double(Migration::ParityCheck::Client)
- ecf_result_dpuble = { response: instance_double(HTTParty::Response, body: "ecf_response_body", code: 200), response_ms: 100 }
- npq_result_double = { response: instance_double(HTTParty::Response, body: "npq_response_body", code: 201), response_ms: 150 }
- allow(client_double).to receive(:make_requests).and_yield(ecf_result_dpuble, npq_result_double, "/formatted/path", nil)
- allow(Migration::ParityCheck::Client).to receive(:new) { client_double }
-
- expect { run }.to change(Migration::ParityCheck::ResponseComparison, :count).by(LeadProvider.count)
-
- expect(Migration::ParityCheck::ResponseComparison.all).to all(have_attributes({
- lead_provider: an_instance_of(LeadProvider),
- request_path: "/formatted/path",
- request_method: "post",
- ecf_response_status_code: 200,
- npq_response_status_code: 201,
- ecf_response_body: "ecf_response_body",
- npq_response_body: "npq_response_body",
- ecf_response_time_ms: 100,
- npq_response_time_ms: 150,
- page: nil,
- }))
- end
- end
-
- context "when there are PUT endpoints" do
- let(:endpoints_file_path) { "spec/fixtures/files/parity_check/put_endpoints.yml" }
-
- it "calls the client for each lead provider with the correct options" do
- client_double = instance_double(Migration::ParityCheck::Client, make_requests: nil)
- allow(Migration::ParityCheck::Client).to receive(:new) { client_double }
-
- run
-
- LeadProvider.find_each do |lead_provider|
- expect(Migration::ParityCheck::Client).to have_received(:new).with(
- lead_provider:,
- method: "put",
- path: "/api/v1/npq-applications/:id/change-funded-place",
- options: { id: "application_ecf_id_for_change_to_funded_place", payload: { type: "npq-application-change-funded-place", attributes: { funded_place: true } } },
- )
- end
- expect(client_double).to have_received(:make_requests).exactly(LeadProvider.count).times
- end
-
- it "saves response comparisons for each endpoint and lead provider" do
- client_double = instance_double(Migration::ParityCheck::Client)
- ecf_result_dpuble = { response: instance_double(HTTParty::Response, body: "ecf_response_body", code: 200), response_ms: 100 }
- npq_result_double = { response: instance_double(HTTParty::Response, body: "npq_response_body", code: 201), response_ms: 150 }
- allow(client_double).to receive(:make_requests).and_yield(ecf_result_dpuble, npq_result_double, "/formatted/path", nil)
- allow(Migration::ParityCheck::Client).to receive(:new) { client_double }
-
- expect { run }.to change(Migration::ParityCheck::ResponseComparison, :count).by(LeadProvider.count)
-
- expect(Migration::ParityCheck::ResponseComparison.all).to all(have_attributes({
- lead_provider: an_instance_of(LeadProvider),
- request_path: "/formatted/path",
- request_method: "put",
- ecf_response_status_code: 200,
- npq_response_status_code: 201,
- ecf_response_body: "ecf_response_body",
- npq_response_body: "npq_response_body",
- ecf_response_time_ms: 100,
- npq_response_time_ms: 150,
- page: nil,
- }))
- end
- end
- end
- end
-end
diff --git a/spec/support/helpers/migration_helper.rb b/spec/support/helpers/migration_helper.rb
deleted file mode 100644
index 54d6eb21e4..0000000000
--- a/spec/support/helpers/migration_helper.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-def create_matching_ecf_lead_providers
- LeadProvider.all.find_each do |lead_provider|
- create(:ecf_migration_npq_lead_provider, id: lead_provider.ecf_id)
- end
-end