diff --git a/.rubocop.yml b/.rubocop.yml index 9e23adcb..02fd138c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -18,6 +18,7 @@ AllCops: - 'bin/*' - 'db/schema.rb' - 'node_modules/**/*' + - 'spec/models/reports/applications_spec.rb' Style/MethodCallWithArgsParentheses: AllowParenthesesInMultilineCall: true @@ -75,6 +76,7 @@ RSpec/ExampleLength: Max: 10 Exclude: - 'spec/features/*' + - 'spec/models/reports/home_office_spec.rb' RSpec/NoExpectationExample: Exclude: diff --git a/Gemfile b/Gemfile index 2b53e0ea..5c4afb4f 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ gem "foreman" gem "jbuilder" gem "okcomputer" gem "pg", "~> 1.5" -gem "puma", "~> 6.3" +gem "puma", "~> 6.4" gem "rails", "~> 7.0.8" gem "sprockets-rails" gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby] @@ -36,6 +36,9 @@ gem "audited", "~> 5.3", ">= 5.3.3" gem "config", "~> 4.2" gem "devise", "~> 4.9" gem "dfe-analytics", github: "DFE-Digital/dfe-analytics", tag: "v1.10.1" +gem "flipper" +gem "flipper-active_record" +gem "flipper-ui" gem "httparty", "~> 0.21" gem "invisible_captcha" gem "omniauth-azure-activedirectory-v2" diff --git a/Gemfile.lock b/Gemfile.lock index e4ad37de..3e6a1beb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -92,6 +92,7 @@ GEM bootsnap (1.16.0) msgpack (~> 1.2) brakeman (6.0.1) + brow (0.4.1) builder (3.2.4) bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) @@ -179,6 +180,18 @@ GEM faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) + flipper (1.0.0) + brow (~> 0.4.1) + concurrent-ruby (< 2) + flipper-active_record (1.0.0) + activerecord (>= 4.2, < 8) + flipper (~> 1.0.0) + flipper-ui (1.0.0) + erubi (>= 1.0.0, < 2.0.0) + flipper (~> 1.0.0) + rack (>= 1.4, < 4) + rack-protection (>= 1.5.3, <= 4.0.0) + sanitize (< 7) foreman (0.87.2) fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) @@ -317,7 +330,7 @@ GEM byebug (~> 11.0) pry (>= 0.13, < 0.15) public_suffix (5.0.1) - puma (6.3.1) + puma (6.4.0) nio4r (~> 2.0) raabro (1.4.0) racc (1.7.1) @@ -430,6 +443,9 @@ GEM rubocop-factory_bot (~> 2.22) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) + sanitize (6.1.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) scenic (1.7.0) activerecord (>= 4.0.0) railties (>= 4.0.0) @@ -521,6 +537,9 @@ DEPENDENCIES dotenv-rails factory_bot_rails faker + flipper + flipper-active_record + flipper-ui foreman govuk-components (= 4.1.1) govuk_design_system_formbuilder @@ -536,7 +555,7 @@ DEPENDENCIES pg (~> 1.5) phonelib pry-byebug - puma (~> 6.3) + puma (~> 6.4) rails (~> 7.0.8) rspec-rails rubocop-govuk diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9e1d6897..7a7f7eef 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,37 +7,6 @@ class ApplicationController < ActionController::Base before_action :check_service_open! - delegate :application_route, to: :current_application - helper_method :application_route - - def check_application! - return if session["application_id"] - - redirect_to(root_path) - end - - def current_application - Application.in_progress.find(session["application_id"]) - end - helper_method :current_application - - def current_applicant - current_application.applicant - end - helper_method :current_applicant - - def check_teacher! - return unless application_route != "teacher" - - redirect_to(new_applicants_application_route_path) - end - - def check_trainee! - return unless application_route != "salaried_trainee" - - redirect_to(new_applicants_application_route_path) - end - def check_service_open! return if request.path == destroy_user_session_path # skip this for log out page return if Gatekeeper.application_open? diff --git a/app/controllers/system_admin/applicants_controller.rb b/app/controllers/system_admin/applicants_controller.rb index 9a868b41..06bbca36 100644 --- a/app/controllers/system_admin/applicants_controller.rb +++ b/app/controllers/system_admin/applicants_controller.rb @@ -9,7 +9,7 @@ class ApplicantsController < AdminController include Pagy::Backend def index - results = Application.submitted + results = Application.all .search(params[:search]) .filter_by_status(params[:status]) .order(created_at: :desc) @@ -18,20 +18,7 @@ def index @pagy, @applications = pagy_array(results) session[:filter_status] = params[:status] - session[:application_ids] = @applications.map(&:id) - end - - def download_qa_csv - status = session[:filter_status] - application_ids = session[:application_ids] - - applications = Application.where(id: application_ids).reject(&:qa?) - - applications.each(&:mark_as_qa!) - - report = Reports::QaReport.new(applications, status) - create_audit(action: "Downloaded QA CSV report (#{status.humanize})") - send_data(report.csv, filename: report.name) + session[:application_ids] = results.map(&:id) end def duplicates diff --git a/app/controllers/system_admin/reports_controller.rb b/app/controllers/system_admin/reports_controller.rb index e4187e58..b09876d9 100644 --- a/app/controllers/system_admin/reports_controller.rb +++ b/app/controllers/system_admin/reports_controller.rb @@ -3,20 +3,16 @@ class ReportsController < AdminController def index; end def show - report = find_report(params[:id]) - create_audit(action: "Downloaded #{report.class.to_s.capitalize} report") + service = Report.call(params[:id], **report_params) + create_audit(action: "Downloaded #{service.report_name} report") - send_data(report.csv, filename: report.name) + send_data(service.data, filename: service.filename) end private - def find_report(report_id) - { - home_office: Reports::HomeOffice.new, - standing_data: Reports::StandingData.new, - payroll: Reports::Payroll.new, - }.with_indifferent_access.fetch(report_id) + def report_params + params.permit(:id, :status) end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index dd7e9147..e5e2f9cd 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -20,4 +20,18 @@ def mailto_irp_express_interest def banner_feedback_form govuk_link_to("feedback", "https://forms.office.com/e/p45Wm1Vmxg", target: "_blank") end + + def application_statuses + ApplicationProgress + .statuses + .keys + .map { |status| [status.humanize, status] } + end + + def application_statuses_options(selected: nil, all_statuses: false) + statuses = application_statuses + statuses = application_statuses.unshift(["All statuses", ""]) if all_statuses + + options_for_select(statuses, selected:) + end end diff --git a/app/javascript/packs/stylesheets/custom.scss b/app/javascript/packs/stylesheets/custom.scss index 5d0ffa4d..46279d85 100644 --- a/app/javascript/packs/stylesheets/custom.scss +++ b/app/javascript/packs/stylesheets/custom.scss @@ -68,6 +68,11 @@ padding: 5px; font-size: 20px; } + + a { + color: #fff; + text-decoration: none; + } } } diff --git a/app/models/application.rb b/app/models/application.rb index 358f13ab..79e54f6e 100644 --- a/app/models/application.rb +++ b/app/models/application.rb @@ -30,10 +30,6 @@ class Application < ApplicationRecord delegate :sla_breached?, to: :application_progress delegate :status, to: :application_progress, allow_nil: false - default_scope { submitted } - scope :submitted, -> { where.not(urn: nil) } - scope :in_progress, -> { unscoped.where(urn: nil) } - scope :search, lambda { |term| return if term.blank? @@ -64,22 +60,11 @@ def mark_as_qa! unique_by: %i[application_id status]) end - with_options if: :submitted? do - validates(:application_date, presence: true) - validates(:application_route, presence: true) - validates(:date_of_entry, presence: true) - validates(:start_date, presence: true) - validates(:subject, presence: true) - validates(:visa_type, presence: true) - validates(:urn, presence: true) - validates(:applicant, presence: true) - end - - def assign_urn! - update!(urn: Urn.generate(application_route)) - end - - def submitted? - urn.present? - end + validates(:application_date, presence: true) + validates(:application_route, presence: true) + validates(:date_of_entry, presence: true) + validates(:start_date, presence: true) + validates(:subject, presence: true) + validates(:visa_type, presence: true) + validates(:applicant, presence: true) end diff --git a/app/models/kpis.rb b/app/models/kpis.rb index 10ac1596..227a536b 100644 --- a/app/models/kpis.rb +++ b/app/models/kpis.rb @@ -4,7 +4,7 @@ def initialize end def total_applications - Application.submitted.count + Application.count end def total_rejections @@ -62,4 +62,8 @@ def time_to_banking_approval def time_to_payment_confirmation TimeToPaymentConfirmationQuery.new.call end + + def status_breakdown + StatusBreakdownQuery.call + end end diff --git a/app/models/policies/contract_start_date.rb b/app/models/policies/contract_start_date.rb deleted file mode 100644 index 925282eb..00000000 --- a/app/models/policies/contract_start_date.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -# The `ContractStartDate` class defines the policy for contract start dates. -# -# This policy determines whether a given start date is eligible based on the -# rule that employment start dates from the first Monday in July are considered -# eligible. -# -# It calculates the first Monday in July of the current year and compares -# it with the given start date to determine eligibility. -# -# @example -# Policies::ContractStartDate.eligible?(Date.new(2023, 7, 3)) #=> true -# -# @example -# Policies::ContractStartDate.eligible?(Date.new(2023, 6, 30)) #=> false -# -module Policies - class ContractStartDate - def self.eligible?(start_date) - current_year = Date.current.year - first_monday_in_july = Date.new(current_year, 7, 1) - .beginning_of_month - .next_occurring(:monday) - - start_date >= first_monday_in_july - end - end -end diff --git a/app/models/policies/entry_date.rb b/app/models/policies/entry_date.rb deleted file mode 100644 index 6837f009..00000000 --- a/app/models/policies/entry_date.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -# The `EntryDate` class defines the policy for contract start dates. -# -# This Policy determines whether a given entry date is eligible based on the -# start date of the contract. If the applicant entered the country 3 months -# or less before the start of the contract, then they are eligible. -# -# @example -# Policies::ContractStartDate.eligible?( -# Date.new(2023, 1, 2), -# Date.new(2023, 4, 2) -# ) #=> true -# -module Policies - class EntryDate - def self.eligible?(entry_date, start_date) - entry_date >= start_date - 3.months - end - end -end diff --git a/app/models/reports/applications.rb b/app/models/reports/applications.rb new file mode 100644 index 00000000..870ce2eb --- /dev/null +++ b/app/models/reports/applications.rb @@ -0,0 +1,79 @@ +module Reports + class Applications + def name + current_time = Time.zone.now.strftime("%Y%m%d-%H%M%S") + + "Applications-Report-#{current_time}.csv" + end + + def csv + CSV.generate do |csv| + csv << header + rows.find_each(batch_size: 50) { |row| csv << columns(row) } + end + end + + private + + def header + %i[ + ip_address + given_name + middle_name + family_name + email_address + phone_number + date_of_birth + sex + passport_number + nationality + student_loan + address_line_1 + address_line_2 + city + postcode + application_date + application_route + status + date_of_entry + start_date + subject + urn + visa_type + ].map { _1.to_s.titleize } + end + + def columns(application) + applicant = application.applicant + [ + applicant.ip_address, + applicant.given_name, + applicant.middle_name, + applicant.family_name, + applicant.email_address, + applicant.phone_number, + applicant.date_of_birth, + applicant.sex, + applicant.passport_number, + applicant.nationality, + applicant.student_loan, + applicant.address.address_line_1, + applicant.address.address_line_2, + applicant.address.city, + applicant.address.postcode, + application.application_date, + application.application_route, + application.status, + application.date_of_entry, + application.start_date, + application.subject, + application.urn, + application.visa_type, + ] + end + + def rows + Application.includes(:applicant, :application_progress, applicant: :address) + end + end +end diff --git a/app/models/reports/home_office.rb b/app/models/reports/home_office.rb index e54f2db8..b8aad595 100644 --- a/app/models/reports/home_office.rb +++ b/app/models/reports/home_office.rb @@ -25,10 +25,16 @@ def rows application.urn, application.applicant.full_name, application.applicant.date_of_birth, + nil, application.applicant.nationality, + nil, application.applicant.passport_number, nil, nil, + nil, + nil, + nil, + nil, ] end end @@ -49,13 +55,19 @@ def applications def header [ - "URN", + "ID", "Full Name", "DOB", + "Gender", "Nationality", + "Place of Birth", "Passport Number", - "Visa Type", - "Date of UK entry", + "National Insurance Number", + "Address", + "Postcode", + "Email", + "Telephone", + "Reference", ] end end diff --git a/app/queries/average_age_query.rb b/app/queries/average_age_query.rb index 08240ad1..383d827d 100644 --- a/app/queries/average_age_query.rb +++ b/app/queries/average_age_query.rb @@ -1,6 +1,6 @@ class AverageAgeQuery def initialize(relation = Applicant.all) - @relation = relation.joins(:application).merge(Application.submitted) + @relation = relation.joins(:application).merge(Application.all) end def call diff --git a/app/queries/gender_breakdown_query.rb b/app/queries/gender_breakdown_query.rb index 73b354bf..98340ba9 100644 --- a/app/queries/gender_breakdown_query.rb +++ b/app/queries/gender_breakdown_query.rb @@ -1,6 +1,6 @@ class GenderBreakdownQuery def initialize(relation = Applicant.all) - @relation = relation.joins(:application).merge(Application.submitted) + @relation = relation.joins(:application).merge(Application.all) end def call diff --git a/app/queries/nationality_breakdown_query.rb b/app/queries/nationality_breakdown_query.rb index b22f006d..6b075920 100644 --- a/app/queries/nationality_breakdown_query.rb +++ b/app/queries/nationality_breakdown_query.rb @@ -1,6 +1,6 @@ class NationalityBreakdownQuery def initialize(relation = Applicant.all) - @relation = relation.joins(:application).merge(Application.submitted) + @relation = relation.joins(:application).merge(Application.all) end def call diff --git a/app/queries/rejection_reason_breakdown_query.rb b/app/queries/rejection_reason_breakdown_query.rb index a9d5aea9..ee8a6a07 100644 --- a/app/queries/rejection_reason_breakdown_query.rb +++ b/app/queries/rejection_reason_breakdown_query.rb @@ -1,6 +1,6 @@ class RejectionReasonBreakdownQuery def initialize(relation = ApplicationProgress.all) - @relation = relation.joins(:application).merge(Application.submitted).where.not(rejection_reason: nil) + @relation = relation.joins(:application).merge(Application.all).where.not(rejection_reason: nil) end def call diff --git a/app/queries/route_breakdown_query.rb b/app/queries/route_breakdown_query.rb index 2b865dc7..2df254ba 100644 --- a/app/queries/route_breakdown_query.rb +++ b/app/queries/route_breakdown_query.rb @@ -1,6 +1,6 @@ class RouteBreakdownQuery def initialize(relation = Application.all) - @relation = relation.submitted + @relation = relation end def call diff --git a/app/queries/status_breakdown_query.rb b/app/queries/status_breakdown_query.rb new file mode 100644 index 00000000..2b08f888 --- /dev/null +++ b/app/queries/status_breakdown_query.rb @@ -0,0 +1,22 @@ +class StatusBreakdownQuery + def self.call + new.execute + end + + def execute + ordered_with_defaults(status_breakdown) + end + +private + + def status_breakdown + ApplicationProgress.group(:status).count + end + + def ordered_with_defaults(breakdown) + ApplicationProgress.statuses.each_with_object({}) do |(key, _), hsh| + hsh[key] = breakdown.fetch(key, 0) + hsh + end + end +end diff --git a/app/queries/subject_breakdown_query.rb b/app/queries/subject_breakdown_query.rb index ea0aa1db..19559899 100644 --- a/app/queries/subject_breakdown_query.rb +++ b/app/queries/subject_breakdown_query.rb @@ -1,6 +1,6 @@ class SubjectBreakdownQuery def initialize(relation = Application.all) - @relation = relation.submitted + @relation = relation end def call diff --git a/app/queries/visa_breakdown_query.rb b/app/queries/visa_breakdown_query.rb index 80d815b7..a55afdf1 100644 --- a/app/queries/visa_breakdown_query.rb +++ b/app/queries/visa_breakdown_query.rb @@ -1,6 +1,6 @@ class VisaBreakdownQuery def initialize(relation = Application.all) - @relation = relation.submitted + @relation = relation end def call diff --git a/app/services/report.rb b/app/services/report.rb new file mode 100644 index 00000000..dbfb50b4 --- /dev/null +++ b/app/services/report.rb @@ -0,0 +1,61 @@ +class Report + REGISTERED_REPORTS = { + home_office: Reports::HomeOffice, + standing_data: Reports::StandingData, + payroll: Reports::Payroll, + applications: Reports::Applications, + qa: Reports::QaReport, + }.freeze + + def self.call(...) + service = new(...) + service.data + service + end + + def initialize(report_id, **kwargs) + @kwargs = kwargs&.symbolize_keys || {} + @report_class = REGISTERED_REPORTS.with_indifferent_access.fetch(report_id) + rescue KeyError + raise(ArgumentError, "Invalid report id #{report_id}") + end + + def report_name + report_class.to_s.capitalize + end + + def filename + report.name + end + + def data + report.csv + end + +private + + attr_reader :report_class, :kwargs + + def report + return @report if @report + return @report = report_class.new(*report_args) if report_args + + @report = report_class.new + end + + def report_args + return qa_report_args if report_class == Reports::QaReport + + nil + end + + def qa_report_args + return @qa_report_args if @qa_report_args + + status = kwargs.fetch(:status) + applications = Application.filter_by_status(status).reject(&:qa?) + applications.each(&:mark_as_qa!) + + @qa_report_args = [applications, status] + end +end diff --git a/app/services/submit_form.rb b/app/services/submit_form.rb index 84f9a20e..939f2756 100644 --- a/app/services/submit_form.rb +++ b/app/services/submit_form.rb @@ -36,15 +36,15 @@ def submit_form! def create_application_records ActiveRecord::Base.transaction do - school = create_school - applicant = create_applicant(school) - @application = create_application(applicant) + create_school + create_applicant + create_application delete_form end end def create_school - School.create!( + @school = School.create!( name: form.school_name, headteacher_name: form.school_headteacher_name, address_attributes: { @@ -56,8 +56,8 @@ def create_school ) end - def create_applicant(school) - Applicant.create!( + def create_applicant + @applicant = Applicant.create!( ip_address: ip_address, given_name: form.given_name, middle_name: form.middle_name, @@ -75,13 +75,13 @@ def create_applicant(school) city: form.city, postcode: form.postcode, }, - school: school, + school: @school, ) end - def create_application(applicant) - Application.create!( - applicant: applicant, + def create_application + @application = Application.create!( + applicant: @applicant, application_date: Date.current.to_s, application_route: form.application_route, application_progress: ApplicationProgress.new, @@ -100,7 +100,7 @@ def delete_form def send_applicant_email GovukNotifyMailer .with( - email: application.applicant.email_address, + email: @applicant.email_address, urn: application.urn, ) .application_submission diff --git a/app/steps/personal_details_step.rb b/app/steps/personal_details_step.rb index e792cd85..524e9a49 100644 --- a/app/steps/personal_details_step.rb +++ b/app/steps/personal_details_step.rb @@ -80,10 +80,10 @@ def minimum_age return unless date_of_birth.present? # rubocop:enable Rails/Blank - errors.add(:date_of_birth, :below_min_age) if date_of_birth > MIN_AGE.years.ago.to_date + errors.add(:date_of_birth, :below_min_age) unless date_of_birth <= MIN_AGE.years.ago.to_date end MAX_AGE = 80 - MIN_AGE = 22 + MIN_AGE = 21 private_constant :MAX_AGE, :MIN_AGE end diff --git a/app/views/pages/closed.html.erb b/app/views/pages/closed.html.erb index b8680206..50934ec0 100644 --- a/app/views/pages/closed.html.erb +++ b/app/views/pages/closed.html.erb @@ -39,11 +39,11 @@

Check your eligibility

- <%= govuk_link_to("Check full eligibility details for teachers.", "https://getintoteaching.education.gov.uk/non-uk-teachers/get-an-international-relocation-payment", { target: "_blank" })%> + <%= govuk_link_to("Get an international relocation payment: criteria for teachers", "https://getintoteaching.education.gov.uk/non-uk-teachers/get-an-international-relocation-payment/#criteria-for-teachers", { target: "_blank" })%>

- <%= govuk_link_to("Check full eligibility details for trainees.", "https://getintoteaching.education.gov.uk/non-uk-teachers/get-an-international-relocation-payment", { target: "_blank" })%> + <%= govuk_link_to("Get an international relocation payment: criteria for trainee teachers", "https://getintoteaching.education.gov.uk/non-uk-teachers/get-an-international-relocation-payment/#criteria-for-teachers", { target: "_blank" })%>

Get help

diff --git a/app/views/system_admin/applicants/duplicates.html.erb b/app/views/system_admin/applicants/duplicates.html.erb index 031436f1..d7aa9301 100644 --- a/app/views/system_admin/applicants/duplicates.html.erb +++ b/app/views/system_admin/applicants/duplicates.html.erb @@ -10,6 +10,7 @@ row.with_cell(text: 'Email') row.with_cell(text: 'Phone number') row.with_cell(text: 'Passport') + row.with_cell(text: 'IP Address') row.with_cell(text: 'Status') end end @@ -37,8 +38,15 @@ row.with_cell(text: duplicate.passport_number) end + if duplicate.applicant.ip_address.present? + row.with_cell(text: govuk_link_to(duplicate.applicant.ip_address, duplicates_path(search: duplicate.applicant.ip_address))) + else + row.with_cell(text: duplicate.applicant.ip_address) + end row.with_cell(text: duplicate.status.humanize) end end end end %> + +<%= govuk_pagination(pagy: @pagy) %> diff --git a/app/views/system_admin/applicants/index.html.erb b/app/views/system_admin/applicants/index.html.erb index dc6637ab..5aba9eb6 100644 --- a/app/views/system_admin/applicants/index.html.erb +++ b/app/views/system_admin/applicants/index.html.erb @@ -1,8 +1,3 @@ -<% statuses = options_for_select( - ApplicationProgress.statuses.keys.map { |status| [status.humanize, status] }.unshift(['All statuses', '']), - selected: params[:status] - ) -%> <%= form_with(url: applicants_path, method: :get, id: :search) do |f| %>
<%= f.govuk_text_field :search, value: params[:search], label: { text: 'Search' }, hint: { text: 'Search by name, email, passport number or unique reference number (URN)' } %> @@ -10,7 +5,7 @@
- <%= f.govuk_select :status, statuses, label: { text: "Filter by application status" } %> + <%= f.govuk_select :status, application_statuses_options(selected: params[:status], all_statuses: true), label: { text: "Filter by application status" } %>
@@ -25,9 +20,6 @@
<%= f.govuk_submit 'Search', secondary: true %>
- <% if session[:filter_status].present? %> - <%= link_to "Download QA CSV", download_qa_csv_applicants_path, class: "govuk-button" %> - <% end %> <% end %> <%= govuk_table(classes: "applicants-table") do |table| diff --git a/app/views/system_admin/dashboard/show.html.erb b/app/views/system_admin/dashboard/show.html.erb index 4b160375..f2f37d87 100644 --- a/app/views/system_admin/dashboard/show.html.erb +++ b/app/views/system_admin/dashboard/show.html.erb @@ -141,4 +141,17 @@
+
+

Statuses

+ + <% @kpis.status_breakdown.each do |k,v|%> + + + + + <% end %> +
<%= link_to(k.titleize, applicants_path(search: nil, status: k)) %><%= v %>
+
+ +
diff --git a/app/views/system_admin/reports/index.html.erb b/app/views/system_admin/reports/index.html.erb index 50268e67..5e78d0f7 100644 --- a/app/views/system_admin/reports/index.html.erb +++ b/app/views/system_admin/reports/index.html.erb @@ -28,3 +28,33 @@ <%= link_to "Download", report_path(:payroll), class: "govuk-button" %>

+ +
+

Applications Data report

+

+ Download a CSV file of all applications +

+

+ <%= link_to "Download", report_path(:applications), class: "govuk-button" %> +

+
+ +
+

QA reports

+

+ Download a QA CSV file +

+

+ + <%= form_with(url: report_path(:qa), method: :get) do |f| %> +

+
+ <%= f.govuk_select :status, application_statuses_options, label: { text: "Filter by application status" } %> +
+
+ <%= f.govuk_submit 'Download', class: "govuk-button"%> +
+
+ <% end %> +

+
diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb new file mode 100644 index 00000000..b75f9716 --- /dev/null +++ b/config/initializers/flipper.rb @@ -0,0 +1,6 @@ +class CanAccessFlipperUI + def self.matches?(request) + current_user = request.env["warden"].user + current_user.present? + end +end diff --git a/config/locales/steps.en.yml b/config/locales/steps.en.yml index 1b757e40..5f49c753 100644 --- a/config/locales/steps.en.yml +++ b/config/locales/steps.en.yml @@ -195,7 +195,7 @@ en: blank: Enter your date of birth not_in_future: Date of birth cannot be in the future over_max_age: Age must be below 80 - below_min_age: Age must be above 22 + below_min_age: Age must be above 21 sex: blank: Enter your sex inclusion: Enter your sex diff --git a/config/routes.rb b/config/routes.rb index 25bd141e..ad1fdbab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -25,17 +25,16 @@ end scope module: :system_admin, path: "system-admin" do - resources :applicants, only: %i[index show edit update] do - collection do - get :download_qa_csv - end - end - + resources :applicants, only: %i[index show edit update] resources :users, except: %i[show] resource :settings, only: %i[edit update] get "/dashboard", to: "dashboard#show" resources "reports", only: %i[show index] get "/duplicates", to: "applicants#duplicates" get "/audits", to: "audits#index" + + constraints CanAccessFlipperUI do + mount Flipper::UI.app(Flipper) => "/features" + end end end diff --git a/db/migrate/20230915092215_create_flipper_tables.rb b/db/migrate/20230915092215_create_flipper_tables.rb new file mode 100644 index 00000000..1eae1b6d --- /dev/null +++ b/db/migrate/20230915092215_create_flipper_tables.rb @@ -0,0 +1,22 @@ +class CreateFlipperTables < ActiveRecord::Migration[7.0] + def self.up + create_table :flipper_features do |t| + t.string :key, null: false + t.timestamps null: false + end + add_index :flipper_features, :key, unique: true + + create_table :flipper_gates do |t| + t.string :feature_key, null: false + t.string :key, null: false + t.string :value + t.timestamps null: false + end + add_index :flipper_gates, %i[feature_key key value], unique: true + end + + def self.down + drop_table :flipper_gates + drop_table :flipper_features + end +end diff --git a/db/schema.rb b/db/schema.rb index 2873ecf2..2a356c60 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -108,6 +108,22 @@ t.index ["user_id", "user_type"], name: "user_index" end + create_table "flipper_features", force: :cascade do |t| + t.string "key", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["key"], name: "index_flipper_features_on_key", unique: true + end + + create_table "flipper_gates", force: :cascade do |t| + t.string "feature_key", null: false + t.string "key", null: false + t.string "value" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["feature_key", "key", "value"], name: "index_flipper_gates_on_feature_key_and_key_and_value", unique: true + end + create_table "forms", force: :cascade do |t| t.string "given_name" t.string "middle_name" diff --git a/spec/factories/applications.rb b/spec/factories/applications.rb index 08050094..49d86172 100644 --- a/spec/factories/applications.rb +++ b/spec/factories/applications.rb @@ -31,7 +31,7 @@ visa_type { VisaStep::VALID_ANSWERS_OPTIONS.reject { _1 == "Other" }.sample } date_of_entry { Time.zone.today } start_date { 1.month.from_now.to_date } - submitted + urn { Urn.generate(application_route) } factory :teacher_application do application_route { "teacher" } @@ -45,14 +45,6 @@ applicant strategy: :create, factory: :applicant end - trait :submitted do - urn { Urn.generate(application_route) } - end - - trait :not_submitted do - urn { nil } - end - trait :with_initial_checks_completed do application_progress strategy: :build, factory: %i[application_progress initial_checks_completed] end diff --git a/spec/features/admin_console/applications_list_spec.rb b/spec/features/admin_console/applications_list_spec.rb index d00ffb07..85a51dee 100644 --- a/spec/features/admin_console/applications_list_spec.rb +++ b/spec/features/admin_console/applications_list_spec.rb @@ -60,13 +60,13 @@ def given_there_are_few_applications # Create 2 specific applications for search tests unique_applicant = create(:applicant, given_name: "Unique Given Name", middle_name: "Unique Middle Name", family_name: "Unique Family Name", email_address: "unique@example.com") - create(:application, :submitted, applicant: unique_applicant, urn: "Unique Urn 1") + create(:application, applicant: unique_applicant, urn: "Unique Urn 1") another_applicant = create(:applicant, given_name: "Another Given Name", middle_name: "Another Middle Name", family_name: "Another Family Name", email_address: "another@example.com") - create(:application, :submitted, applicant: another_applicant, urn: "Unique Urn 2") + create(:application, applicant: another_applicant, urn: "Unique Urn 2") # Create 19 more applications for pagination test - create_list(:application, 19, :submitted) + create_list(:application, 19) end def given_there_is_an_application_that_breached_sla diff --git a/spec/features/admin_console/download_qa_csv_file_spec.rb b/spec/features/admin_console/download_qa_csv_file_spec.rb deleted file mode 100644 index 3c7e1548..00000000 --- a/spec/features/admin_console/download_qa_csv_file_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -require "rails_helper" - -describe "Download QA CSV functionality" do - include AdminHelpers - - it "shows the 'Download QA CSV' button only if a status filter is applied" do - given_there_are_few_applications - given_i_am_signed_as_an_admin - when_i_am_in_the_applications_list_page - then_i_should_not_see_the_download_button - when_i_filter_by_a_status - then_i_should_see_the_download_button - end - - it "marks applications as qa before generating the csv" do - given_there_are_few_applications - given_i_am_signed_as_an_admin - when_i_am_in_the_applications_list_page - when_i_filter_by_a_status - when_i_download_the_csv - - then_all_applications_should_be_marked_as_qa - end - - it "generates an empty csv when downloaded twice consecutively" do - given_there_are_few_applications - given_i_am_signed_as_an_admin - when_i_am_in_the_applications_list_page - when_i_filter_by_a_status - when_i_download_the_csv - when_i_am_in_the_applications_list_page - when_i_filter_by_a_status - when_i_download_the_csv_again - - then_csv_should_be_empty - end - - def given_there_are_few_applications - create_list(:application, 5, :submitted) - end - - def when_i_am_in_the_applications_list_page - visit(applicants_path) - end - - def when_i_filter_by_a_status - select "Initial checks", from: "status" - click_button "Search" - end - - def then_i_should_not_see_the_download_button - expect(page).not_to have_link("Download QA CSV") - end - - def then_i_should_see_the_download_button - expect(page).to have_link("Download QA CSV") - end - - def when_i_download_the_csv - click_link "Download QA CSV" - end - - def when_i_download_the_csv_again - when_i_download_the_csv - end - - def then_all_applications_should_be_marked_as_qa - Application.all.each do |app| - expect(app.reload.qa?).to be(true) - end - end - - def then_csv_should_be_empty - expect(page.body.lines.count).to eq(1) - end -end diff --git a/spec/features/admin_console/duplicates_search_spec.rb b/spec/features/admin_console/duplicates_search_spec.rb index 9d21f2a2..c4f64ce3 100644 --- a/spec/features/admin_console/duplicates_search_spec.rb +++ b/spec/features/admin_console/duplicates_search_spec.rb @@ -10,10 +10,10 @@ let!(:applicant_four) { build(:applicant, email_address: "test2@example.com", passport_number: "123456", phone_number: "999999999") } before do - create(:application, :submitted, applicant: applicant_one) - create(:application, :submitted, applicant: applicant_two) - create(:application, :submitted, applicant: applicant_three) - create(:application, :submitted, applicant: applicant_four) + create(:application, applicant: applicant_one) + create(:application, applicant: applicant_two) + create(:application, applicant: applicant_three) + create(:application, applicant: applicant_four) end it "Admin can search for duplicates by email" do @@ -34,20 +34,6 @@ then_i_see_matching_duplicates_by_passport_number end - it "the view renders even where there are 'in progress' applications" do - given_i_am_signed_as_an_admin - when_there_are_in_progress_applications - when_i_visit_the_duplicates_page - then_i_see_the_in_progress_applications - end - - it "shows duplicates only once even if they match multiple times" do - given_i_am_signed_as_an_admin - when_there_are_in_progress_applications - when_i_visit_the_duplicates_page - then_i_see_the_in_progress_applications_only_once - end - def when_i_search_for_a_duplicate_by(type) visit duplicates_path case type @@ -60,18 +46,10 @@ def when_i_search_for_a_duplicate_by(type) end end - def when_there_are_in_progress_applications - create(:application, applicant: nil, urn: nil, application_progress: nil) - end - def when_i_visit_the_duplicates_page visit duplicates_path end - def then_i_see_the_in_progress_applications - expect(page).to have_content("Duplicated applications") - end - def then_i_see_matching_duplicates expect(page).to have_content("test@example.com") expect(page).to have_content("123456") diff --git a/spec/features/admin_console/reports_spec.rb b/spec/features/admin_console/reports_spec.rb index 41e5aec7..f01b734e 100644 --- a/spec/features/admin_console/reports_spec.rb +++ b/spec/features/admin_console/reports_spec.rb @@ -29,6 +29,22 @@ then_the_payroll_data_csv_report_is_downloaded end + it "exports Application CSV" do + given_i_am_signed_as_an_admin + when_i_am_in_the_reports_page + and_i_click_on_the_applications_csv_link + + then_the_applications_csv_report_is_downloaded + end + + it "exports Qa report CSV" do + given_i_am_signed_as_an_admin + when_i_am_in_the_reports_page + and_i_click_on_the_qa_report_csv_button + + then_the_qa_report_csv_report_is_downloaded + end + private def then_the_standing_data_csv_report_is_downloaded @@ -49,6 +65,18 @@ def then_the_payroll_data_csv_report_is_downloaded expect(page.response_headers["Content-Disposition"]).to match(/filename="Payroll-Report.*/) end + def then_the_applications_csv_report_is_downloaded + expect(page.response_headers["Content-Type"]).to match(/text\/csv/) + expect(page.response_headers["Content-Disposition"]).to include "attachment" + expect(page.response_headers["Content-Disposition"]).to match(/filename="Applications-Report.*/) + end + + def then_the_qa_report_csv_report_is_downloaded + expect(page.response_headers["Content-Type"]).to match(/text\/csv/) + expect(page.response_headers["Content-Disposition"]).to include "attachment" + expect(page.response_headers["Content-Disposition"]).to match(/filename="QA-Report-initial_checks*/) + end + def and_i_click_on_the_home_office_csv_link within ".home-office" do click_on "Download" @@ -67,6 +95,18 @@ def and_i_click_on_the_payroll_data_csv_link end end + def and_i_click_on_the_applications_csv_link + within ".applications" do + click_on "Download" + end + end + + def and_i_click_on_the_qa_report_csv_button + within ".applications-qa" do + click_on "Download" + end + end + def when_i_am_in_the_reports_page visit reports_path end diff --git a/spec/models/application_spec.rb b/spec/models/application_spec.rb index 5ce83fb7..5ed916b0 100644 --- a/spec/models/application_spec.rb +++ b/spec/models/application_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Application do describe "validations" do context "when the application has been submitted" do - subject(:application) { build(:teacher_application, :submitted) } + subject(:application) { build(:teacher_application) } it { expect(application).to validate_presence_of(:application_date) } it { expect(application).to validate_presence_of(:application_route) } @@ -36,30 +36,9 @@ it { expect(application).to validate_presence_of(:visa_type) } it { expect(application).to validate_presence_of(:applicant) } end - - context "when the application has not been submitted" do - it "is valid" do - expect(described_class.new).to be_valid - end - end end describe "scopes" do - describe ".submitted" do - it "returns applications with a URN" do - create_list(:application, 2, :submitted) - create(:application, :not_submitted) - - expect(described_class.submitted.count).to eq 2 - end - - it "does not return applications without a URN" do - create(:application, :not_submitted) - - expect(described_class.submitted.count).to eq 0 - end - end - describe ".filter_by_status" do it "returns applications with the specified status" do initial_checks_application = build(:application) @@ -95,22 +74,6 @@ end end - describe "#assign_urn!" do - it "matches the required format for a teacher" do - application = create(:teacher_application) - application.assign_urn! - - expect(application.reload.urn).to match(/^IRPTE[A-Z0-9]{5}$/) - end - - it "matches the required format for a trainee" do - application = create(:salaried_trainee_application) - application.assign_urn! - - expect(application.reload.urn).to match(/^IRPST[A-Z0-9]{5}$/) - end - end - describe ".search" do let(:applicant_john) { create(:applicant, given_name: "John", family_name: "Doe", email_address: "john.doe@example.com", passport_number: "123456") } let(:application_john) { create(:application, urn: "APP123", applicant: applicant_john) } diff --git a/spec/models/kpis_spec.rb b/spec/models/kpis_spec.rb index f14c4142..c1ed016a 100644 --- a/spec/models/kpis_spec.rb +++ b/spec/models/kpis_spec.rb @@ -3,7 +3,6 @@ RSpec.describe Kpis do describe "#total_applications" do it "returns the total number of applications" do - create(:application, :not_submitted) create_list(:application, 5) kpis = described_class.new diff --git a/spec/models/policies/contract_start_date_spec.rb b/spec/models/policies/contract_start_date_spec.rb deleted file mode 100644 index 0f60f3de..00000000 --- a/spec/models/policies/contract_start_date_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe Policies::ContractStartDate do - let(:first_monday_july) { Date.new(2023, 7, 3) } - - describe "#eligible?" do - it "returns true when the date is on the first Monday in July" do - expect(described_class.eligible?(first_monday_july)).to be true - end - - it "returns true when the date is after the first Monday in July" do - date = first_monday_july.advance(days: 1) - - expect(described_class.eligible?(date)).to be true - end - - it "returns false when the date is before the first Monday in July" do - date = first_monday_july.advance(days: -1) - expect(described_class.eligible?(date)).to be false - end - end -end diff --git a/spec/models/policies/entry_date_spec.rb b/spec/models/policies/entry_date_spec.rb deleted file mode 100644 index b4ced3db..00000000 --- a/spec/models/policies/entry_date_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe Policies::EntryDate do - subject(:policy) { described_class } - - describe "#eligible?" do - it "returns true if the entry date is more than 3 months since start of contract" do - entry_date = Date.parse("2020-01-03") - start_date = Date.parse("2020-04-02") - - expect(policy.eligible?(entry_date, start_date)).to be(true) - end - - it "returns true if the entry date is 3 months since start of contract" do - entry_date = Date.parse("2020-01-02") - start_date = Date.parse("2020-04-02") - - expect(policy.eligible?(entry_date, start_date)).to be(true) - end - - it "returns false if the entry date is less than 3 months since start of contract" do - entry_date = Date.parse("2020-01-01") - start_date = Date.parse("2020-04-02") - - expect(policy.eligible?(entry_date, start_date)).to be(false) - end - end -end diff --git a/spec/models/reports/applications_spec.rb b/spec/models/reports/applications_spec.rb new file mode 100644 index 00000000..1b1928a4 --- /dev/null +++ b/spec/models/reports/applications_spec.rb @@ -0,0 +1,83 @@ +require "rails_helper" + +module Reports + describe Applications do + include ActiveSupport::Testing::TimeHelpers + + subject(:report) { described_class.new } + + it "returns the name of the Report" do + frozen_time = Time.zone.local(2023, 7, 17, 12, 30, 45) + travel_to frozen_time do + expected_name = "Applications-Report-20230717-123045.csv" + + report = described_class.new + actual_name = report.name + + expect(actual_name).to eq(expected_name) + end + end + + describe "#csv" do + let(:rejected) { create(:application, :with_rejection_completed) } + let(:paid) { create(:application, :with_payment_confirmation_completed) } + let(:banking) { create(:application, :with_banking_approval_completed) } + let(:school) { create(:application, :with_school_checks_completed) } + let(:home_office) { create(:application, :with_home_office_checks_completed) } + let(:initial) { create(:application, :with_initial_checks_completed) } + let(:application) { create(:application) } + + before do + rejected + paid + banking + school + home_office + initial + application + end + + context "returns file with header" do + let(:header) do + [ + "Ip Address", + "Given Name", + "Middle Name", + "Family Name", + "Email Address", + "Phone Number", + "Date Of Birth", + "Sex", + "Passport Number", + "Nationality", + "Student Loan", + "Address Line 1", + "Address Line 2", + "City", + "Postcode", + "Application Date", + "Application Route", + "Status", + "Date Of Entry", + "Start Date", + "Subject", + "Urn", + "Visa Type", + ].join(",") + end + + it { expect(report.csv).to include(header) } + end + + context "returns all applications" do + it { expect(report.csv).to include(application.urn) } + it { expect(report.csv).to include(initial.urn) } + it { expect(report.csv).to include(home_office.urn) } + it { expect(report.csv).to include(school.urn) } + it { expect(report.csv).to include(banking.urn) } + it { expect(report.csv).to include(paid.urn) } + it { expect(report.csv).to include(rejected.urn) } + end + end + end +end diff --git a/spec/models/reports/home_office_spec.rb b/spec/models/reports/home_office_spec.rb index 88405791..2d413db2 100644 --- a/spec/models/reports/home_office_spec.rb +++ b/spec/models/reports/home_office_spec.rb @@ -55,22 +55,34 @@ module Reports application.urn, application.applicant.full_name, application.applicant.date_of_birth, + nil, application.applicant.nationality, + nil, application.applicant.passport_number, nil, nil, + nil, + nil, + nil, + nil, ].join(",")) end it "returns the header in CSV format" do expected_header = [ - "URN", + "ID", "Full Name", "DOB", + "Gender", "Nationality", + "Place of Birth", "Passport Number", - "Visa Type", - "Date of UK entry", + "National Insurance Number", + "Address", + "Postcode", + "Email", + "Telephone", + "Reference", ].join(",") expect(report.csv).to include(expected_header) diff --git a/spec/queries/average_age_query_spec.rb b/spec/queries/average_age_query_spec.rb index 19615279..6960cf71 100644 --- a/spec/queries/average_age_query_spec.rb +++ b/spec/queries/average_age_query_spec.rb @@ -4,8 +4,6 @@ describe "#call" do context "when there are a few applicants" do before do - create(:application, :not_submitted, applicant: create(:applicant, date_of_birth: 25.years.ago)) - create(:application, applicant: create(:applicant, date_of_birth: 35.years.ago)) create(:application, applicant: create(:applicant, date_of_birth: 45.years.ago)) create(:application, applicant: create(:applicant, date_of_birth: 52.years.ago)) diff --git a/spec/queries/gender_breakdown_query_spec.rb b/spec/queries/gender_breakdown_query_spec.rb index 4584948f..603d7269 100644 --- a/spec/queries/gender_breakdown_query_spec.rb +++ b/spec/queries/gender_breakdown_query_spec.rb @@ -4,8 +4,6 @@ describe "#call" do context "when there are a few applicants" do before do - create(:application, :not_submitted, applicant: create(:applicant, sex: "male")) - create(:application, applicant: create(:applicant, sex: "male")) create(:application, applicant: create(:applicant, sex: "female")) create(:application, applicant: create(:applicant, sex: "male")) diff --git a/spec/queries/nationality_breakdown_query_spec.rb b/spec/queries/nationality_breakdown_query_spec.rb index 9d3b5dc2..565fb718 100644 --- a/spec/queries/nationality_breakdown_query_spec.rb +++ b/spec/queries/nationality_breakdown_query_spec.rb @@ -4,8 +4,6 @@ describe "#call" do context "when there are a few applicants" do before do - create(:application, :not_submitted, applicant: create(:applicant, nationality: "Nationality 1")) - create(:application, applicant: create(:applicant, nationality: "Nationality 1")) create(:application, applicant: create(:applicant, nationality: "Nationality 3")) create(:application, applicant: create(:applicant, nationality: "Nationality 4")) diff --git a/spec/queries/rejection_reason_breakdown_query_spec.rb b/spec/queries/rejection_reason_breakdown_query_spec.rb index adf14d0c..d6c80bae 100644 --- a/spec/queries/rejection_reason_breakdown_query_spec.rb +++ b/spec/queries/rejection_reason_breakdown_query_spec.rb @@ -8,7 +8,6 @@ create(:application_progress, application: create(:application, applicant:), rejection_reason: "suspected_fraud") create(:application_progress, application: create(:application, applicant:), rejection_reason: "suspected_fraud") create(:application_progress, application: create(:application, applicant:), rejection_reason: "ineligible_school") - create(:application_progress, application: create(:application, :not_submitted, applicant:), rejection_reason: "duplicate_submission") # This one should not be included in the count because the associated application is not submitted end it "returns the correct rejection reason breakdown" do diff --git a/spec/queries/route_breakdown_query_spec.rb b/spec/queries/route_breakdown_query_spec.rb index 7391a312..016a32c6 100644 --- a/spec/queries/route_breakdown_query_spec.rb +++ b/spec/queries/route_breakdown_query_spec.rb @@ -4,7 +4,6 @@ describe "#call" do context "when there are a few applicants" do before do - create(:teacher_application, :not_submitted) create(:teacher_application) create(:salaried_trainee_application) create(:teacher_application) diff --git a/spec/queries/status_breakdown_query_spec.rb b/spec/queries/status_breakdown_query_spec.rb new file mode 100644 index 00000000..43603ded --- /dev/null +++ b/spec/queries/status_breakdown_query_spec.rb @@ -0,0 +1,45 @@ +require "rails_helper" + +RSpec.describe StatusBreakdownQuery, type: :service do + subject(:breakdown) { described_class.call } + + describe ".call" do + let(:expected_breakdown) do + { + "initial_checks" => 0, + "home_office_checks" => 0, + "school_checks" => 0, + "bank_approval" => 0, + "payment_confirmation" => 0, + "paid" => 0, + "rejected" => 0, + } + end + + it "returns the ordered application status breakdown" do + expect(breakdown).to include(expected_breakdown) + end + + context "with data" do + before do + create(:application, :with_initial_checks_completed) + end + + let(:expected_breakdown) do + { + "initial_checks" => 0, + "home_office_checks" => 1, + "school_checks" => 0, + "bank_approval" => 0, + "payment_confirmation" => 0, + "paid" => 0, + "rejected" => 0, + } + end + + it "returns the ordered application status breakdown" do + expect(breakdown).to include(expected_breakdown) + end + end + end +end diff --git a/spec/queries/subject_breakdown_query_spec.rb b/spec/queries/subject_breakdown_query_spec.rb index 00e8147d..c9d4408b 100644 --- a/spec/queries/subject_breakdown_query_spec.rb +++ b/spec/queries/subject_breakdown_query_spec.rb @@ -4,7 +4,6 @@ describe "#call" do context "when there are a few applicants" do before do - create(:application, :not_submitted) create(:application, subject: :physics) create(:application, subject: :languages) create(:application, subject: :general_science) diff --git a/spec/queries/visa_breakdown_query_spec.rb b/spec/queries/visa_breakdown_query_spec.rb index 7280d223..abeaf581 100644 --- a/spec/queries/visa_breakdown_query_spec.rb +++ b/spec/queries/visa_breakdown_query_spec.rb @@ -4,7 +4,6 @@ describe "#call" do context "when there are a few applicants" do before do - create(:application, :not_submitted, visa_type: "visa_1") create(:application, visa_type: "visa_1") create_list(:application, 2, visa_type: "visa_2") create_list(:application, 4, visa_type: "visa_3") diff --git a/spec/services/report_spec.rb b/spec/services/report_spec.rb new file mode 100644 index 00000000..046ad024 --- /dev/null +++ b/spec/services/report_spec.rb @@ -0,0 +1,76 @@ +require "rails_helper" + +RSpec.describe Report do + subject(:report) { described_class } + + describe "registered reports" do + subject(:registered_reports) { described_class::REGISTERED_REPORTS } + + let(:expected_ids) do + %i[home_office standing_data payroll applications qa] + end + + let(:expected_classes) do + [ + Reports::HomeOffice, + Reports::StandingData, + Reports::Payroll, + Reports::Applications, + Reports::QaReport, + ] + end + + it { expect(registered_reports.keys).to match_array(expected_ids) } + it { expect(registered_reports.values).to match_array(expected_classes) } + end + + describe ".call" do + subject(:service) { described_class.new(report_id, status:) } + + let(:report_id) { "qa" } + let(:status) { "initial_checks" } + + context "report_name" do + it { expect(service.report_name).to eq("Reports::qareport") } + end + + context "filename" do + include ActiveSupport::Testing::TimeHelpers + it "returns the name of the Report" do + frozen_time = Time.zone.local(2023, 7, 17, 12, 30, 45) + travel_to frozen_time do + expected_name = "QA-Report-initial_checks-2023_07_17-12_30_45.csv" + + expect(service.filename).to eq(expected_name) + end + end + end + + # rubocop:disable RSpec/VerifiedDoubles + context "qa report data" do + let(:report) { spy(Reports::QaReport) } + + before do + allow(Reports::QaReport).to receive(:new).and_return(report) + service.data + end + + it { expect(Reports::QaReport).to have_received(:new).with(kind_of(Array), status) } + it { expect(report).to have_received(:csv) } + end + + context "other report data" do + let(:report_id) { "home_office" } + let(:report) { spy(Reports::HomeOffice) } + + before do + allow(Reports::HomeOffice).to receive(:new).and_return(report) + service.data + end + + it { expect(Reports::HomeOffice).to have_received(:new) } + it { expect(report).to have_received(:csv) } + end + # rubocop:enable RSpec/VerifiedDoubles + end +end diff --git a/spec/steps/personal_details_step_spec.rb b/spec/steps/personal_details_step_spec.rb index 08237787..50a11e67 100644 --- a/spec/steps/personal_details_step_spec.rb +++ b/spec/steps/personal_details_step_spec.rb @@ -111,7 +111,7 @@ describe "date_of_birth minimum age" do let(:form) { build(:form, date_of_birth: age) } let(:error) { step.errors.messages_for(:date_of_birth) } - let(:minimum_age) { 22 } + let(:minimum_age) { 21 } before { step.valid? } @@ -189,7 +189,7 @@ it "has MIN_AGE set to 22" do min_age = described_class.send(:const_get, :MIN_AGE) - expect(min_age).to eq(22) + expect(min_age).to eq(21) end end