diff --git a/Gemfile b/Gemfile index deaf820b7..2e94d7e75 100644 --- a/Gemfile +++ b/Gemfile @@ -37,6 +37,7 @@ gem "devise", github: "ghiculescu/devise", branch: "error-code-422" # UK postcode parsing and validation gem "uk_postcode" # Use Ruby objects to build reusable markup. A React inspired evolution of the presenter pattern +gem "postcodes_io" gem "view_component" group :development, :test do @@ -66,6 +67,7 @@ group :test do gem "factory_bot_rails" gem "selenium-webdriver", require: false gem "simplecov", require: false + gem "webmock" %w[rspec-core rspec-expectations rspec-mocks rspec-rails rspec-support].each do |lib| gem lib, git: "https://github.com/rspec/#{lib}.git", branch: "main", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index d2f2bd632..cad5c5eb5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -158,6 +158,8 @@ GEM childprocess (4.1.0) coderay (1.1.3) concurrent-ruby (1.1.9) + crack (0.4.5) + rexml crass (1.0.6) deep_merge (1.2.1) diff-lcs (1.4.4) @@ -169,6 +171,7 @@ GEM dotenv (= 2.7.6) railties (>= 3.2) erubi (1.10.0) + excon (0.89.0) factory_bot (6.2.0) activesupport (>= 5.0.0) factory_bot_rails (6.2.0) @@ -192,6 +195,7 @@ GEM has_scope (0.8.0) actionpack (>= 5.2) activesupport (>= 5.2) + hashdiff (1.0.1) hotwire-rails (0.1.3) rails (>= 6.0.0) stimulus-rails @@ -252,6 +256,8 @@ GEM parser (3.0.3.1) ast (~> 2.4.1) pg (1.2.3) + postcodes_io (0.4.0) + excon (~> 0.39) pry (0.13.1) coderay (~> 1.1) method_source (~> 1.0) @@ -389,6 +395,10 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) + webmock (3.14.0) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) webpacker (5.4.3) activesupport (>= 5.2) rack-proxy (>= 0.6.1) @@ -425,6 +435,7 @@ DEPENDENCIES listen (~> 3.3) overcommit (>= 0.37.0) pg (~> 1.1) + postcodes_io pry-byebug puma (~> 5.0) rack-mini-profiler (~> 2.0) @@ -445,6 +456,7 @@ DEPENDENCIES uk_postcode view_component web-console (>= 4.1.0) + webmock webpacker (~> 5.0) RUBY VERSION diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 7ea9af741..50444d311 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -1,3 +1,5 @@ +require "postcodes_io" + class CaseLogValidator < ActiveModel::Validator # Validations methods need to be called 'validate_' to run on model save # or form page submission @@ -117,7 +119,7 @@ class CaseLog < ApplicationRecord enum postcode_known: POLAR, _suffix: true enum la_known: POLAR, _suffix: true - AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at renttype lettype].freeze + AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at renttype lettype is_la_inferred].freeze OPTIONAL_FIELDS = %w[postcode_known la_known first_time_property_let_as_social_housing].freeze @@ -159,6 +161,8 @@ class CaseLog < ApplicationRecord private + PIO = Postcodes::IO.new + def update_status! self.status = if all_fields_completed? && errors.empty? "completed" @@ -193,6 +197,20 @@ private self.hhmemb = other_hhmemb + 1 if other_hhmemb.present? self.renttype = RENT_TYPE_MAPPING[rent_type] self.lettype = "#{renttype} #{needstype} #{owning_organisation['Org type']}" if renttype.present? && needstype.present? && owning_organisation["Org type"].present? + self.is_la_inferred = false if is_la_inferred.nil? + self.la = get_la(property_postcode) + end + + def get_la(postcode) + if postcode.present? + postcode_lookup = PIO.lookup(postcode) + if postcode_lookup && postcode_lookup.info.present? + self.is_la_inferred = true + return postcode_lookup.admin_district + end + end + self.la = nil + self.is_la_inferred = false end def all_fields_completed? diff --git a/app/models/form/page.rb b/app/models/form/page.rb index 6ee911de6..2cdebff39 100644 --- a/app/models/form/page.rb +++ b/app/models/form/page.rb @@ -24,7 +24,7 @@ class Form::Page return true unless depends_on depends_on.all? do |question, value| - case_log[question].present? && case_log[question] == value + !case_log[question].nil? && case_log[question] == value end end end diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index a9b458c2e..09485844b 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -1119,10 +1119,10 @@ "1": "Yes" }, "conditional_for": { - "postcode": ["Yes"] + "property_postcode": ["Yes"] } }, - "postcode": { + "property_postcode": { "check_answer_label": "Postcode", "header": "", "hint_text": "", @@ -1145,7 +1145,8 @@ "1": "Yes" } } - } + }, + "depends_on": {"is_la_inferred": false} }, "select_local_authority": { "header": "", @@ -1473,7 +1474,7 @@ } } }, - "depends_on": { "la_known": "Yes" } + "depends_on": { "la_known": "Yes", "is_la_inferred": false } }, "why_dont_you_know_la": { "header": "", @@ -1486,7 +1487,7 @@ "type": "textarea" } }, - "depends_on": { "la_known": "No" } + "depends_on": { "la_known": "No", "is_la_inferred": false } }, "first_time_property_let_as_social_housing": { "header": "", diff --git a/db/migrate/20211209092131_add_is_la_inferred.rb b/db/migrate/20211209092131_add_is_la_inferred.rb new file mode 100644 index 000000000..d53c06c3d --- /dev/null +++ b/db/migrate/20211209092131_add_is_la_inferred.rb @@ -0,0 +1,7 @@ +class AddIsLaInferred < ActiveRecord::Migration[6.1] + def change + change_table :case_logs, bulk: true do |t| + t.column :is_la_inferred, :boolean + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7f5ba379d..70cdb5569 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -174,6 +174,7 @@ ActiveRecord::Schema.define(version: 2021_12_13_122642) do t.integer "day" t.integer "month" t.integer "year" + t.boolean "is_la_inferred" t.index ["discarded_at"], name: "index_case_logs_on_discarded_at" 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" diff --git a/spec/controllers/admin/case_logs_controller_spec.rb b/spec/controllers/admin/case_logs_controller_spec.rb index 8c4d9a1e6..13684951c 100644 --- a/spec/controllers/admin/case_logs_controller_spec.rb +++ b/spec/controllers/admin/case_logs_controller_spec.rb @@ -1,7 +1,11 @@ require "rails_helper" require_relative "../../support/devise" +require_relative "../../request_helper" describe Admin::CaseLogsController, type: :controller do + before do + RequestHelper.stub_http_requests + end render_views let(:page) { Capybara::Node::Simple.new(response.body) } let(:resource_title) { "Logs" } diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb index 6b9f6f808..8bf8652d2 100644 --- a/spec/controllers/admin/dashboard_controller_spec.rb +++ b/spec/controllers/admin/dashboard_controller_spec.rb @@ -1,7 +1,11 @@ require "rails_helper" require_relative "../../support/devise" +require_relative "../../request_helper" describe Admin::DashboardController, type: :controller do + before do + RequestHelper.stub_http_requests + end render_views let(:page) { Capybara::Node::Simple.new(response.body) } let(:resource_title) { "Dashboard" } diff --git a/spec/features/form/check_answers_page_spec.rb b/spec/features/form/check_answers_page_spec.rb index a3abeae4f..57e09c295 100644 --- a/spec/features/form/check_answers_page_spec.rb +++ b/spec/features/form/check_answers_page_spec.rb @@ -1,5 +1,6 @@ require "rails_helper" require_relative "helpers" +require_relative "../../request_helper" RSpec.describe "Form Check Answers Page" do include Helpers @@ -22,6 +23,7 @@ RSpec.describe "Form Check Answers Page" do let(:id) { case_log.id } before do + RequestHelper.stub_http_requests sign_in user end diff --git a/spec/features/form/conditional_questions_spec.rb b/spec/features/form/conditional_questions_spec.rb index fe6d866ce..357b0414d 100644 --- a/spec/features/form/conditional_questions_spec.rb +++ b/spec/features/form/conditional_questions_spec.rb @@ -1,5 +1,6 @@ require "rails_helper" require_relative "helpers" +require_relative "../../request_helper" RSpec.describe "Form Conditional Questions" do include Helpers @@ -15,6 +16,7 @@ RSpec.describe "Form Conditional Questions" do let(:id) { case_log.id } before do + RequestHelper.stub_http_requests sign_in user end diff --git a/spec/features/form/form_navigation_spec.rb b/spec/features/form/form_navigation_spec.rb index 353497eb8..445ff98e5 100644 --- a/spec/features/form/form_navigation_spec.rb +++ b/spec/features/form/form_navigation_spec.rb @@ -1,5 +1,6 @@ require "rails_helper" require_relative "helpers" +require_relative "../../request_helper" RSpec.describe "Form Navigation" do include Helpers @@ -23,6 +24,7 @@ RSpec.describe "Form Navigation" do end before do + RequestHelper.stub_http_requests sign_in user end diff --git a/spec/features/form/page_routing_spec.rb b/spec/features/form/page_routing_spec.rb index c7b37ac04..5c33f4b8c 100644 --- a/spec/features/form/page_routing_spec.rb +++ b/spec/features/form/page_routing_spec.rb @@ -1,5 +1,6 @@ require "rails_helper" require_relative "helpers" +require_relative "../../request_helper" RSpec.describe "Form Page Routing" do include Helpers @@ -15,6 +16,7 @@ RSpec.describe "Form Page Routing" do let(:id) { case_log.id } before do + RequestHelper.stub_http_requests allow_any_instance_of(CaseLogValidator).to receive(:validate_pregnancy).and_return(true) sign_in user end @@ -45,4 +47,29 @@ RSpec.describe "Form Page Routing" do click_button("Save and continue") expect(page).to have_current_path("/logs/#{id}/conditional-question/check-answers") end + + context "inferred answers routing", js: true do + it "shows question if the answer could not be inferred" do + visit("/logs/#{id}/property-postcode") + fill_in("case-log-property-postcode-field", with: "P0 5ST") + click_button("Save and continue") + expect(page).to have_current_path("/logs/#{id}/do-you-know-the-local-authority") + end + + it "shows question if the answer could not be inferred" do + visit("/logs/#{id}/property-postcode") + click_button("Save and continue") + expect(page).to have_current_path("/logs/#{id}/do-you-know-the-local-authority") + end + + it "does not show question if the answer could be inferred" do + stub_request(:get, /api.postcodes.io/) + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\"}}", headers: {}) + + visit("/logs/#{id}/property-postcode") + fill_in("case-log-property-postcode-field", with: "P0 5ST") + click_button("Save and continue") + expect(page).to have_current_path("/logs/#{id}/property-wheelchair-accessible") + end + end end diff --git a/spec/features/form/saving_data_spec.rb b/spec/features/form/saving_data_spec.rb index f419a3438..c4e634909 100644 --- a/spec/features/form/saving_data_spec.rb +++ b/spec/features/form/saving_data_spec.rb @@ -1,5 +1,6 @@ require "rails_helper" require_relative "helpers" +require_relative "../../request_helper" RSpec.describe "Form Saving Data" do include Helpers @@ -31,6 +32,7 @@ RSpec.describe "Form Saving Data" do end before do + RequestHelper.stub_http_requests sign_in user end diff --git a/spec/features/form/validations_spec.rb b/spec/features/form/validations_spec.rb index 83999139c..4aa72e261 100644 --- a/spec/features/form/validations_spec.rb +++ b/spec/features/form/validations_spec.rb @@ -1,7 +1,13 @@ require "rails_helper" require_relative "helpers" +require_relative "../../request_helper" RSpec.describe "validations" do + before do + RequestHelper.stub_http_requests + sign_in user + end + include Helpers let(:user) { FactoryBot.create(:user) } let(:case_log) do @@ -21,10 +27,6 @@ RSpec.describe "validations" do end let(:id) { case_log.id } - before do - sign_in user - end - describe "Question validation" do context "given an invalid tenant age" do it " of less than 0 it shows validation" do diff --git a/spec/fixtures/forms/test_form.json b/spec/fixtures/forms/test_form.json index 71d002e13..d2c234bc0 100644 --- a/spec/fixtures/forms/test_form.json +++ b/spec/fixtures/forms/test_form.json @@ -224,6 +224,36 @@ "property_information": { "label": "Property information", "pages": { + "property_postcode": { + "header": "", + "description": "", + "questions": { + "property_postcode": { + "check_answer_label": "Postcode", + "header": "", + "hint_text": "", + "type": "text", + "width": 5 + } + } + }, + "do_you_know_the_local_authority": { + "header": "", + "description": "", + "questions": { + "la_known": { + "check_answer_label": "Do you know what local authority the property is located in?", + "header": "Do you know what local authority the property is located in?", + "hint_text": "", + "type": "radio", + "answer_options": { + "0": "No", + "1": "Yes" + } + } + }, + "depends_on": {"is_la_inferred": false} + }, "property_wheelchair_accessible": { "questions": { "wchair": { diff --git a/spec/helpers/tasklist_helper_spec.rb b/spec/helpers/tasklist_helper_spec.rb index e609c142b..704866ce9 100644 --- a/spec/helpers/tasklist_helper_spec.rb +++ b/spec/helpers/tasklist_helper_spec.rb @@ -1,6 +1,10 @@ require "rails_helper" +require_relative "../request_helper" RSpec.describe TasklistHelper do + before do + RequestHelper.stub_http_requests + end let(:empty_case_log) { FactoryBot.create(:case_log) } let(:case_log) { FactoryBot.create(:case_log, :in_progress) } form_handler = FormHandler.instance @@ -31,7 +35,7 @@ RSpec.describe TasklistHelper do end it "returns the number of sections in progress" do - expect(get_subsections_count(form, case_log, :in_progress)).to eq(2) + expect(get_subsections_count(form, case_log, :in_progress)).to eq(3) end it "returns 0 for invalid state" do diff --git a/spec/models/case_log_spec.rb b/spec/models/case_log_spec.rb index 8086aa3e3..c8ad9195e 100644 --- a/spec/models/case_log_spec.rb +++ b/spec/models/case_log_spec.rb @@ -1,8 +1,12 @@ require "rails_helper" +require_relative "../request_helper" RSpec.describe Form, type: :model do let(:owning_organisation) { FactoryBot.create(:organisation) } let(:managing_organisation) { owning_organisation } + before do + RequestHelper.stub_http_requests + end describe "#new" do it "validates age is a number" do @@ -1016,5 +1020,44 @@ RSpec.describe Form, type: :model do expect(record_from_db["month"]).to eq(10) expect(record_from_db["year"]).to eq(2021) end + + context "addresses" do + before do + stub_request(:get, /api.postcodes.io/) + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\"}}", headers: {}) + end + + let!(:address_case_log) do + CaseLog.create({ + managing_organisation: organisation, + owning_organisation: organisation, + property_postcode: "M1 1AE", + }) + end + + it "correctly infers la" do + address_case_log.reload + + record_from_db = ActiveRecord::Base.connection.execute("select la from case_logs where id=#{address_case_log.id}").to_a[0] + expect(address_case_log.la).to eq("Manchester") + expect(record_from_db["la"]).to eq("E08000003") + end + + it "correctly resets all fields" do + address_case_log.reload + + record_from_db = ActiveRecord::Base.connection.execute("select la from case_logs where id=#{address_case_log.id}").to_a[0] + expect(address_case_log.la).to eq("Manchester") + expect(record_from_db["la"]).to eq("E08000003") + + address_case_log.update!({ property_postcode: "" }) + address_case_log.reload + + record_from_db = ActiveRecord::Base.connection.execute("select la, property_postcode from case_logs where id=#{address_case_log.id}").to_a[0] + expect(record_from_db["property_postcode"]).to eq("") + expect(address_case_log.la).to eq(nil) + expect(record_from_db["la"]).to eq(nil) + end + end end end diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index e87765aac..4e5883ec2 100644 --- a/spec/models/form_handler_spec.rb +++ b/spec/models/form_handler_spec.rb @@ -15,7 +15,7 @@ RSpec.describe FormHandler do form_handler = FormHandler.instance form = form_handler.get_form("test_form") expect(form).to be_a(Form) - expect(form.pages.count).to eq(25) + expect(form.pages.count).to eq(27) end end diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index 1194483d9..99a7cab96 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -1,6 +1,10 @@ require "rails_helper" +require_relative "../request_helper" RSpec.describe Organisation, type: :model do + before do + RequestHelper.stub_http_requests + end describe "#new" do let(:user) { FactoryBot.create(:user) } let(:organisation) { user.organisation } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3195b73a0..1c65f597e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,6 +1,10 @@ require "rails_helper" +require_relative "../request_helper" RSpec.describe User, type: :model do + before do + RequestHelper.stub_http_requests + end describe "#new" do let(:user) { FactoryBot.create(:user) } let(:other_organisation) { FactoryBot.create(:organisation) } diff --git a/spec/request_helper.rb b/spec/request_helper.rb new file mode 100644 index 000000000..c939e7afa --- /dev/null +++ b/spec/request_helper.rb @@ -0,0 +1,9 @@ +require "webmock/rspec" + +module RequestHelper + def self.stub_http_requests + WebMock.disable_net_connect!(allow_localhost: true) + WebMock.stub_request(:get, /api.postcodes.io/) + .to_return(status: 200, body: "{\"status\":404,\"error\":\"Postcode not found\"}", headers: {}) + end +end diff --git a/spec/requests/case_log_controller_spec.rb b/spec/requests/case_log_controller_spec.rb index 0dac4f9b3..c46394742 100644 --- a/spec/requests/case_log_controller_spec.rb +++ b/spec/requests/case_log_controller_spec.rb @@ -1,4 +1,5 @@ require "rails_helper" +require_relative "../request_helper" RSpec.describe CaseLogsController, type: :request do let(:owning_organisation) { FactoryBot.create(:organisation) } @@ -19,6 +20,7 @@ RSpec.describe CaseLogsController, type: :request do end before do + RequestHelper.stub_http_requests allow(ENV).to receive(:[]) allow(ENV).to receive(:[]).with("API_USER").and_return(api_username) allow(ENV).to receive(:[]).with("API_KEY").and_return(api_password) @@ -136,6 +138,7 @@ RSpec.describe CaseLogsController, type: :request do let(:headers) { { "Accept" => "text/html" } } before do + RequestHelper.stub_http_requests sign_in user get "/logs", headers: headers, params: {} end diff --git a/spec/requests/soft_validations_controller_spec.rb b/spec/requests/soft_validations_controller_spec.rb index 56bccea9d..03dd71c06 100644 --- a/spec/requests/soft_validations_controller_spec.rb +++ b/spec/requests/soft_validations_controller_spec.rb @@ -1,10 +1,15 @@ require "rails_helper" +require_relative "../request_helper" RSpec.describe SoftValidationsController, type: :request do let(:params) { { case_log_id: case_log.id } } let(:url) { "/logs/#{case_log.id}/net-income/soft-validations" } let(:user) { FactoryBot.create(:user) } + before do + RequestHelper.stub_http_requests + end + context "a not signed in user" do let(:case_log) { FactoryBot.create(:case_log, :in_progress) } diff --git a/spec/views/case_log_index_view_spec.rb b/spec/views/case_log_index_view_spec.rb index 17da18411..63f7dd29b 100644 --- a/spec/views/case_log_index_view_spec.rb +++ b/spec/views/case_log_index_view_spec.rb @@ -1,5 +1,11 @@ require "rails_helper" +require_relative "../request_helper" + RSpec.describe "case_logs/index" do + before do + RequestHelper.stub_http_requests + end + let(:in_progress_log) { FactoryBot.create(:case_log, :in_progress) } let(:completed_log) { FactoryBot.create(:case_log, :completed) }