Browse Source

Merge branch 'main' into case-log-exports

pull/288/head
Stéphane Meny 3 years ago committed by GitHub
parent
commit
043ebd0690
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      Gemfile
  2. 135
      Gemfile.lock
  3. 15
      app/concerns/admin/paper_trail.rb
  4. 8
      app/controllers/application_controller.rb
  5. 2
      app/controllers/case_logs_controller.rb
  6. 4
      app/controllers/form_controller.rb
  7. 8
      app/helpers/check_answers_helper.rb
  8. 2
      app/models/admin_user.rb
  9. 3
      app/models/bulk_upload.rb
  10. 27
      app/models/case_log.rb
  11. 11
      app/models/constants/case_log.rb
  12. 11
      app/models/form.rb
  13. 35
      app/models/form/question.rb
  14. 4
      app/models/form/subsection.rb
  15. 3
      app/models/organisation.rb
  16. 2
      app/models/user.rb
  17. 12
      app/models/validations/financial_validations.rb
  18. 2
      app/models/validations/household_validations.rb
  19. 10
      app/models/validations/submission_validations.rb
  20. 2
      app/views/case_logs/edit.html.erb
  21. 3
      app/views/form/_checkbox_question.html.erb
  22. 2
      app/views/form/_numeric_question.html.erb
  23. 11
      app/views/form/page.html.erb
  24. 146
      config/forms/2021_2022.json
  25. 4
      config/initializers/active_admin.rb
  26. 2
      config/initializers/devise.rb
  27. 5
      config/locales/en.yml
  28. 7
      db/migrate/20220207091117_add_declaration.rb
  29. 22
      db/migrate/20220207151239_create_versions.rb
  30. 11
      db/migrate/20220207151312_remove_discarded_at_from_case_logs.rb
  31. 15
      db/migrate/20220208101235_remove_gdpr_fields.rb
  32. 20
      db/schema.rb
  33. 27
      infrastructure_setup.md
  34. 5
      manifest.yml
  35. 26
      spec/controllers/admin/admin_users_controller_spec.rb
  36. 47
      spec/controllers/admin/case_logs_controller_spec.rb
  37. 3
      spec/controllers/admin/dashboard_controller_spec.rb
  38. 40
      spec/controllers/admin/organisations_controller_spec.rb
  39. 24
      spec/controllers/admin/users_controller_spec.rb
  40. 8
      spec/factories/case_log.rb
  41. 6
      spec/features/form/check_answers_page_spec.rb
  42. 41
      spec/features/form/checkboxes_spec.rb
  43. 4
      spec/features/form/tasklist_page_spec.rb
  44. 31
      spec/features/form/validations_spec.rb
  45. 15
      spec/fixtures/complete_case_log.json
  46. 54
      spec/fixtures/forms/2021_2022.json
  47. 39
      spec/fixtures/forms/2022_2023.json
  48. 3
      spec/helpers/check_answers_helper_spec.rb
  49. 4
      spec/helpers/tasklist_helper_spec.rb
  50. 15
      spec/models/admin_user_spec.rb
  51. 53
      spec/models/case_log_spec.rb
  52. 40
      spec/models/form/question_spec.rb
  53. 32
      spec/models/form/subsection_spec.rb
  54. 16
      spec/models/form_spec.rb
  55. 13
      spec/models/organisation_spec.rb
  56. 13
      spec/models/user_spec.rb
  57. 24
      spec/models/validations/financial_validations_spec.rb
  58. 29
      spec/requests/case_logs_controller_spec.rb
  59. 7
      spec/requests/form_controller_spec.rb
  60. 7
      spec/requests/organisations_controller_spec.rb
  61. 7
      spec/requests/users_controller_spec.rb
  62. 8
      spec/support/controller_macros.rb
  63. 21
      spec/views/form/page_view_spec.rb

7
Gemfile

@ -23,11 +23,8 @@ gem "govuk_design_system_formbuilder"
gem "notifications-ruby-client" gem "notifications-ruby-client"
# Turbo and Stimulus # Turbo and Stimulus
gem "hotwire-rails" gem "hotwire-rails"
# Soft delete ActiveRecords objects
gem "discard"
# Administration framework # Administration framework
gem "activeadmin", git: "https://github.com/tagliala/activeadmin.git", branch: "feature/railties-7" gem "activeadmin", git: "https://github.com/tagliala/activeadmin.git", branch: "feature/railties-7"
gem "arbre", github: "activeadmin/arbre"
# Admin charts # Admin charts
gem "chartkick" gem "chartkick"
# Spreadsheet parsing # Spreadsheet parsing
@ -48,6 +45,10 @@ gem "postcodes_io"
gem "view_component" gem "view_component"
# Use the AWS S3 SDK as storage mechanism # Use the AWS S3 SDK as storage mechanism
gem "aws-sdk-s3" gem "aws-sdk-s3"
# Track changes to models for auditing or versioning.
gem "paper_trail"
# Store active record objects in version whodunnits
gem "paper_trail-globalid"
group :development, :test do group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console # Call 'byebug' anywhere in the code to stop execution and get a debugger console

135
Gemfile.lock

@ -1,11 +1,3 @@
GIT
remote: https://github.com/activeadmin/arbre.git
revision: dcea09da31322f2bc9bdfc32eb14ec0aaf34ae92
specs:
arbre (1.4.0)
activesupport (>= 3.0.0, < 7.1)
ruby2_keywords (>= 0.0.2, < 1.0)
GIT GIT
remote: https://github.com/baarkerlounger/devise.git remote: https://github.com/baarkerlounger/devise.git
revision: 6d0b6b52a9d0e87ae6d9f9acb562169751623078 revision: 6d0b6b52a9d0e87ae6d9f9acb562169751623078
@ -20,7 +12,7 @@ GIT
GIT GIT
remote: https://github.com/baarkerlounger/two_factor_authentication.git remote: https://github.com/baarkerlounger/two_factor_authentication.git
revision: 1fa214d18d311e019a343f836f2c591c0fa3d308 revision: c2237dedb89b1fc53101cec536e57912049c5412
specs: specs:
two_factor_authentication (2.2.0) two_factor_authentication (2.2.0)
devise devise
@ -31,7 +23,7 @@ GIT
GIT GIT
remote: https://github.com/tagliala/activeadmin.git remote: https://github.com/tagliala/activeadmin.git
revision: 5d6dfbe086331ce0538cc726abfe2eebe0260f02 revision: 1515a40a1d407dafd34b104fabd8401e84baca94
branch: feature/railties-7 branch: feature/railties-7
specs: specs:
activeadmin (2.9.0) activeadmin (2.9.0)
@ -47,73 +39,76 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.0.1) actioncable (7.0.2)
actionpack (= 7.0.1) actionpack (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (7.0.1) actionmailbox (7.0.2)
actionpack (= 7.0.1) actionpack (= 7.0.2)
activejob (= 7.0.1) activejob (= 7.0.2)
activerecord (= 7.0.1) activerecord (= 7.0.2)
activestorage (= 7.0.1) activestorage (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.0.1) actionmailer (7.0.2)
actionpack (= 7.0.1) actionpack (= 7.0.2)
actionview (= 7.0.1) actionview (= 7.0.2)
activejob (= 7.0.1) activejob (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (7.0.1) actionpack (7.0.2)
actionview (= 7.0.1) actionview (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
rack (~> 2.0, >= 2.2.0) rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.1) actiontext (7.0.2)
actionpack (= 7.0.1) actionpack (= 7.0.2)
activerecord (= 7.0.1) activerecord (= 7.0.2)
activestorage (= 7.0.1) activestorage (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.0.1) actionview (7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.1) activejob (7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.0.1) activemodel (7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
activerecord (7.0.1) activerecord (7.0.2)
activemodel (= 7.0.1) activemodel (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
activestorage (7.0.1) activestorage (7.0.2)
actionpack (= 7.0.1) actionpack (= 7.0.2)
activejob (= 7.0.1) activejob (= 7.0.2)
activerecord (= 7.0.1) activerecord (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
marcel (~> 1.0) marcel (~> 1.0)
mini_mime (>= 1.1.0) mini_mime (>= 1.1.0)
activesupport (7.0.1) activesupport (7.0.2)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) tzinfo (~> 2.0)
addressable (2.8.0) addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
arbre (1.5.0)
activesupport (>= 3.0.0, < 7.1)
ruby2_keywords (>= 0.0.2, < 1.0)
ast (2.4.2) ast (2.4.2)
aws-eventstream (1.2.0) aws-eventstream (1.2.0)
aws-partitions (1.553.0) aws-partitions (1.553.0)
@ -161,8 +156,6 @@ GEM
deep_merge (1.2.2) deep_merge (1.2.2)
diff-lcs (1.5.0) diff-lcs (1.5.0)
digest (3.1.0) digest (3.1.0)
discard (1.2.1)
activerecord (>= 4.2, < 8)
docile (1.4.0) docile (1.4.0)
dotenv (2.7.6) dotenv (2.7.6)
dotenv-rails (2.7.6) dotenv-rails (2.7.6)
@ -271,6 +264,12 @@ GEM
childprocess (>= 0.6.3, < 5) childprocess (>= 0.6.3, < 5)
iniparse (~> 1.4) iniparse (~> 1.4)
rexml (~> 3.2) rexml (~> 3.2)
paper_trail (12.2.0)
activerecord (>= 5.2)
request_store (~> 1.1)
paper_trail-globalid (0.2.0)
globalid
paper_trail (>= 3.0.0)
parallel (1.21.0) parallel (1.21.0)
parser (3.1.0.0) parser (3.1.0.0)
ast (~> 2.4.1) ast (~> 2.4.1)
@ -294,28 +293,28 @@ GEM
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (7.0.1) rails (7.0.2)
actioncable (= 7.0.1) actioncable (= 7.0.2)
actionmailbox (= 7.0.1) actionmailbox (= 7.0.2)
actionmailer (= 7.0.1) actionmailer (= 7.0.2)
actionpack (= 7.0.1) actionpack (= 7.0.2)
actiontext (= 7.0.1) actiontext (= 7.0.2)
actionview (= 7.0.1) actionview (= 7.0.2)
activejob (= 7.0.1) activejob (= 7.0.2)
activemodel (= 7.0.1) activemodel (= 7.0.2)
activerecord (= 7.0.1) activerecord (= 7.0.2)
activestorage (= 7.0.1) activestorage (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.0.1) railties (= 7.0.2)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2) rails-html-sanitizer (1.4.2)
loofah (~> 2.3) loofah (~> 2.3)
railties (7.0.1) railties (7.0.2)
actionpack (= 7.0.1) actionpack (= 7.0.2)
activesupport (= 7.0.1) activesupport (= 7.0.2)
method_source method_source
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0) thor (~> 1.0)
@ -331,6 +330,8 @@ GEM
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
regexp_parser (2.2.0) regexp_parser (2.2.0)
request_store (1.5.1)
rack (>= 1.4)
responders (3.0.1) responders (3.0.1)
actionpack (>= 5.0) actionpack (>= 5.0)
railties (>= 5.0) railties (>= 5.0)
@ -455,7 +456,6 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
activeadmin! activeadmin!
arbre!
aws-sdk-s3 aws-sdk-s3
bootsnap (>= 1.4.4) bootsnap (>= 1.4.4)
byebug byebug
@ -463,7 +463,6 @@ DEPENDENCIES
capybara-lockstep capybara-lockstep
chartkick chartkick
devise! devise!
discard
dotenv-rails dotenv-rails
factory_bot_rails factory_bot_rails
govuk-components govuk-components
@ -473,6 +472,8 @@ DEPENDENCIES
listen (~> 3.3) listen (~> 3.3)
notifications-ruby-client notifications-ruby-client
overcommit (>= 0.37.0) overcommit (>= 0.37.0)
paper_trail
paper_trail-globalid
pg (~> 1.1) pg (~> 1.1)
postcodes_io postcodes_io
pry-byebug pry-byebug

15
app/concerns/admin/paper_trail.rb

@ -0,0 +1,15 @@
module Admin
module PaperTrail
extend ActiveSupport::Concern
included do
before_action :set_paper_trail_whodunnit
end
protected
def user_for_paper_trail
current_admin_user
end
end
end

8
app/controllers/application_controller.rb

@ -1,4 +1,6 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
before_action :set_paper_trail_whodunnit
def render_not_found def render_not_found
render "errors/not_found", status: :not_found render "errors/not_found", status: :not_found
end end
@ -6,4 +8,10 @@ class ApplicationController < ActionController::Base
def render_not_found_json(class_name, id) def render_not_found_json(class_name, id)
render json: { error: "#{class_name} #{id} not found" }, status: :not_found render json: { error: "#{class_name} #{id} not found" }, status: :not_found
end end
protected
def user_for_paper_trail
current_user
end
end end

2
app/controllers/case_logs_controller.rb

@ -60,7 +60,7 @@ class CaseLogsController < ApplicationController
def destroy def destroy
if @case_log if @case_log
if @case_log.discard if @case_log.delete
head :no_content head :no_content
else else
render json: { errors: @case_log.errors.messages }, status: :unprocessable_entity render json: { errors: @case_log.errors.messages }, status: :unprocessable_entity

4
app/controllers/form_controller.rb

@ -8,8 +8,12 @@ class FormController < ApplicationController
@page = @case_log.form.get_page(params[:case_log][:page]) @page = @case_log.form.get_page(params[:case_log][:page])
responses_for_page = responses_for_page(@page) responses_for_page = responses_for_page(@page)
if @case_log.update(responses_for_page) && @case_log.has_no_unresolved_soft_errors? if @case_log.update(responses_for_page) && @case_log.has_no_unresolved_soft_errors?
if @case_log.form.is_last_question?(@page, @case_log.form.subsection_for_page(@page), @case_log)
redirect_to(case_logs_path)
else
redirect_path = @case_log.form.next_page_redirect_path(@page, @case_log) redirect_path = @case_log.form.next_page_redirect_path(@page, @case_log)
redirect_to(send(redirect_path, @case_log)) redirect_to(send(redirect_path, @case_log))
end
else else
@subsection = @case_log.form.subsection_for_page(@page) @subsection = @case_log.form.subsection_for_page(@page)
render "form/page", status: :unprocessable_entity render "form/page", status: :unprocessable_entity

8
app/helpers/check_answers_helper.rb

@ -12,12 +12,6 @@ module CheckAnswersHelper
end end
def get_answer_label(question, case_log) def get_answer_label(question, case_log)
answer = question.prefix == "£" ? ActionController::Base.helpers.number_to_currency(question.answer_label(case_log), delimiter: ",", format: "%n") : question.answer_label(case_log) question.answer_label(case_log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
if answer.present?
[question.prefix, answer, question.suffix].join("")
else
"<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
end
end end
end end

2
app/models/admin_user.rb

@ -6,6 +6,8 @@ class AdminUser < ApplicationRecord
has_one_time_password(encrypted: true) has_one_time_password(encrypted: true)
has_paper_trail
validates :phone, presence: true, numericality: true validates :phone, presence: true, numericality: true
MFA_SMS_TEMPLATE_ID = "bf309d93-804e-4f95-b1f4-bd513c48ecb0".freeze MFA_SMS_TEMPLATE_ID = "bf309d93-804e-4f95-b1f4-bd513c48ecb0".freeze

3
app/models/bulk_upload.rb

@ -196,8 +196,7 @@ class BulkUpload
intermediate_rent_product_name: row[131], intermediate_rent_product_name: row[131],
# data_protection: row[132], # data_protection: row[132],
sale_or_letting: "letting", sale_or_letting: "letting",
gdpr_acceptance: 1, declaration: 1,
gdpr_declined: 0,
} }
end end

27
app/models/case_log.rb

@ -7,6 +7,7 @@ class CaseLogValidator < ActiveModel::Validator
include Validations::TenancyValidations include Validations::TenancyValidations
include Validations::DateValidations include Validations::DateValidations
include Validations::LocalAuthorityValidations include Validations::LocalAuthorityValidations
include Validations::SubmissionValidations
def validate(record) def validate(record)
validation_methods = public_methods.select { |method| method.starts_with?("validate_") } validation_methods = public_methods.select { |method| method.starts_with?("validate_") }
@ -29,11 +30,11 @@ private
end end
class CaseLog < ApplicationRecord class CaseLog < ApplicationRecord
include Discard::Model
include Validations::SoftValidations include Validations::SoftValidations
include Constants::CaseLog include Constants::CaseLog
include Constants::IncomeRanges include Constants::IncomeRanges
default_scope -> { kept }
has_paper_trail
validates_with CaseLogValidator validates_with CaseLogValidator
before_validation :process_postcode_changes!, if: :property_postcode_changed? before_validation :process_postcode_changes!, if: :property_postcode_changed?
@ -140,9 +141,9 @@ class CaseLog < ApplicationRecord
enum is_carehome: POLAR, _suffix: true enum is_carehome: POLAR, _suffix: true
enum nocharge: POLAR, _suffix: true enum nocharge: POLAR, _suffix: true
enum referral: REFERRAL, _suffix: true enum referral: REFERRAL, _suffix: true
enum declaration: POLAR, _suffix: true
AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze
OPTIONAL_FIELDS = %w[postcode_known la_known first_time_property_let_as_social_housing].freeze
def form def form
FormHandler.instance.get_form(form_name) FormHandler.instance.get_form(form_name)
@ -241,22 +242,7 @@ private
self.month = startdate.month self.month = startdate.month
self.year = startdate.year self.year = startdate.year
end end
case net_income_known self.incref = 1 if net_income_known == "Tenant prefers not to say"
when "Weekly"
self.incfreq = "Weekly"
self.incref = nil
when "Monthly"
self.incfreq = "Monthly"
self.incref = nil
when "Annually"
self.incfreq = "Yearly"
self.incref = nil
when "Tenant prefers not to say"
self.incref = 1
self.incfreq = nil
when "Don’t know"
self.incfreq = nil
end
self.hhmemb = other_hhmemb + 1 if other_hhmemb.present? self.hhmemb = other_hhmemb + 1 if other_hhmemb.present?
self.renttype = RENT_TYPE_MAPPING[rent_type] self.renttype = RENT_TYPE_MAPPING[rent_type]
self.lettype = "#{renttype} #{needstype} #{owning_organisation[:provider_type]}" if renttype.present? && needstype.present? && owning_organisation[:provider_type].present? self.lettype = "#{renttype} #{needstype} #{owning_organisation[:provider_type]}" if renttype.present? && needstype.present? && owning_organisation[:provider_type].present?
@ -266,6 +252,9 @@ private
self.tcharge = brent.to_f + scharge.to_f + pscharge.to_f + supcharg.to_f self.tcharge = brent.to_f + scharge.to_f + pscharge.to_f + supcharg.to_f
self.has_benefits = get_has_benefits self.has_benefits = get_has_benefits
self.nocharge = household_charge == "Yes" ? "No" : "Yes" self.nocharge = household_charge == "Yes" ? "No" : "Yes"
self.layear = "Less than 1 year" if renewal == "Yes"
self.underoccupation_benefitcap = "No" if renewal == "Yes" && year == 2021
self.homeless = "No" if renewal == "Yes"
end end
def process_postcode_changes! def process_postcode_changes!

11
app/models/constants/case_log.rb

@ -1071,11 +1071,10 @@ module Constants::CaseLog
}.freeze }.freeze
NET_INCOME_KNOWN = { NET_INCOME_KNOWN = {
"Weekly" => 0, "Yes" => 0,
"Monthly" => 1, "No" => 1,
"Annually" => 2, "Tenant prefers not to say" => 2,
"Tenant prefers not to say" => 3, "Don’t know" => 3,
"Don’t know" => 4,
}.freeze }.freeze
HAS_BENEFITS_OPTIONS = ["Housing benefit", HAS_BENEFITS_OPTIONS = ["Housing benefit",
@ -1124,4 +1123,6 @@ module Constants::CaseLog
"Sheltered accomodation", "Sheltered accomodation",
"Home Office Asylum Support", "Home Office Asylum Support",
"Other"].freeze "Other"].freeze
OPTIONAL_FIELDS = %w[postcode_known la_known first_time_property_let_as_social_housing tenant_code propcode].freeze
end end

11
app/models/form.rb

@ -24,6 +24,10 @@ class Form
pages.find { |p| p.id == id.to_s.underscore } pages.find { |p| p.id == id.to_s.underscore }
end end
def get_question(id)
questions.find { |q| q.id == id.to_s.underscore }
end
def subsection_for_page(page) def subsection_for_page(page)
subsections.find { |s| s.pages.find { |p| p.id == page.id } } subsections.find { |s| s.pages.find { |p| p.id == page.id } }
end end
@ -79,7 +83,7 @@ class Form
next_subsection_id_index = subsection_ids.index(subsection.id) + 1 next_subsection_id_index = subsection_ids.index(subsection.id) + 1
next_subsection = get_subsection(subsection_ids[next_subsection_id_index]) next_subsection = get_subsection(subsection_ids[next_subsection_id_index])
if next_subsection.id == "declaration" && case_log.status != "completed" if subsection.id == "declaration" && case_log.status != "completed"
next_subsection = get_subsection(subsection_ids[0]) next_subsection = get_subsection(subsection_ids[0])
end end
@ -122,4 +126,9 @@ class Form
def readonly_questions def readonly_questions
questions.select(&:read_only?) questions.select(&:read_only?)
end end
def is_last_question?(page, subsection, case_log)
subsection_ids = subsections.map(&:id)
subsection.id == subsection_ids[subsection_ids.length - 1] && next_page(page, case_log) == :check_answers
end
end end

35
app/models/form/question.rb

@ -38,7 +38,10 @@ class Form::Question
return checkbox_answer_label(case_log) if type == "checkbox" return checkbox_answer_label(case_log) if type == "checkbox"
return case_log[id]&.to_formatted_s(:govuk_date).to_s if type == "date" return case_log[id]&.to_formatted_s(:govuk_date).to_s if type == "date"
return case_log[id].to_s if case_log[id].present? answer = case_log[id].to_s if case_log[id].present?
answer_label = [prefix, format_value(answer), suffix_label(case_log)].join("") if answer
return answer_label if answer_label
has_inferred_check_answers_value?(case_log) ? inferred_check_answers_value["value"] : "" has_inferred_check_answers_value?(case_log) ? inferred_check_answers_value["value"] : ""
end end
@ -79,8 +82,6 @@ class Form::Question
end end
def completed?(case_log) def completed?(case_log)
# Special case as No is a valid answer but doesn't let you progress and use the service
return false if id == "gdpr_acceptance" && case_log[id] == "No"
return answer_options.keys.any? { |key| case_log[key] == "Yes" } if type == "checkbox" return answer_options.keys.any? { |key| case_log[key] == "Yes" } if type == "checkbox"
case_log[id].present? || !case_log.respond_to?(id.to_sym) || has_inferred_display_value?(case_log) case_log[id].present? || !case_log.respond_to?(id.to_sym) || has_inferred_display_value?(case_log)
@ -98,6 +99,28 @@ private
answer.join(", ") answer.join(", ")
end end
def format_value(answer_label)
prefix == "£" ? ActionController::Base.helpers.number_to_currency(answer_label, delimiter: ",", format: "%n") : answer_label
end
def suffix_label(case_log)
return "" unless suffix
return suffix if suffix.is_a?(String)
label = ""
suffix.each do |s|
condition = s["depends_on"]
next unless condition
answer = case_log.send(condition.keys.first)
if answer == condition.values.first
label = ANSWER_SUFFIX_LABELS.key?(answer.to_sym) ? ANSWER_SUFFIX_LABELS[answer.to_sym] : answer
end
end
label
end
def conditional_on def conditional_on
@conditional_on ||= form.conditional_question_conditions.select do |condition| @conditional_on ||= form.conditional_question_conditions.select do |condition|
condition[:to] == id condition[:to] == id
@ -120,4 +143,10 @@ private
def enabled_inferred_answers(inferred_answers, case_log) def enabled_inferred_answers(inferred_answers, case_log)
inferred_answers.filter { |_key, value| value.all? { |condition_key, condition_value| case_log[condition_key] == condition_value } } inferred_answers.filter { |_key, value| value.all? { |condition_key, condition_value| case_log[condition_key] == condition_value } }
end end
ANSWER_SUFFIX_LABELS = {
"Weekly": " every week",
"Monthly": " every month",
"Yearly": " every year",
}.freeze
end end

4
app/models/form/subsection.rb

@ -1,4 +1,6 @@
class Form::Subsection class Form::Subsection
include Constants::CaseLog
attr_accessor :id, :label, :section, :pages, :depends_on, :form attr_accessor :id, :label, :section, :pages, :depends_on, :form
def initialize(id, hsh, section) def initialize(id, hsh, section)
@ -30,7 +32,7 @@ class Form::Subsection
return :cannot_start_yet return :cannot_start_yet
end end
qs = applicable_questions(case_log) qs = applicable_questions(case_log).reject { |q| OPTIONAL_FIELDS.include?(q.id) }
return :not_started if qs.all? { |question| case_log[question.id].blank? || question.read_only? } return :not_started if qs.all? { |question| case_log[question.id].blank? || question.read_only? }
return :completed if qs.all? { |question| question.completed?(case_log) } return :completed if qs.all? { |question| question.completed?(case_log) }

3
app/models/organisation.rb

@ -3,7 +3,10 @@ class Organisation < ApplicationRecord
has_many :owned_case_logs, class_name: "CaseLog", foreign_key: "owning_organisation_id" has_many :owned_case_logs, class_name: "CaseLog", foreign_key: "owning_organisation_id"
has_many :managed_case_logs, class_name: "CaseLog", foreign_key: "managing_organisation_id" has_many :managed_case_logs, class_name: "CaseLog", foreign_key: "managing_organisation_id"
has_paper_trail
include Constants::Organisation include Constants::Organisation
enum provider_type: PROVIDER_TYPE enum provider_type: PROVIDER_TYPE
def case_logs def case_logs

2
app/models/user.rb

@ -10,6 +10,8 @@ class User < ApplicationRecord
has_many :owned_case_logs, through: :organisation has_many :owned_case_logs, through: :organisation
has_many :managed_case_logs, through: :organisation has_many :managed_case_logs, through: :organisation
has_paper_trail
enum role: ROLES enum role: ROLES
def case_logs def case_logs

12
app/models/validations/financial_validations.rb

@ -21,8 +21,7 @@ module Validations::FinancialValidations
end end
def validate_net_income(record) def validate_net_income(record)
return unless record.ecstat1 && record.weekly_net_income if record.ecstat1 && record.weekly_net_income
if record.weekly_net_income > record.applicable_income_range.hard_max if record.weekly_net_income > record.applicable_income_range.hard_max
record.errors.add :earnings, I18n.t("validations.financial.earnings.under_hard_max", hard_max: record.applicable_income_range.hard_max) record.errors.add :earnings, I18n.t("validations.financial.earnings.under_hard_max", hard_max: record.applicable_income_range.hard_max)
end end
@ -32,6 +31,15 @@ module Validations::FinancialValidations
end end
end end
if record.earnings.present? && record.incfreq.blank?
record.errors.add :incfreq, I18n.t("validations.financial.earnings.freq_missing")
end
if record.incfreq.present? && record.earnings.blank?
record.errors.add :earnings, I18n.t("validations.financial.earnings.earnings_missing")
end
end
def validate_tshortfall(record) def validate_tshortfall(record)
is_yes = record.hbrentshortfall == "Yes" is_yes = record.hbrentshortfall == "Yes"
hb_donotknow = record.hb == "Don’t know" hb_donotknow = record.hb == "Don’t know"

2
app/models/validations/household_validations.rb

@ -65,7 +65,7 @@ module Validations::HouseholdValidations
if all_options.count("Yes") > 1 if all_options.count("Yes") > 1
mobility_accessibility_options = [record.housingneeds_a, record.housingneeds_b, record.housingneeds_c] mobility_accessibility_options = [record.housingneeds_a, record.housingneeds_b, record.housingneeds_c]
unless all_options.count("Yes") == 2 && record.housingneeds_f == "Yes" && mobility_accessibility_options.any? { |x| x == "Yes" } unless all_options.count("Yes") == 2 && record.housingneeds_f == "Yes" && mobility_accessibility_options.any? { |x| x == "Yes" }
record.errors.add :housingneeds_a, I18n.t("validations.household.housingneeds_a.one_or_two_choices") record.errors.add :accessibility_requirements, I18n.t("validations.household.housingneeds_a.one_or_two_choices")
end end
end end
end end

10
app/models/validations/submission_validations.rb

@ -0,0 +1,10 @@
module Validations::SubmissionValidations
# Validations methods need to be called 'validate_<page_name>' to run on model save
# or 'validate_' to run on submit as well
def validate_declaration(record)
if record.declaration == "No"
record.errors.add :declaration, I18n.t("validations.declaration.missing")
end
end
end

2
app/views/case_logs/edit.html.erb

@ -21,12 +21,14 @@
<% next_incomplete_section = get_next_incomplete_section(@case_log) %> <% next_incomplete_section = get_next_incomplete_section(@case_log) %>
</p> </p>
<p> <p>
<% if next_incomplete_section.present? %>
<a class="app-section-skip-link" href="#<%= next_incomplete_section.id %>" <a class="app-section-skip-link" href="#<%= next_incomplete_section.id %>"
data-controller="tasklist" data-controller="tasklist"
data-action="tasklist#addHighlight" data-action="tasklist#addHighlight"
data-info=<%= next_incomplete_section.id %>> data-info=<%= next_incomplete_section.id %>>
Skip to next incomplete section: <%= next_incomplete_section.label %> Skip to next incomplete section: <%= next_incomplete_section.label %>
</a> </a>
<% end %>
</p> </p>
<%= render "tasklist" %> <%= render "tasklist" %>
</div> </div>

3
app/views/form/_checkbox_question.html.erb

@ -4,14 +4,17 @@
caption: caption(caption_text, page_header, conditional), caption: caption(caption_text, page_header, conditional),
legend: legend(question, page_header, conditional), legend: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe } do %> hint: { text: question.hint_text&.html_safe } do %>
<% after_divider = false %>
<% question.answer_options.map do |key, val| %> <% question.answer_options.map do |key, val| %>
<% if key.starts_with?("divider") %> <% if key.starts_with?("divider") %>
<% after_divider = true %>
<%= f.govuk_check_box_divider %> <%= f.govuk_check_box_divider %>
<% else %> <% else %>
<%= f.govuk_check_box question.id, key, <%= f.govuk_check_box question.id, key,
label: { text: val }, label: { text: val },
checked: @case_log[key] == "Yes", checked: @case_log[key] == "Yes",
exclusive: after_divider,
**stimulus_html_attributes(question) **stimulus_html_attributes(question)
%> %>
<% end %> <% end %>

2
app/views/form/_numeric_question.html.erb

@ -7,6 +7,6 @@
min: question.min, max: question.max, step: question.step, min: question.min, max: question.max, step: question.step,
width: question.width, :readonly => question.read_only?, width: question.width, :readonly => question.read_only?,
prefix_text: question.prefix.to_s, prefix_text: question.prefix.to_s,
suffix_text: question.suffix.to_s, suffix_text: question.suffix.is_a?(String) ? question.suffix : nil,
**stimulus_html_attributes(question) **stimulus_html_attributes(question)
%> %>

11
app/views/form/page.html.erb

@ -14,8 +14,12 @@
<div data-controller="govukfrontend"></div> <div data-controller="govukfrontend"></div>
<%= turbo_frame_tag "case_log_form", target: "_top" do %> <%= turbo_frame_tag "case_log_form", target: "_top" do %>
<%= form_with model: @case_log, url: form_case_log_path(@case_log), method: "post" do |f| %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop"> <div class="govuk-grid-column-two-thirds-from-desktop">
<% remove_other_page_errors(@case_log, @page) %>
<%= f.govuk_error_summary %>
<% if @page.header.present? %> <% if @page.header.present? %>
<h1 class="govuk-heading-l"> <h1 class="govuk-heading-l">
<% if !@page.hide_subsection_label %> <% if !@page.hide_subsection_label %>
@ -29,9 +33,6 @@
<p class="govuk-body govuk-body-m"><%= @page.description.html_safe %></p> <p class="govuk-body govuk-body-m"><%= @page.description.html_safe %></p>
<% end %> <% end %>
<%= form_with model: @case_log, url: form_case_log_path(@case_log), method: "post" do |f| %>
<% remove_other_page_errors(@case_log, @page) %>
<%= f.govuk_error_summary %>
<% @page.non_conditional_questions.map do |question| %> <% @page.non_conditional_questions.map do |question| %>
<div id=<%= question.id + "_div " %><%= display_question_key_div(@page, question) %> > <div id=<%= question.id + "_div " %><%= display_question_key_div(@page, question) %> >
<% if question.read_only? %> <% if question.read_only? %>
@ -46,8 +47,12 @@
<% end %> <% end %>
<%= f.hidden_field :page, value: @page.id %> <%= f.hidden_field :page, value: @page.id %>
<% if @case_log.form.is_last_question?(@page, @subsection, @case_log) %>
<%= f.govuk_submit "Submit lettings log" %>
<%else %>
<%= f.govuk_submit "Save and continue" %> <%= f.govuk_submit "Save and continue" %>
<%end %> <%end %>
</div> </div>
</div> </div>
<% end %> <% end %>
<% end %>

146
config/forms/2021_2022.json

@ -9,30 +9,6 @@
"setup": { "setup": {
"label": "Set up your lettings log", "label": "Set up your lettings log",
"pages": { "pages": {
"gdpr_acceptance": {
"header": "",
"description": "",
"questions": {
"gdpr_acceptance": {
"check_answer_label": "Privacy notice seen",
"header": "Has the tenant or buyer seen the Department for Levelling Up, Housing and Communities (DLUHC) privacy notice?",
"hint_text": "You must <a class=\"govuk-link\" href=\"/files/privacy-notice.pdf\">show the privacy notice</a> to the tenant or buyer before you can use this service.",
"type": "radio",
"answer_options": {
"0": "Yes",
"1": "No"
}
}
}
},
"gdpr_declined": {
"hide_subsection_label": true,
"header": "You cannot use this service",
"hint_text": "",
"description": "We cannot accept data about a tenant or buyer unless they’ve seen the <a class=\"govuk-link\" href=\"/files/privacy-notice.pdf\">DLUHC privacy notice</a>.<br /><br /><a class=\"govuk-link\" href=\"/logs\">Go to your logs</a>",
"questions": {},
"depends_on": [{ "gdpr_acceptance": "No" }]
},
"organisation_details": { "organisation_details": {
"header": "Organisation details", "header": "Organisation details",
"description": "", "description": "",
@ -57,8 +33,7 @@
"1": "B" "1": "B"
} }
} }
}, }
"depends_on": [{ "gdpr_acceptance": "Yes" }]
}, },
"renewal": { "renewal": {
"header": "", "header": "",
@ -74,10 +49,7 @@
"0": "No" "0": "No"
} }
} }
}, }
"depends_on": [{
"gdpr_acceptance": "Yes"
}]
}, },
"startdate": { "startdate": {
"header": "", "header": "",
@ -89,10 +61,7 @@
"hint_text": "For example, 27 3 2021.", "hint_text": "For example, 27 3 2021.",
"type": "date" "type": "date"
} }
}, }
"depends_on": [{
"gdpr_acceptance": "Yes"
}]
}, },
"about_this_letting": { "about_this_letting": {
"header": "Tell us about this letting", "header": "Tell us about this letting",
@ -132,10 +101,7 @@
"0": "Supported housing" "0": "Supported housing"
} }
} }
}, }
"depends_on": [{
"gdpr_acceptance": "Yes"
}]
}, },
"tenant_code": { "tenant_code": {
"header": "", "header": "",
@ -148,10 +114,7 @@
"type": "text", "type": "text",
"width": 10 "width": 10
} }
}, }
"depends_on": [{
"gdpr_acceptance": "Yes"
}]
}, },
"property_reference": { "property_reference": {
"header": "", "header": "",
@ -164,8 +127,7 @@
"type": "text", "type": "text",
"width": 10 "width": 10
} }
}, }
"depends_on": [{ "gdpr_acceptance": "Yes" }]
} }
} }
} }
@ -2330,95 +2292,56 @@
"depends_on": [{ "setup": "completed" }], "depends_on": [{ "setup": "completed" }],
"pages": { "pages": {
"net_income_known": { "net_income_known": {
"header": "Household’s combined income", "header": "",
"description": "", "description": "",
"questions": { "questions": {
"net_income_known": { "net_income_known": {
"check_answer_label": "How often household receives income", "check_answer_label": "Do you know the household’s combined income?",
"header": "How often does the household receive income?", "header": "Do you know the household’s combined income?",
"guidance_partial": "what_counts_as_income",
"hint_text": "", "hint_text": "",
"type": "radio", "type": "radio",
"answer_options": { "answer_options": {
"0": "Weekly", "0": "Yes",
"1": "Monthly", "1": "No",
"2": "Annually",
"divider_a": true, "divider_a": true,
"3": "Don’t know", "2": "Don’t know",
"4": "Tenant prefers not to say" "3": "Tenant prefers not to say"
} }
} }
} }
}, },
"weekly_net_income": { "net_income": {
"depends_on": [{ "net_income_known": "Weekly" }], "depends_on": [{ "net_income_known": "Yes" }],
"header": "", "header": "Household’s combined income after tax",
"description": "", "description": "",
"questions": { "questions": {
"earnings": { "earnings": {
"check_answer_label": "Total household income", "check_answer_label": "Total household income",
"header": "How much income does the household have in total every week?", "header": "How much income does the household have in total?",
"guidance_partial": "what_counts_as_income",
"hint_text": "", "hint_text": "",
"type": "numeric", "type": "numeric",
"min": 0, "min": 0,
"step": "1", "step": "1",
"width": 5, "width": 5,
"prefix": "£", "prefix": "£",
"suffix": " every week" "suffix": [
} { "label": "every week", "depends_on" : { "incfreq": "Weekly" } },
}, { "label": "every month", "depends_on" : { "incfreq": "Monthly" } },
"soft_validations": { { "label": "every month", "depends_on" : { "incfreq": "Yearly" } }
"override_net_income_validation": { ]
"check_answer_label": "Net income confirmed?",
"type": "validation_override",
"answer_options": {
"override_net_income_validation": "Yes"
}
}
}
}, },
"monthly_net_income": { "incfreq": {
"depends_on": [{ "net_income_known": "Monthly" }], "check_answer_label": "How often does the household receive this amount?",
"header": "", "header": "How often does the household receive this amount?",
"description": "",
"questions": {
"earnings": {
"check_answer_label": "Total household income",
"header": "How much income does the household have in total every month?",
"hint_text": "", "hint_text": "",
"type": "numeric", "type": "radio",
"min": 0,
"step": "1",
"width": 5,
"prefix": "£",
"suffix": " every month"
}
},
"soft_validations": {
"override_net_income_validation": {
"check_answer_label": "Net income confirmed?",
"type": "validation_override",
"answer_options": { "answer_options": {
"override_net_income_validation": "Yes" "0": "Weekly",
} "1": "Monthly",
} "2": "Yearly"
}
}, },
"yearly_net_income": { "hidden_in_check_answers": true
"depends_on": [{ "net_income_known": "Annually" }],
"header": "",
"description": "",
"questions": {
"earnings": {
"check_answer_label": "Total household income",
"header": "How much income does the household have in total every year?",
"hint_text": "",
"type": "numeric",
"min": 0,
"step": "1",
"width": 5,
"prefix": "£",
"suffix": " every year"
} }
}, },
"soft_validations": { "soft_validations": {
@ -3226,9 +3149,12 @@
"questions": { "questions": {
"declaration": { "declaration": {
"check_answer_label": "", "check_answer_label": "",
"header": "What is the tenant code?", "header": "Submit your lettings log ",
"hint_text": "", "hint_text": "",
"type": "text" "type": "checkbox",
"answer_options": {
"declaration": "The tenant has seen the Department for Levelling Up, Housing & Communities (DLUHC) privacy notice"
}
} }
} }
} }

4
config/initializers/active_admin.rb

@ -340,3 +340,7 @@ end
Rails.application.config.after_initialize do Rails.application.config.after_initialize do
ActiveAdmin.application.stylesheets.delete("active_admin/print.css") ActiveAdmin.application.stylesheets.delete("active_admin/print.css")
end end
Rails.application.config.after_initialize do
ActiveAdmin::BaseController.include Admin::PaperTrail
end

2
config/initializers/devise.rb

@ -189,7 +189,7 @@ Devise.setup do |config|
# ==> Configuration for :timeoutable # ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. After this # The time you want to timeout the user session without activity. After this
# time the user will be asked for credentials again. Default is 30 minutes. # time the user will be asked for credentials again. Default is 30 minutes.
# config.timeout_in = 30.minutes config.timeout_in = 1.day
# ==> Configuration for :lockable # ==> Configuration for :lockable
# Defines which strategy will be used to lock an account. # Defines which strategy will be used to lock an account.

5
config/locales/en.yml

@ -68,6 +68,8 @@ en:
earnings: earnings:
under_hard_max: "Net income cannot be greater than %{hard_max} given the tenant’s working situation" under_hard_max: "Net income cannot be greater than %{hard_max} given the tenant’s working situation"
over_hard_min: "Net income cannot be less than %{hard_min} given the tenant’s working situation" over_hard_min: "Net income cannot be less than %{hard_min} given the tenant’s working situation"
freq_missing: "Select how often the household receives income"
earnings_missing: "Enter how much income the household has in total"
household: household:
reasonpref: reasonpref:
@ -115,6 +117,9 @@ en:
referral: referral:
rsnvac_non_temp: "Answer cannot be this source of referral as you already told us this is a re-let to tenant who occupied the same property as temporary accommodation" rsnvac_non_temp: "Answer cannot be this source of referral as you already told us this is a re-let to tenant who occupied the same property as temporary accommodation"
declaration:
missing: "You must show the DLUHC privacy notice to the tenant before you can submit this log."
soft_validations: soft_validations:
net_income: net_income:
hint_text: "This is based on the tenant’s work situation: %{ecstat1}" hint_text: "This is based on the tenant’s work situation: %{ecstat1}"

7
db/migrate/20220207091117_add_declaration.rb

@ -0,0 +1,7 @@
class AddDeclaration < ActiveRecord::Migration[7.0]
def change
change_table :case_logs, bulk: true do |t|
t.column :declaration, :integer
end
end
end

22
db/migrate/20220207151239_create_versions.rb

@ -0,0 +1,22 @@
# This migration creates the `versions` table, the only schema PT requires.
# All other migrations PT provides are optional.
class CreateVersions < ActiveRecord::Migration[7.0]
# The largest text column available in all supported RDBMS is
# 1024^3 - 1 bytes, roughly one gibibyte. We specify a size
# so that MySQL will use `longtext` instead of `text`. Otherwise,
# when serializing very large objects, `text` might not be big enough.
TEXT_BYTES = 1_073_741_823
def change
create_table :versions do |t|
t.string :item_type, null: false, limit: 191
t.bigint :item_id, null: false
t.string :event, null: false
t.string :whodunnit
t.text :object, limit: TEXT_BYTES
t.datetime :created_at
end
add_index :versions, %i[item_type item_id]
end
end

11
db/migrate/20220207151312_remove_discarded_at_from_case_logs.rb

@ -0,0 +1,11 @@
class RemoveDiscardedAtFromCaseLogs < ActiveRecord::Migration[7.0]
def up
remove_index :case_logs, :discarded_at
remove_column :case_logs, :discarded_at
end
def down
add_column :case_logs, :discarded_at, :datetime
add_index :case_logs, :discarded_at
end
end

15
db/migrate/20220208101235_remove_gdpr_fields.rb

@ -0,0 +1,15 @@
class RemoveGdprFields < ActiveRecord::Migration[7.0]
def up
change_table :case_logs, bulk: true do |t|
t.remove :gdpr_declined
t.remove :gdpr_acceptance
end
end
def down
change_table :case_logs, bulk: true do |t|
t.column :gdpr_declined, :string
t.column :gdpr_acceptance, :string
end
end
end

20
db/schema.rb

@ -10,7 +10,8 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_02_07_1123100) do ActiveRecord::Schema.define(version: 202202071123100) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -92,6 +93,7 @@ ActiveRecord::Schema.define(version: 2022_02_07_1123100) do
t.integer "beds" t.integer "beds"
t.integer "offered" t.integer "offered"
t.integer "wchair" t.integer "wchair"
t.integer "earnings"
t.integer "incfreq" t.integer "incfreq"
t.integer "benefits" t.integer "benefits"
t.integer "period" t.integer "period"
@ -126,11 +128,8 @@ ActiveRecord::Schema.define(version: 2022_02_07_1123100) do
t.integer "rp_medwel" t.integer "rp_medwel"
t.integer "rp_hardship" t.integer "rp_hardship"
t.integer "rp_dontknow" t.integer "rp_dontknow"
t.datetime "discarded_at"
t.string "tenancyother" t.string "tenancyother"
t.integer "override_net_income_validation" t.integer "override_net_income_validation"
t.string "gdpr_acceptance"
t.string "gdpr_declined"
t.string "property_owner_organisation" t.string "property_owner_organisation"
t.string "property_manager_organisation" t.string "property_manager_organisation"
t.string "sale_or_letting" t.string "sale_or_letting"
@ -183,7 +182,6 @@ ActiveRecord::Schema.define(version: 2022_02_07_1123100) do
t.integer "is_carehome" t.integer "is_carehome"
t.integer "letting_in_sheltered_accomodation" t.integer "letting_in_sheltered_accomodation"
t.integer "household_charge" t.integer "household_charge"
t.integer "earnings"
t.integer "referral" t.integer "referral"
t.decimal "brent", precision: 10, scale: 2 t.decimal "brent", precision: 10, scale: 2
t.decimal "scharge", precision: 10, scale: 2 t.decimal "scharge", precision: 10, scale: 2
@ -192,7 +190,7 @@ ActiveRecord::Schema.define(version: 2022_02_07_1123100) do
t.decimal "tcharge", precision: 10, scale: 2 t.decimal "tcharge", precision: 10, scale: 2
t.decimal "tshortfall", precision: 10, scale: 2 t.decimal "tshortfall", precision: 10, scale: 2
t.decimal "chcharge", precision: 10, scale: 2 t.decimal "chcharge", precision: 10, scale: 2
t.index ["discarded_at"], name: "index_case_logs_on_discarded_at" t.integer "declaration"
t.index ["managing_organisation_id"], name: "index_case_logs_on_managing_organisation_id" t.index ["managing_organisation_id"], name: "index_case_logs_on_managing_organisation_id"
t.index ["owning_organisation_id"], name: "index_case_logs_on_owning_organisation_id" t.index ["owning_organisation_id"], name: "index_case_logs_on_owning_organisation_id"
end end
@ -252,4 +250,14 @@ ActiveRecord::Schema.define(version: 2022_02_07_1123100) do
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end end
create_table "versions", force: :cascade do |t|
t.string "item_type", limit: 191, null: false
t.bigint "item_id", null: false
t.string "event", null: false
t.string "whodunnit"
t.text "object"
t.datetime "created_at", precision: 6
t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id"
end
end end

27
infrastructure_setup.md

@ -13,9 +13,34 @@
`cf create-service aws-s3-bucket default dluhc-core-staging-export-bucket` `cf create-service aws-s3-bucket default dluhc-core-staging-export-bucket`
4. Deploy manifest:\
`cf push dluhc-core-staging --strategy rolling`
5. Bind S3 services to app:\
`cf bind-service dluhc-core-staging dluhc-core-staging-import-bucket -c '{"permissions": "read-only"}'` `cf bind-service dluhc-core-staging dluhc-core-staging-import-bucket -c '{"permissions": "read-only"}'`
`cf bind-service dluhc-core-staging dluhc-core-staging-export-bucket -c '{"permissions": "read-write"}'` `cf bind-service dluhc-core-staging dluhc-core-staging-export-bucket -c '{"permissions": "read-write"}'`
# Production
1. Login:\
`cf login -a api.london.cloud.service.gov.uk -u <your_username>`
2. Set your deployment target (production):\
`cf target -o dluhc-core -s production`
3. Create required Postgres and S3 bucket backing services (this will take ~15 mins to finish creating):\
`cf create-service postgres small-ha-13 dluhc-core-production-postgres`
`cf create-service aws-s3-bucket default dluhc-core-production-import-bucket`
`cf create-service aws-s3-bucket default dluhc-core-production-export-bucket`
4. Deploy manifest:\ 4. Deploy manifest:\
`cf push dluhc-core-staging --strategy rolling` `cf push dluhc-core-production --strategy rolling`
5. Bind S3 services to app:\
`cf bind-service dluhc-core-production dluhc-core-production-import-bucket -c '{"permissions": "read-only"}'`
`cf bind-service dluhc-core-production dluhc-core-production-export-bucket -c '{"permissions": "read-write"}'`

5
manifest.yml

@ -20,6 +20,11 @@ applications:
- name: dluhc-core-production - name: dluhc-core-production
<<: *defaults <<: *defaults
processes:
- type: web
command: bundle exec rake cf:on_first_instance db:migrate && bin/rails server
instances: 4
memory: 1G
env: env:
RAILS_ENV: production RAILS_ENV: production
host: submit-social-housing-lettings-sales-data host: submit-social-housing-lettings-sales-data

26
spec/controllers/admin/admin_users_controller_spec.rb

@ -6,8 +6,11 @@ describe Admin::AdminUsersController, type: :controller do
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:resource_title) { "Admin Users" } let(:resource_title) { "Admin Users" }
let(:valid_session) { {} } let(:valid_session) { {} }
let(:signed_in_admin_user) { FactoryBot.create(:admin_user) }
login_admin_user before do
sign_in signed_in_admin_user
end
describe "Get admin users" do describe "Get admin users" do
before do before do
@ -27,22 +30,30 @@ describe Admin::AdminUsersController, type: :controller do
it "creates a new admin user" do it "creates a new admin user" do
expect { post :create, session: valid_session, params: params }.to change(AdminUser, :count).by(1) expect { post :create, session: valid_session, params: params }.to change(AdminUser, :count).by(1)
end end
it "tracks who created the record" do
post :create, session: valid_session, params: params
created_id = response.location.match(/[0-9]+/)[0]
whodunnit_actor = AdminUser.find_by(id: created_id).versions.last.actor
expect(whodunnit_actor).to be_a(AdminUser)
expect(whodunnit_actor.id).to eq(signed_in_admin_user.id)
end
end end
describe "Update admin users" do describe "Update admin users" do
context "when editing the form" do context "when viewing the form" do
before do before do
get :edit, session: valid_session, params: { id: AdminUser.first.id } get :edit, session: valid_session, params: { id: AdminUser.first.id }
end end
it "shows an edit form" do it "shows the correct fields" do
expect(page).to have_field("admin_user_email") expect(page).to have_field("admin_user_email")
expect(page).to have_field("admin_user_password") expect(page).to have_field("admin_user_password")
expect(page).to have_field("admin_user_password_confirmation") expect(page).to have_field("admin_user_password_confirmation")
end end
end end
context "when updating the form" do context "when updating an admin user" do
let(:admin_user) { FactoryBot.create(:admin_user) } let(:admin_user) { FactoryBot.create(:admin_user) }
let(:email) { "new_email@example.com" } let(:email) { "new_email@example.com" }
let(:params) { { id: admin_user.id, admin_user: { email: email } } } let(:params) { { id: admin_user.id, admin_user: { email: email } } }
@ -55,6 +66,13 @@ describe Admin::AdminUsersController, type: :controller do
admin_user.reload admin_user.reload
expect(admin_user.email).to eq(email) expect(admin_user.email).to eq(email)
end end
it "tracks who updated the record" do
admin_user.reload
whodunnit_actor = admin_user.versions.last.actor
expect(whodunnit_actor).to be_a(AdminUser)
expect(whodunnit_actor.id).to eq(signed_in_admin_user.id)
end
end end
end end
end end

47
spec/controllers/admin/case_logs_controller_spec.rb

@ -6,8 +6,7 @@ describe Admin::CaseLogsController, type: :controller do
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:resource_title) { "Logs" } let(:resource_title) { "Logs" }
let(:valid_session) { {} } let(:valid_session) { {} }
let(:admin_user) { FactoryBot.create(:admin_user) }
login_admin_user
describe "Get case logs" do describe "Get case logs" do
let!(:case_log) { FactoryBot.create(:case_log, :in_progress) } let!(:case_log) { FactoryBot.create(:case_log, :in_progress) }
@ -39,5 +38,49 @@ describe Admin::CaseLogsController, type: :controller do
it "creates a new case log" do it "creates a new case log" do
expect { post :create, session: valid_session, params: params }.to change(CaseLog, :count).by(1) expect { post :create, session: valid_session, params: params }.to change(CaseLog, :count).by(1)
end end
it "tracks who created the record" do
post :create, session: valid_session, params: params
created_id = response.location.match(/[0-9]+/)[0]
whodunnit_actor = CaseLog.find_by(id: created_id).versions.last.actor
expect(whodunnit_actor).to be_a(AdminUser)
expect(whodunnit_actor.id).to eq(admin_user.id)
end
end
describe "Update case log" do
let!(:case_log) { FactoryBot.create(:case_log, :in_progress) }
context "when viewing the edit form" do
before do
get :edit, session: valid_session, params: { id: case_log.id }
end
it "has the correct fields" do
expect(page).to have_field("case_log_age1")
expect(page).to have_field("case_log_tenant_code")
end
end
context "when updating the case_log" do
let(:tenant_code) { "New tenant code by Admin" }
let(:params) { { id: case_log.id, case_log: { tenant_code: tenant_code } } }
before do
patch :update, session: valid_session, params: params
end
it "updates the case log" do
case_log.reload
expect(case_log.tenant_code).to eq(tenant_code)
end
it "tracks who updated the record" do
case_log.reload
whodunnit_actor = case_log.versions.last.actor
expect(whodunnit_actor).to be_a(AdminUser)
expect(whodunnit_actor.id).to eq(admin_user.id)
end
end
end end
end end

3
spec/controllers/admin/dashboard_controller_spec.rb

@ -6,8 +6,7 @@ describe Admin::DashboardController, type: :controller do
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:resource_title) { "Dashboard" } let(:resource_title) { "Dashboard" }
let(:valid_session) { {} } let(:valid_session) { {} }
let(:admin_user) { FactoryBot.create(:admin_user) }
login_admin_user
describe "Get case logs" do describe "Get case logs" do
before do before do

40
spec/controllers/admin/organisations_controller_spec.rb

@ -7,8 +7,11 @@ describe Admin::OrganisationsController, type: :controller do
let(:resource_title) { "Organisations" } let(:resource_title) { "Organisations" }
let(:valid_session) { {} } let(:valid_session) { {} }
let!(:organisation) { FactoryBot.create(:organisation) } let!(:organisation) { FactoryBot.create(:organisation) }
let!(:admin_user) { FactoryBot.create(:admin_user) }
login_admin_user before do
sign_in admin_user
end
describe "Organisations" do describe "Organisations" do
before do before do
@ -22,23 +25,54 @@ describe Admin::OrganisationsController, type: :controller do
end end
end end
describe "Create admin users" do describe "Create organisation" do
let(:params) { { organisation: { name: "DLUHC" } } } let(:params) { { organisation: { name: "DLUHC" } } }
it "creates a organisation" do it "creates a organisation" do
expect { post :create, session: valid_session, params: params }.to change(Organisation, :count).by(1) expect { post :create, session: valid_session, params: params }.to change(Organisation, :count).by(1)
end end
it "tracks who created the record" do
post :create, session: valid_session, params: params
created_id = response.location.match(/[0-9]+/)[0]
whodunnit_actor = Organisation.find_by(id: created_id).versions.last.actor
expect(whodunnit_actor).to be_a(AdminUser)
expect(whodunnit_actor.id).to eq(admin_user.id)
end
end end
describe "Update organisation" do describe "Update organisation" do
context "when viewing the edit form" do
before do before do
get :edit, session: valid_session, params: { id: organisation.id } get :edit, session: valid_session, params: { id: organisation.id }
end end
it "creates a new admin users" do it "has the correct fields" do
expect(page).to have_field("organisation_name") expect(page).to have_field("organisation_name")
expect(page).to have_field("organisation_provider_type") expect(page).to have_field("organisation_provider_type")
expect(page).to have_field("organisation_phone") expect(page).to have_field("organisation_phone")
end end
end end
context "when updating the organisation" do
let(:name) { "New Org Name by Admin" }
let(:params) { { id: organisation.id, organisation: { name: name } } }
before do
patch :update, session: valid_session, params: params
end
it "updates the organisation" do
organisation.reload
expect(organisation.name).to eq(name)
end
it "tracks who updated the record" do
organisation.reload
whodunnit_actor = organisation.versions.last.actor
expect(whodunnit_actor).to be_a(AdminUser)
expect(whodunnit_actor.id).to eq(admin_user.id)
end
end
end
end end

24
spec/controllers/admin/users_controller_spec.rb

@ -8,8 +8,11 @@ describe Admin::UsersController, type: :controller do
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:resource_title) { "Users" } let(:resource_title) { "Users" }
let(:valid_session) { {} } let(:valid_session) { {} }
let!(:admin_user) { FactoryBot.create(:admin_user) }
login_admin_user before do
sign_in admin_user
end
describe "Get users" do describe "Get users" do
before do before do
@ -39,15 +42,23 @@ describe Admin::UsersController, type: :controller do
it "creates a new user" do it "creates a new user" do
expect { post :create, session: valid_session, params: params }.to change(User, :count).by(1) expect { post :create, session: valid_session, params: params }.to change(User, :count).by(1)
end end
it "tracks who created the record" do
post :create, session: valid_session, params: params
created_id = response.location.match(/[0-9]+/)[0]
whodunnit_actor = User.find_by(id: created_id).versions.last.actor
expect(whodunnit_actor).to be_a(AdminUser)
expect(whodunnit_actor.id).to eq(admin_user.id)
end
end end
describe "Update users" do describe "Update users" do
context "when updating the form" do context "when viewing the edit form" do
before do before do
get :edit, session: valid_session, params: { id: user.id } get :edit, session: valid_session, params: { id: user.id }
end end
it "shows an edit form" do it "has the correct fields" do
expect(page).to have_field("user_email") expect(page).to have_field("user_email")
expect(page).to have_field("user_name") expect(page).to have_field("user_name")
expect(page).to have_field("user_organisation_id") expect(page).to have_field("user_organisation_id")
@ -69,6 +80,13 @@ describe Admin::UsersController, type: :controller do
user.reload user.reload
expect(user.name).to eq(name) expect(user.name).to eq(name)
end end
it "tracks who updated the record" do
user.reload
whodunnit_actor = user.versions.last.actor
expect(whodunnit_actor).to be_a(AdminUser)
expect(whodunnit_actor.id).to eq(admin_user.id)
end
end end
end end
end end

8
spec/factories/case_log.rb

@ -3,7 +3,6 @@ FactoryBot.define do
owning_organisation { FactoryBot.create(:organisation) } owning_organisation { FactoryBot.create(:organisation) }
managing_organisation { FactoryBot.create(:organisation) } managing_organisation { FactoryBot.create(:organisation) }
trait :about_completed do trait :about_completed do
gdpr_acceptance { "Yes" }
renewal { "No" } renewal { "No" }
needstype { 1 } needstype { 1 }
rent_type { 1 } rent_type { 1 }
@ -70,6 +69,7 @@ FactoryBot.define do
offered { 2 } offered { 2 }
wchair { "Yes" } wchair { "Yes" }
earnings { 68 } earnings { 68 }
incfreq { "Weekly" }
benefits { "Some" } benefits { "Some" }
period { "Every 2 weeks" } period { "Every 2 weeks" }
brent { 200 } brent { 200 }
@ -108,12 +108,9 @@ FactoryBot.define do
rp_medwel { "No" } rp_medwel { "No" }
rp_hardship { "No" } rp_hardship { "No" }
rp_dontknow { "No" } rp_dontknow { "No" }
discarded_at { nil }
tenancyother { nil } tenancyother { nil }
override_net_income_validation { nil } override_net_income_validation { nil }
net_income_known { "Weekly" } net_income_known { "Yes" }
gdpr_acceptance { "Yes" }
gdpr_declined { "No" }
property_owner_organisation { "Test" } property_owner_organisation { "Test" }
property_manager_organisation { "Test" } property_manager_organisation { "Test" }
renewal { 1 } renewal { 1 }
@ -152,6 +149,7 @@ FactoryBot.define do
chcharge { 7 } chcharge { 7 }
letting_in_sheltered_accomodation { "No" } letting_in_sheltered_accomodation { "No" }
la_known { "Yes" } la_known { "Yes" }
declaration { "Yes" }
end end
created_at { Time.zone.now } created_at { Time.zone.now }
updated_at { Time.zone.now } updated_at { Time.zone.now }

6
spec/features/form/check_answers_page_spec.rb

@ -36,7 +36,7 @@ RSpec.describe "Form Check Answers Page" do
end end
context "when the user needs to check their answers for a subsection" do context "when the user needs to check their answers for a subsection" do
let(:last_question_for_subsection) { "household-number-of-other-members" } let(:last_question_for_subsection) { "propcode" }
it "can be visited by URL" do it "can be visited by URL" do
visit("/logs/#{id}/#{subsection}/check-answers") visit("/logs/#{id}/#{subsection}/check-answers")
@ -44,7 +44,7 @@ RSpec.describe "Form Check Answers Page" do
end end
it "redirects to the check answers page when answering the last question and clicking save and continue" do it "redirects to the check answers page when answering the last question and clicking save and continue" do
fill_in_number_question(id, "other_hhmemb", 0, last_question_for_subsection) fill_in_number_question(id, "propcode", 0, last_question_for_subsection)
expect(page).to have_current_path("/logs/#{id}/#{subsection}/check-answers") expect(page).to have_current_path("/logs/#{id}/#{subsection}/check-answers")
end end
@ -207,7 +207,7 @@ RSpec.describe "Form Check Answers Page" do
end end
it "they can click a button to cycle around to the next incomplete section" do it "they can click a button to cycle around to the next incomplete section" do
visit("/logs/#{cycle_sections_case_log.id}/setup/check-answers") visit("/logs/#{cycle_sections_case_log.id}/declaration/check-answers")
click_link("Save and go to next incomplete section") click_link("Save and go to next incomplete section")
expect(page).to have_current_path("/logs/#{cycle_sections_case_log.id}/tenant-code") expect(page).to have_current_path("/logs/#{cycle_sections_case_log.id}/tenant-code")
end end

41
spec/features/form/checkboxes_spec.rb

@ -0,0 +1,41 @@
require "rails_helper"
require_relative "helpers"
require_relative "../../request_helper"
RSpec.describe "Checkboxes" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
FactoryBot.create(
:case_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
)
end
let(:id) { case_log.id }
before do
RequestHelper.stub_http_requests
sign_in user
end
context "when exclusive checkbox is selected", js: true do
it "deselects all other checkboxes" do
visit("/logs/#{id}/accessibility-requirements")
page.check("case-log-accessibility-requirements-housingneeds-a-field", allow_label_click: true)
click_button("Save and continue")
case_log.reload
expect(case_log["housingneeds_a"]).to eq("Yes")
visit("/logs/#{id}/accessibility-requirements")
page.check("case-log-accessibility-requirements-housingneeds-h-field", allow_label_click: true)
click_button("Save and continue")
case_log.reload
expect(case_log["housingneeds_a"]).to eq("No")
expect(case_log["housingneeds_h"]).to eq("Yes")
end
end
end

4
spec/features/form/tasklist_page_spec.rb

@ -33,12 +33,12 @@ RSpec.describe "Task List" do
it "shows the number of completed sections if no sections are completed" do it "shows the number of completed sections if no sections are completed" do
visit("/logs/#{empty_case_log.id}") visit("/logs/#{empty_case_log.id}")
expect(page).to have_content("You have completed 0 of 10 sections.") expect(page).to have_content("You have completed 0 of 9 sections.")
end end
it "shows the number of completed sections if one section is completed" do it "shows the number of completed sections if one section is completed" do
answer_all_questions_in_income_subsection(empty_case_log) answer_all_questions_in_income_subsection(empty_case_log)
visit("/logs/#{empty_case_log.id}") visit("/logs/#{empty_case_log.id}")
expect(page).to have_content("You have completed 1 of 10 sections.") expect(page).to have_content("You have completed 1 of 9 sections.")
end end
end end

31
spec/features/form/validations_spec.rb

@ -23,6 +23,16 @@ RSpec.describe "validations" do
managing_organisation: user.organisation, managing_organisation: user.organisation,
) )
end end
let(:completed_without_declaration) do
FactoryBot.create(
:case_log,
:completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
status: 1,
declaration: nil,
)
end
let(:id) { case_log.id } let(:id) { case_log.id }
describe "Question validation" do describe "Question validation" do
@ -160,4 +170,25 @@ RSpec.describe "validations" do
end end
end end
end end
describe "Submission validation" do
context "when tenant has not seen the privacy notice" do
it "shows a warning" do
visit("/logs/#{completed_without_declaration.id}/declaration")
expect(page).to have_current_path("/logs/#{completed_without_declaration.id}/declaration")
click_button("Submit lettings log")
expect(page).to have_content("You must show the DLUHC privacy notice to the tenant")
end
end
context "when tenant has seen the privacy notice" do
it "lets submit the log" do
completed_without_declaration.update!({ declaration: "Yes" })
visit("/logs/#{completed_without_declaration.id}/declaration")
expect(page).to have_current_path("/logs/#{completed_without_declaration.id}/declaration")
click_button("Submit lettings log")
expect(page).to have_current_path("/logs")
end
end
end
end end

15
spec/fixtures/complete_case_log.json vendored

@ -40,7 +40,7 @@
"age8": 2, "age8": 2,
"sex8": "Prefer not to say", "sex8": "Prefer not to say",
"ecstat8": "Child under 16", "ecstat8": "Child under 16",
"homeless": "Yes - other homelessness", "homeless": "No",
"reason": 1, "reason": 1,
"underoccupation_benefitcap": "No", "underoccupation_benefitcap": "No",
"leftreg": "No - they left up to 5 years ago", "leftreg": "No - they left up to 5 years ago",
@ -74,8 +74,9 @@
"mrcyear": 2020, "mrcyear": 2020,
"offered": 2, "offered": 2,
"wchair": "Yes", "wchair": "Yes",
"net_income_known": "Weekly", "net_income_known": "Yes",
"earnings": 150, "earnings": 150,
"incfreq": "Weekly",
"benefits": "Some", "benefits": "Some",
"hb": "Universal Credit with housing element (excluding housing benefit)", "hb": "Universal Credit with housing element (excluding housing benefit)",
"period": "Every 2 weeks", "period": "Every 2 weeks",
@ -89,8 +90,7 @@
"lawaitlist": "Less than 1 year", "lawaitlist": "Less than 1 year",
"prevloc": "Ashford", "prevloc": "Ashford",
"previous_postcode": "SE2 6RT", "previous_postcode": "SE2 6RT",
"reasonpref": "Yes", "reasonpref": "No",
"reasonable_preference_reason": "dummy",
"cbl": "Yes", "cbl": "Yes",
"chr": "Yes", "chr": "Yes",
"cap": "No", "cap": "No",
@ -115,15 +115,13 @@
"illness_type_9": "No", "illness_type_9": "No",
"illness_type_10": "No", "illness_type_10": "No",
"condition_effects_prefer_not_to_say": "Yes", "condition_effects_prefer_not_to_say": "Yes",
"rp_homeless": "Yes", "rp_homeless": "No",
"rp_insan_unsat": "No", "rp_insan_unsat": "No",
"rp_medwel": "No", "rp_medwel": "No",
"rp_hardship": "No", "rp_hardship": "No",
"rp_dontknow": "No", "rp_dontknow": "No",
"discarded_at": "05/05/2020", "discarded_at": "05/05/2020",
"override_net_income_validation": "", "override_net_income_validation": "",
"gdpr_acceptance": "",
"gdpr_declined": "",
"property_owner_organisation": "", "property_owner_organisation": "",
"property_manager_organisation": "", "property_manager_organisation": "",
"rent_type": "Social Rent", "rent_type": "Social Rent",
@ -147,6 +145,7 @@
"household_charge": "Yes", "household_charge": "Yes",
"is_carehome": "Yes", "is_carehome": "Yes",
"chcharge": "6", "chcharge": "6",
"letting_in_sheltered_accomodation": "No" "letting_in_sheltered_accomodation": "No",
"declaration": "Yes"
} }
} }

54
spec/fixtures/forms/2021_2022.json vendored

@ -104,6 +104,15 @@
} }
} }
} }
},
"propcode": {
"questions": {
"propcode": {
"check_answer_label": "",
"header": "property reference?",
"type": "text"
}
}
} }
} }
}, },
@ -358,7 +367,11 @@
"step": 1, "step": 1,
"width": 5, "width": 5,
"prefix": "£", "prefix": "£",
"suffix": "incfreq" "suffix": [
{ "label": "every week", "depends_on" : { "incfreq": "Weekly" } },
{ "label": "every month", "depends_on" : { "incfreq": "Monthly" } },
{ "label": "every month", "depends_on" : { "incfreq": "Yearly" } }
]
}, },
"incfreq": { "incfreq": {
"check_answer_label": "Income Frequency", "check_answer_label": "Income Frequency",
@ -626,32 +639,6 @@
} }
} }
}, },
"setup": {
"label": "Before you start",
"subsections": {
"setup": {
"label": "Set up your lettings log",
"pages": {
"gdpr_acceptance": {
"header": "",
"description": "",
"questions": {
"gdpr_acceptance": {
"check_answer_label": "Privacy notice seen",
"header": "Has the tenant or buyer seen the Department for Levelling Up, Housing and Communities (DLUHC) privacy notice?",
"hint_text": "You must <a class=\"govuk-link\" href=\"/files/privacy-notice.pdf\">show the privacy notice</a> to the tenant or buyer before you can use this service.",
"type": "radio",
"answer_options": {
"0": "Yes",
"1": "No"
}
}
}
}
}
}
}
},
"submission": { "submission": {
"label": "Submission", "label": "Submission",
"subsections": { "subsections": {
@ -661,19 +648,18 @@
"household_characteristics": "completed", "household_characteristics": "completed",
"household_needs": "completed", "household_needs": "completed",
"tenancy_information": "completed", "tenancy_information": "completed",
"property_information": "completed", "property_information": "completed"
"income_and_benefits": "completed",
"rent_and_charges": "completed",
"local_authority": "completed"
}], }],
"pages": { "pages": {
"declaration": { "declaration": {
"questions": { "questions": {
"declaration": { "declaration": {
"check_answer_label": "", "check_answer_label": "",
"header": "What is the tenant code?", "header": "Submit your lettings log ",
"type": "text", "type": "checkbox",
"width": 10 "answer_options": {
"declaration": "The tenant has seen the Department for Levelling Up, Housing & Communities (DLUHC) privacy notice"
}
} }
} }
} }

39
spec/fixtures/forms/2022_2023.json vendored

@ -7,30 +7,6 @@
"setup": { "setup": {
"label": "Set up your lettings log", "label": "Set up your lettings log",
"pages": { "pages": {
"gdpr_acceptance": {
"header": "",
"description": "",
"questions": {
"gdpr_acceptance": {
"check_answer_label": "Privacy notice seen",
"header": "Has the tenant or buyer seen the Department for Levelling Up, Housing and Communities (DLUHC) privacy notice?",
"hint_text": "You must <a class=\"govuk-link\" href=\"/files/privacy-notice.pdf\">show the privacy notice</a> to the tenant or buyer before you can use this service.",
"type": "radio",
"answer_options": {
"0": "Yes",
"1": "No"
}
}
}
},
"gdpr_declined": {
"header": "You cannot use this service",
"hint_text": "",
"description": "We cannot accept data about a tenant or buyer unless they’ve seen the DLUHC privacy notice.",
"questions": {
},
"depends_on": [{ "gdpr_acceptance": "No" }]
},
"renewal": { "renewal": {
"header": "", "header": "",
"description": "", "description": "",
@ -45,10 +21,7 @@
"0": "No" "0": "No"
} }
} }
}, }
"depends_on": [{
"gdpr_acceptance": "Yes"
}]
}, },
"startdate": { "startdate": {
"header": "", "header": "",
@ -60,10 +33,7 @@
"hint_text": "For example, 27 3 2007", "hint_text": "For example, 27 3 2007",
"type": "date" "type": "date"
} }
}, }
"depends_on": [{
"gdpr_acceptance": "Yes"
}]
}, },
"about_this_letting": { "about_this_letting": {
"header": "Tell us about this letting", "header": "Tell us about this letting",
@ -101,10 +71,7 @@
"1": "General needs" "1": "General needs"
} }
} }
}, }
"depends_on": [{
"gdpr_acceptance": "Yes"
}]
} }
} }
} }

3
spec/helpers/check_answers_helper_spec.rb

@ -9,7 +9,7 @@ RSpec.describe CheckAnswersHelper do
context "when a section hasn't been completed yet" do context "when a section hasn't been completed yet" do
it "returns that you have unanswered questions" do it "returns that you have unanswered questions" do
expect(display_answered_questions_summary(subsection, case_log)) expect(display_answered_questions_summary(subsection, case_log))
.to match(/You have answered 2 of 4 questions./) .to match(/You have answered 2 of 5 questions./)
end end
end end
@ -17,6 +17,7 @@ RSpec.describe CheckAnswersHelper do
it "returns that you have answered all the questions" do it "returns that you have answered all the questions" do
case_log.sex1 = "F" case_log.sex1 = "F"
case_log.other_hhmemb = 0 case_log.other_hhmemb = 0
case_log.propcode = "123"
expect(display_answered_questions_summary(subsection, case_log)) expect(display_answered_questions_summary(subsection, case_log))
.to match(/You answered all the questions./) .to match(/You answered all the questions./)
expect(display_answered_questions_summary(subsection, case_log)) expect(display_answered_questions_summary(subsection, case_log))

4
spec/helpers/tasklist_helper_spec.rb

@ -17,7 +17,7 @@ RSpec.describe TasklistHelper do
describe "get sections count" do describe "get sections count" do
it "returns the total of sections if no status is given" do it "returns the total of sections if no status is given" do
expect(get_subsections_count(empty_case_log)).to eq(10) expect(get_subsections_count(empty_case_log)).to eq(9)
end end
it "returns 0 sections for completed sections if no sections are completed" do it "returns 0 sections for completed sections if no sections are completed" do
@ -25,7 +25,7 @@ RSpec.describe TasklistHelper do
end end
it "returns the number of not started sections" do it "returns the number of not started sections" do
expect(get_subsections_count(empty_case_log, :not_started)).to eq(9) expect(get_subsections_count(empty_case_log, :not_started)).to eq(8)
end end
it "returns the number of sections in progress" do it "returns the number of sections in progress" do

15
spec/models/admin_user_spec.rb

@ -20,7 +20,6 @@ RSpec.describe AdminUser, type: :model do
) )
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
end end
end
it "requires an email" do it "requires an email" do
expect { expect {
@ -50,3 +49,17 @@ RSpec.describe AdminUser, type: :model do
}.to change(described_class, :count).by(1) }.to change(described_class, :count).by(1)
end end
end end
describe "paper trail" do
let(:admin_user) { FactoryBot.create(:admin_user) }
it "creates a record of changes to a log" do
expect { admin_user.update!(phone: "09673867853") }.to change(admin_user.versions, :count).by(1)
end
it "allows case logs to be restored to a previous version" do
admin_user.update!(phone: "09673867853")
expect(admin_user.paper_trail.previous_version.phone).to eq("07563867654")
end
end
end

53
spec/models/case_log_spec.rb

@ -1087,17 +1087,6 @@ RSpec.describe CaseLog do
end end
end end
context "when saving net_income" do
it "infers the income frequency" do
case_log.update!(net_income_known: "Weekly")
expect(case_log.reload.incfreq).to eq("Weekly")
case_log.update!(net_income_known: "Monthly")
expect(case_log.reload.incfreq).to eq("Monthly")
case_log.update!(net_income_known: "Annually")
expect(case_log.reload.incfreq).to eq("Yearly")
end
end
context "when saving rent and charges" do context "when saving rent and charges" do
let!(:case_log) do let!(:case_log) do
described_class.create({ described_class.create({
@ -1159,6 +1148,35 @@ RSpec.describe CaseLog do
record_from_db = ActiveRecord::Base.connection.execute("select has_benefits from case_logs where id=#{case_log.id}").to_a[0] record_from_db = ActiveRecord::Base.connection.execute("select has_benefits from case_logs where id=#{case_log.id}").to_a[0]
expect(record_from_db["has_benefits"]).to eq("Yes") expect(record_from_db["has_benefits"]).to eq("Yes")
end end
context "when it is a renewal" do
let!(:case_log) do
described_class.create({
managing_organisation: organisation,
owning_organisation: organisation,
renewal: "Yes",
year: 2021,
})
end
it "correctly derives and saves layear" do
record_from_db = ActiveRecord::Base.connection.execute("select layear from case_logs where id=#{case_log.id}").to_a[0]
expect(record_from_db["layear"]).to eq(2)
expect(case_log["layear"]).to eq("Less than 1 year")
end
it "correctly derives and saves underoccupation_benefitcap if year is 2021" do
record_from_db = ActiveRecord::Base.connection.execute("select underoccupation_benefitcap from case_logs where id=#{case_log.id}").to_a[0]
expect(record_from_db["underoccupation_benefitcap"]).to eq(2)
expect(case_log["underoccupation_benefitcap"]).to eq("No")
end
it "correctly derives and saves homeless" do
record_from_db = ActiveRecord::Base.connection.execute("select homeless from case_logs where id=#{case_log.id}").to_a[0]
expect(record_from_db["homeless"]).to eq(1)
expect(case_log["homeless"]).to eq("No")
end
end
end end
describe "resetting invalidated fields" do describe "resetting invalidated fields" do
@ -1178,4 +1196,17 @@ RSpec.describe CaseLog do
end end
end end
end end
describe "paper trail" do
let(:case_log) { FactoryBot.create(:case_log, :in_progress) }
it "creates a record of changes to a log" do
expect { case_log.update!(age1: 64) }.to change(case_log.versions, :count).by(1)
end
it "allows case logs to be restored to a previous version" do
case_log.update!(age1: 63)
expect(case_log.paper_trail.previous_version.age1).to eq(17)
end
end
end end

40
spec/models/form/question_spec.rb

@ -99,16 +99,17 @@ RSpec.describe Form::Question, type: :model do
context "with a case log" do context "with a case log" do
let(:case_log) { FactoryBot.build(:case_log, :in_progress) } let(:case_log) { FactoryBot.build(:case_log, :in_progress) }
let(:question_id) { "incfreq" }
it "has an answer label" do it "has an answer label" do
case_log.earnings = 100 case_log.incfreq = "Weekly"
expect(question.answer_label(case_log)).to eq("100") expect(question.answer_label(case_log)).to eq("Weekly")
end end
it "has an update answer link text helper" do it "has an update answer link text helper" do
expect(question.update_answer_link_name(case_log)).to eq("Answer<span class=\"govuk-visually-hidden\"> income</span>") expect(question.update_answer_link_name(case_log)).to match(/Answer/)
case_log[question_id] = 5 case_log["incfreq"] = "Weekly"
expect(question.update_answer_link_name(case_log)).to eq("Change<span class=\"govuk-visually-hidden\"> income</span>") expect(question.update_answer_link_name(case_log)).to match(/Change/)
end end
context "when type is date" do context "when type is date" do
@ -155,6 +156,23 @@ RSpec.describe Form::Question, type: :model do
expect(question.enabled?(case_log)).to be true expect(question.enabled?(case_log)).to be true
end end
end end
context "when answers have a suffix dependent on another answer" do
let(:section_id) { "rent_and_charges" }
let(:subsection_id) { "income_and_benefits" }
let(:page_id) { "net_income" }
let(:question_id) { "earnings" }
it "displays the correct label for given suffix and answer the suffix depends on" do
case_log.incfreq = "Weekly"
case_log.earnings = 500
expect(question.answer_label(case_log)).to eq("£500.00 every week")
case_log.incfreq = "Monthly"
expect(question.answer_label(case_log)).to eq("£500.00 every month")
case_log.incfreq = "Yearly"
expect(question.answer_label(case_log)).to eq("£500.00 every year")
end
end
end end
describe ".completed?" do describe ".completed?" do
@ -169,17 +187,5 @@ RSpec.describe Form::Question, type: :model do
expect(question.completed?(case_log)).to be(true) expect(question.completed?(case_log)).to be(true)
end end
end end
context "when the gdpr acceptance is No" do
let(:section_id) { "setup" }
let(:subsection_id) { "setup" }
let(:page_id) { "gdpr_acceptance" }
let(:question_id) { "gdpr_acceptance" }
it "returns false" do
case_log["gdpr_acceptance"] = "No"
expect(question.completed?(case_log)).to be(false)
end
end
end end
end end

32
spec/models/form/subsection_spec.rb

@ -1,4 +1,5 @@
require "rails_helper" require "rails_helper"
require_relative "../../request_helper"
RSpec.describe Form::Subsection, type: :model do RSpec.describe Form::Subsection, type: :model do
subject(:sub_section) { described_class.new(subsection_id, subsection_definition, section) } subject(:sub_section) { described_class.new(subsection_id, subsection_definition, section) }
@ -11,6 +12,10 @@ RSpec.describe Form::Subsection, type: :model do
let(:subsection_id) { "household_characteristics" } let(:subsection_id) { "household_characteristics" }
let(:subsection_definition) { section_definition["subsections"][subsection_id] } let(:subsection_definition) { section_definition["subsections"][subsection_id] }
before do
RequestHelper.stub_http_requests
end
it "has an id" do it "has an id" do
expect(sub_section.id).to eq(subsection_id) expect(sub_section.id).to eq(subsection_id)
end end
@ -20,12 +25,12 @@ RSpec.describe Form::Subsection, type: :model do
end end
it "has pages" do it "has pages" do
expected_pages = %w[tenant_code person_1_age person_1_gender household_number_of_other_members] expected_pages = %w[tenant_code person_1_age person_1_gender household_number_of_other_members propcode]
expect(sub_section.pages.map(&:id)).to eq(expected_pages) expect(sub_section.pages.map(&:id)).to eq(expected_pages)
end end
it "has questions" do it "has questions" do
expected_questions = %w[tenant_code age1 sex1 other_hhmemb relat2 age2 sex2 ecstat2] expected_questions = %w[tenant_code age1 sex1 other_hhmemb relat2 age2 sex2 ecstat2 propcode]
expect(sub_section.questions.map(&:id)).to eq(expected_questions) expect(sub_section.questions.map(&:id)).to eq(expected_questions)
end end
@ -53,9 +58,9 @@ RSpec.describe Form::Subsection, type: :model do
end end
it "has question helpers for the number of applicable questions" do it "has question helpers for the number of applicable questions" do
expected_questions = %w[tenant_code age1 sex1 other_hhmemb] expected_questions = %w[tenant_code age1 sex1 other_hhmemb propcode]
expect(sub_section.applicable_questions(case_log).map(&:id)).to eq(expected_questions) expect(sub_section.applicable_questions(case_log).map(&:id)).to eq(expected_questions)
expect(sub_section.applicable_questions_count(case_log)).to eq(4) expect(sub_section.applicable_questions_count(case_log)).to eq(5)
end end
it "has question helpers for the number of answered questions" do it "has question helpers for the number of answered questions" do
@ -72,28 +77,25 @@ RSpec.describe Form::Subsection, type: :model do
end end
it "has a question helpers for the unanswered questions" do it "has a question helpers for the unanswered questions" do
expected_questions = %w[sex1 other_hhmemb] expected_questions = %w[sex1 other_hhmemb propcode]
expect(sub_section.unanswered_questions(case_log).map(&:id)).to eq(expected_questions) expect(sub_section.unanswered_questions(case_log).map(&:id)).to eq(expected_questions)
end end
end end
context "when the privacy notice has not been shown" do
let(:section_id) { "setup" }
let(:subsection_id) { "setup" }
let(:case_log) { FactoryBot.build(:case_log, :about_completed, gdpr_acceptance: "No") }
it "does not mark the section as completed" do
expect(sub_section.status(case_log)).to eq(:in_progress)
end
end
context "with a completed case log" do context "with a completed case log" do
let(:case_log) { FactoryBot.build(:case_log, :completed) } let(:case_log) { FactoryBot.build(:case_log, :completed) }
let(:case_log_too) { FactoryBot.build(:case_log, :in_progress) }
it "has a status" do it "has a status" do
expect(sub_section.status(case_log)).to eq(:completed) expect(sub_section.status(case_log)).to eq(:completed)
end end
it "has a status when optional fields are not filled" do
case_log.update!({ propcode: nil })
case_log.reload
expect(sub_section.status(case_log)).to eq(:completed)
end
it "has status helpers" do it "has status helpers" do
expect(sub_section.is_incomplete?(case_log)).to be(false) expect(sub_section.is_incomplete?(case_log)).to be(false)
expect(sub_section.is_started?(case_log)).to be(true) expect(sub_section.is_started?(case_log)).to be(true)

16
spec/models/form_spec.rb

@ -36,7 +36,7 @@ RSpec.describe Form, type: :model do
describe "next_incomplete_section_redirect_path" do describe "next_incomplete_section_redirect_path" do
let(:case_log) { FactoryBot.build(:case_log, :in_progress) } let(:case_log) { FactoryBot.build(:case_log, :in_progress) }
let(:subsection) { form.get_subsection("household_characteristics") } let(:subsection) { form.get_subsection("household_characteristics") }
let(:later_subsection) { form.get_subsection("setup") } let(:later_subsection) { form.get_subsection("declaration") }
context "when a user is on the check answers page for a subsection" do context "when a user is on the check answers page for a subsection" do
def answer_household_needs(case_log) def answer_household_needs(case_log)
@ -85,10 +85,6 @@ RSpec.describe Form, type: :model do
case_log.mrcdate = Time.zone.parse("03/11/2019") case_log.mrcdate = Time.zone.parse("03/11/2019")
end end
def answer_local_gdpr_acceptance(case_log)
case_log.gdpr_acceptance = "Yes"
end
before do before do
case_log.tenant_code = "123" case_log.tenant_code = "123"
case_log.age1 = 35 case_log.age1 = 35
@ -111,15 +107,14 @@ RSpec.describe Form, type: :model do
expect(form.next_incomplete_section_redirect_path(subsection, case_log)).to eq("tenancy-code") expect(form.next_incomplete_section_redirect_path(subsection, case_log)).to eq("tenancy-code")
end end
it "returns the next incomplete section by cycling back around if next subsections are completed" do
answer_local_gdpr_acceptance(case_log)
expect(form.next_incomplete_section_redirect_path(later_subsection, case_log)).to eq("armed-forces")
end
it "returns the declaration section for a completed case log" do it "returns the declaration section for a completed case log" do
expect(form.next_incomplete_section_redirect_path(subsection, completed_case_log)).to eq("declaration") expect(form.next_incomplete_section_redirect_path(subsection, completed_case_log)).to eq("declaration")
end end
it "returns the next incomplete section by cycling back around if next subsections are completed" do
expect(form.next_incomplete_section_redirect_path(later_subsection, case_log)).to eq("armed-forces")
end
it "returns the declaration section if all sections are complete but the case log is in progress" do it "returns the declaration section if all sections are complete but the case log is in progress" do
answer_household_needs(case_log) answer_household_needs(case_log)
answer_tenancy_information(case_log) answer_tenancy_information(case_log)
@ -128,7 +123,6 @@ RSpec.describe Form, type: :model do
answer_income_and_benefits(case_log) answer_income_and_benefits(case_log)
answer_rent_and_charges(case_log) answer_rent_and_charges(case_log)
answer_local_authority(case_log) answer_local_authority(case_log)
answer_local_gdpr_acceptance(case_log)
expect(form.next_incomplete_section_redirect_path(subsection, case_log)).to eq("declaration") expect(form.next_incomplete_section_redirect_path(subsection, case_log)).to eq("declaration")
end end

13
spec/models/organisation_spec.rb

@ -49,4 +49,17 @@ RSpec.describe Organisation, type: :model do
end end
end end
end end
describe "paper trail" do
let(:organisation) { FactoryBot.create(:organisation) }
it "creates a record of changes to a log" do
expect { organisation.update!(name: "new test name") }.to change(organisation.versions, :count).by(1)
end
it "allows case logs to be restored to a previous version" do
organisation.update!(name: "new test name")
expect(organisation.paper_trail.previous_version.name).to eq("DLUHC")
end
end
end end

13
spec/models/user_spec.rb

@ -47,4 +47,17 @@ RSpec.describe User, type: :model do
expect(user.data_coordinator?).to be false expect(user.data_coordinator?).to be false
end end
end end
describe "paper trail" do
let(:user) { FactoryBot.create(:user) }
it "creates a record of changes to a log" do
expect { user.update!(name: "new test name") }.to change(user.versions, :count).by(1)
end
it "allows case logs to be restored to a previous version" do
user.update!(name: "new test name")
expect(user.paper_trail.previous_version.name).to eq("Danny Rojas")
end
end
end end

24
spec/models/validations/financial_validations_spec.rb

@ -0,0 +1,24 @@
require "rails_helper"
RSpec.describe Validations::FinancialValidations do
subject(:financial_validator) { validator_class.new }
let(:validator_class) { Class.new { include Validations::FinancialValidations } }
let(:record) { FactoryBot.create(:case_log) }
describe "earnings and income frequency" do
it "when earnings are provided it validates that income frequency must be provided" do
record.earnings = 500
record.incfreq = nil
financial_validator.validate_net_income(record)
expect(record.errors["incfreq"]).to include(match I18n.t("validations.financial.earnings.freq_missing"))
end
it "when income frequency is provided it validates that earnings must be provided" do
record.earnings = nil
record.incfreq = "Weekly"
financial_validator.validate_net_income(record)
expect(record.errors["earnings"]).to include(match I18n.t("validations.financial.earnings.earnings_missing"))
end
end
end

29
spec/requests/case_logs_controller_spec.rb

@ -32,6 +32,7 @@ RSpec.describe CaseLogsController, type: :request do
let(:in_progress) { "in_progress" } let(:in_progress) { "in_progress" }
let(:completed) { "completed" } let(:completed) { "completed" }
context "when API" do
let(:params) do let(:params) do
{ {
"owning_organisation_id": owning_organisation.id, "owning_organisation_id": owning_organisation.id,
@ -114,6 +115,25 @@ RSpec.describe CaseLogsController, type: :request do
end end
end end
context "when UI" do
let(:user) { FactoryBot.create(:user) }
let(:headers) { { "Accept" => "text/html" } }
before do
RequestHelper.stub_http_requests
sign_in user
post "/logs", headers: headers
end
it "tracks who created the record" do
created_id = response.location.match(/[0-9]+/)[0]
whodunnit_actor = CaseLog.find_by(id: created_id).versions.last.actor
expect(whodunnit_actor).to be_a(User)
expect(whodunnit_actor.id).to eq(user.id)
end
end
end
describe "GET" do describe "GET" do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:organisation) { user.organisation } let(:organisation) { user.organisation }
@ -197,7 +217,7 @@ RSpec.describe CaseLogsController, type: :request do
end end
it "displays a section status for a case log" do it "displays a section status for a case log" do
assert_select ".govuk-tag", text: /Not started/, count: 9 assert_select ".govuk-tag", text: /Not started/, count: 8
assert_select ".govuk-tag", text: /Completed/, count: 0 assert_select ".govuk-tag", text: /Completed/, count: 0
assert_select ".govuk-tag", text: /Cannot start yet/, count: 1 assert_select ".govuk-tag", text: /Cannot start yet/, count: 1
end end
@ -219,7 +239,7 @@ RSpec.describe CaseLogsController, type: :request do
end end
it "displays a section status for a case log" do it "displays a section status for a case log" do
assert_select ".govuk-tag", text: /Not started/, count: 8 assert_select ".govuk-tag", text: /Not started/, count: 7
assert_select ".govuk-tag", text: /Completed/, count: 1 assert_select ".govuk-tag", text: /Completed/, count: 1
assert_select ".govuk-tag", text: /Cannot start yet/, count: 1 assert_select ".govuk-tag", text: /Cannot start yet/, count: 1
end end
@ -398,9 +418,8 @@ RSpec.describe CaseLogsController, type: :request do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
it "soft deletes the case log" do it "deletes the case log" do
expect { CaseLog.find(id) }.to raise_error(ActiveRecord::RecordNotFound) expect { CaseLog.find(id) }.to raise_error(ActiveRecord::RecordNotFound)
expect(CaseLog.with_discarded.find(id)).to be_a(CaseLog)
end end
context "with an invalid case log id" do context "with an invalid case log id" do
@ -425,7 +444,7 @@ RSpec.describe CaseLogsController, type: :request do
context "when a case log deletion fails" do context "when a case log deletion fails" do
before do before do
allow(CaseLog).to receive(:find_by).and_return(case_log) allow(CaseLog).to receive(:find_by).and_return(case_log)
allow(case_log).to receive(:discard).and_return(false) allow(case_log).to receive(:delete).and_return(false)
delete "/logs/#{id}", headers: headers delete "/logs/#{id}", headers: headers
end end

7
spec/requests/form_controller_spec.rb

@ -155,6 +155,13 @@ RSpec.describe FormController, type: :request do
expect(case_log.age1).to eq(answer) expect(case_log.age1).to eq(answer)
expect(case_log.age2).to be nil expect(case_log.age2).to be nil
end end
it "tracks who updated the record" do
case_log.reload
whodunnit_actor = case_log.versions.last.actor
expect(whodunnit_actor).to be_a(User)
expect(whodunnit_actor.id).to eq(user.id)
end
end end
end end

7
spec/requests/organisations_controller_spec.rb

@ -185,6 +185,13 @@ RSpec.describe OrganisationsController, type: :request do
follow_redirect! follow_redirect!
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
end end
it "tracks who updated the record" do
organisation.reload
whodunnit_actor = organisation.versions.last.actor
expect(whodunnit_actor).to be_a(User)
expect(whodunnit_actor.id).to eq(user.id)
end
end end
context "with an organisation that the user does not belong to" do context "with an organisation that the user does not belong to" do

7
spec/requests/users_controller_spec.rb

@ -196,6 +196,13 @@ RSpec.describe UsersController, type: :request do
user.reload user.reload
expect(user.name).to eq(new_value) expect(user.name).to eq(new_value)
end end
it "tracks who updated the record" do
user.reload
whodunnit_actor = user.versions.last.actor
expect(whodunnit_actor).to be_a(User)
expect(whodunnit_actor.id).to eq(user.id)
end
end end
context "when the update fails to persist" do context "when the update fails to persist" do

8
spec/support/controller_macros.rb

@ -6,12 +6,4 @@ module ControllerMacros
sign_in user sign_in user
end end
end end
def login_admin_user
before do
@request.env["devise.mapping"] = Devise.mappings[:admin_user]
admin_user = FactoryBot.create(:admin_user)
sign_in admin_user
end
end
end end

21
spec/views/form/page_view_spec.rb

@ -68,6 +68,27 @@ RSpec.describe "form/page" do
expect(rendered).to match(/govuk-input__suffix/) expect(rendered).to match(/govuk-input__suffix/)
expect(rendered).to match("every week") expect(rendered).to match("every week")
end end
context "when the suffix is conditional and not a string" do
let(:question_attributes) do
{
type: "numeric",
prefix: "£",
suffix: [
{ "label": "every week", "depends_on": { "incfreq": "Weekly" } },
{ "label": "every month", "depends_on": { "incfreq": "Monthly" } },
{ "label": "every month", "depends_on": { "incfreq": "Yearly" } },
],
}
end
it "does not render the suffix" do
expect(rendered).not_to match(/govuk-input__suffix/)
expect(rendered).not_to match("every week")
expect(rendered).not_to match("every month")
expect(rendered).not_to match("every year")
end
end
end end
context "with a question containing extra guidance" do context "with a question containing extra guidance" do

Loading…
Cancel
Save