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 @@ - - 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