Browse Source

CLDC-4159: Address / UPRN for supported housing - implementation (#3145)

* Ask address and UPRN questions for 2026 and later logs

* Fix page tests

* Fix LA and postcode overrides

* Fix resetting address fields and LA

* Add tests for resetting address fields and LA

* Fix lettings log derived fields tests

* Fix lint

* Fix tests

* Clean up lettings log property overrides

* Add comments

* Fix lint

* Add error to location field

* Add error to location field

* Add tests for error on location field

* Refactor property and location postcode matching validation into own function

* Add test for LA override

* Add test for postcode override

* Added `!record.read_attribute(:la)` guard

* Fix lint

* Fix property validation for checking LA is active/in England

* Fix test

* Minor tweaks

* Fix lint
pull/3160/merge
Oscar Richardson 6 days ago committed by GitHub
parent
commit
0515a7cb36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 9
      app/models/derived_variables/lettings_log_variables.rb
  2. 2
      app/models/form/lettings/pages/address_fallback.rb
  3. 2
      app/models/form/lettings/pages/address_search.rb
  4. 23
      app/models/lettings_log.rb
  5. 40
      app/models/validations/property_validations.rb
  6. 65
      app/services/bulk_upload/lettings/year2026/row_parser.rb
  7. 3
      config/locales/validations/lettings/property_information.en.yml
  8. 2
      spec/models/form/lettings/pages/address_fallback_spec.rb
  9. 2
      spec/models/form/lettings/pages/address_search_spec.rb
  10. 142
      spec/models/lettings_log_derived_fields_spec.rb
  11. 117
      spec/models/lettings_log_spec.rb
  12. 16
      spec/models/validations/property_validations_spec.rb
  13. 479
      spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb

9
app/models/derived_variables/lettings_log_variables.rb

@ -86,7 +86,7 @@ module DerivedVariables::LettingsLogVariables
end
set_housingneeds_fields if housingneeds?
if form.start_year_2025_or_later? && is_general_needs?
if form.start_year_2026_or_later? || (form.start_year_2025_or_later? && is_general_needs?)
if changed_to_newbuild? && uprn.nil?
self.manual_address_entry_selected = true
end
@ -170,7 +170,12 @@ module DerivedVariables::LettingsLogVariables
self.referral = 7 if referral_type == 6
self.referral = 16 if referral_type == 7
reset_address_fields! if is_supported_housing?
if !form.start_year_2026_or_later? && is_supported_housing?
reset_address_fields!
elsif form.start_year_2026_or_later? && location_changed?
reset_address_fields!
self.la = nil
end
set_checkbox_values!
end

2
app/models/form/lettings/pages/address_fallback.rb

@ -3,7 +3,7 @@ class Form::Lettings::Pages::AddressFallback < ::Form::Page
super
@id = "address"
@copy_key = "lettings.property_information.address"
@depends_on = [{ "is_supported_housing?" => false, "manual_address_entry_selected" => true }]
@depends_on = [{ "is_address_asked?" => true, "manual_address_entry_selected" => true }]
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

2
app/models/form/lettings/pages/address_search.rb

@ -3,7 +3,7 @@ class Form::Lettings::Pages::AddressSearch < ::Form::Page
super
@id = "address_search"
@copy_key = "sales.property_information.address_search"
@depends_on = [{ "is_supported_housing?" => false, "manual_address_entry_selected" => false }]
@depends_on = [{ "is_address_asked?" => true, "manual_address_entry_selected" => false }]
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

23
app/models/lettings_log.rb

@ -184,19 +184,18 @@ class LettingsLog < Log
end
def la
if location
location.linked_local_authorities.active(form.start_date).first&.code || location.location_code
else
super
end
return super unless location
return super if form.start_year_2026_or_later? && super
location.linked_local_authorities.active(form.start_date).first&.code || location.location_code
end
# TODO: CLDC-4119: Beware! This method may cause issues when testing supported housing log duplicate detection after postcode is added, as it can return `location.postcode` instead of the actual `postcode_full` stored on the log record (`super`). If this happens, investigate why it isn't returning `super`, as it should when `form.start_year_2026_or_later? && super`.
def postcode_full
if location
location.postcode
else
super
end
return super unless location
return super if form.start_year_2026_or_later? && super
location.postcode
end
def postcode_full=(postcode)
@ -773,6 +772,10 @@ class LettingsLog < Log
rsnvac != 15 && rsnvac_was == 15
end
def is_address_asked?
form.start_year_2026_or_later? || !is_supported_housing?
end
private
def reset_invalid_unresolved_log_fields!

40
app/models/validations/property_validations.rb

@ -52,17 +52,34 @@ module Validations::PropertyValidations
end
end
def validate_property_and_location_postcodes_match(record)
return unless record.form.start_year_2026_or_later?
postcode = record.postcode_full
return unless postcode
location_postcode = record.location&.postcode
return unless location_postcode
if postcode != location_postcode
record.errors.add :location_id, I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")
record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.postcode_does_not_match_scheme_location_postcode")
record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")
end
end
# see also: this validation in sales/property_validations.rb
def validate_la_in_england(record)
return unless record.form.start_year_2025_or_later?
return unless record.la
return if record.la.in?(LocalAuthority.england.pluck(:code))
record.errors.add :la, I18n.t("validations.lettings.property.la.not_in_england")
record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.not_in_england")
record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.not_in_england")
record.errors.add :uprn_selection, I18n.t("validations.lettings.property.uprn_selection.not_in_england")
if record.is_general_needs?
record.errors.add :la, I18n.t("validations.lettings.property.la.not_in_england")
record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.not_in_england")
record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.not_in_england")
record.errors.add :uprn_selection, I18n.t("validations.lettings.property.uprn_selection.not_in_england")
if record.uprn.present?
record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.address_not_in_england")
else
@ -87,16 +104,15 @@ module Validations::PropertyValidations
# only compare end date if it exists
return if record.startdate >= la.start_date && (la.end_date.nil? || record.startdate <= la.end_date)
if record.is_general_needs?
record.errors.add :la, I18n.t("validations.lettings.property.la.la_not_valid_for_date", la: la.name)
record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.la_not_valid_for_date", la: la.name)
record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.la_not_valid_for_date", la: la.name)
record.errors.add :uprn_selection, I18n.t("validations.lettings.property.uprn_selection.la_not_valid_for_date", la: la.name)
record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.la_not_valid_for_date", la: la.name)
elsif record.is_supported_housing?
record.errors.add :la, I18n.t("validations.lettings.property.la.la_not_valid_for_date", la: la.name)
record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.la_not_valid_for_date", la: la.name)
record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.la_not_valid_for_date", la: la.name)
record.errors.add :uprn_selection, I18n.t("validations.lettings.property.uprn_selection.la_not_valid_for_date", la: la.name)
record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.la_not_valid_for_date", la: la.name)
if record.is_supported_housing?
record.errors.add :location_id, I18n.t("validations.lettings.property.location_id.la_not_valid_for_date", la: la.name)
record.errors.add :scheme_id, I18n.t("validations.lettings.property.scheme_id.la_not_valid_for_date", la: la.name)
record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.la_not_valid_for_date", la: la.name)
end
end
end

65
app/services/bulk_upload/lettings/year2026/row_parser.rb

@ -438,8 +438,8 @@ class BulkUpload::Lettings::Year2026::RowParser
validate :validate_assigned_to_when_support, on: :after_log
validate :validate_all_charges_given, on: :after_log
validate :validate_uprn_exists_if_any_key_address_fields_are_blank, on: :after_log, unless: -> { supported_housing? }
validate :validate_address_fields, on: :after_log, unless: -> { supported_housing? }
validate :validate_uprn_exists_if_any_key_address_fields_are_blank, on: :after_log
validate :validate_address_fields, on: :after_log
validate :validate_incomplete_soft_validations, on: :after_log
validate :validate_nationality, on: :after_log
@ -536,9 +536,9 @@ class BulkUpload::Lettings::Year2026::RowParser
"field_9", # startdate
"field_10", # startdate
"field_13", # tenancycode
!general_needs? ? :field_6.to_s : nil, # location
!supported_housing? ? "field_23" : nil, # postcode
!supported_housing? ? "field_24" : nil, # postcode
!general_needs? ? :field_6.to_s : nil, # location # TODO: CLDC-4119: remove location from hash
!supported_housing? ? "field_23" : nil, # postcode # TODO: CLDC-4119: add postcode to hash for supported housing
!supported_housing? ? "field_24" : nil, # postcode # TODO: CLDC-4119: add postcode to hash for supported housing
"field_42", # age1
"field_43", # sex1
"field_46", # ecstat1
@ -693,8 +693,8 @@ private
"ecstat1",
"owning_organisation",
"tcharge",
!supported_housing? ? "postcode_full" : nil,
!general_needs? ? "location" : nil,
!supported_housing? ? "postcode_full" : nil, # TODO: CLDC-4119: add postcode to duplicate check fields for supported housing
!general_needs? ? "location" : nil, # TODO: CLDC-4119: remove location from duplicate check fields
"tenancycode",
log.chcharge.present? ? "chcharge" : nil,
].compact
@ -973,11 +973,11 @@ private
errors.add(:field_9, error_message) # startdate
errors.add(:field_10, error_message) # startdate
errors.add(:field_13, error_message) # tenancycode
errors.add(:field_6, error_message) if !general_needs? && :field_6.present? # location
errors.add(:field_6, error_message) if !general_needs? && :field_6.present? # location # TODO: CLDC-4119: remove location from error fields
errors.add(:field_5, error_message) if !general_needs? && :field_6.blank? # add to Scheme field as unclear whether log uses New or Old CORE ids
errors.add(:field_23, error_message) unless supported_housing? # postcode_full
errors.add(:field_24, error_message) unless supported_housing? # postcode_full
errors.add(:field_25, error_message) unless supported_housing? # la
errors.add(:field_23, error_message) unless supported_housing? # postcode_full # TODO: CLDC-4119: add postcode to error fields for supported housing
errors.add(:field_24, error_message) unless supported_housing? # postcode_full # TODO: CLDC-4119: add postcode to error fields for supported housing
errors.add(:field_25, error_message) unless supported_housing? # la # TODO: CLDC-4119: add LA to error fields for supported housing
errors.add(:field_42, error_message) # age1
errors.add(:field_43, error_message) # sex1
errors.add(:field_46, error_message) # ecstat1
@ -1335,29 +1335,26 @@ private
attributes["first_time_property_let_as_social_housing"] = first_time_property_let_as_social_housing
if general_needs?
attributes["uprn_known"] = field_18.present? ? 1 : 0
attributes["uprn_confirmed"] = 1 if field_18.present?
attributes["skip_update_uprn_confirmed"] = true
attributes["uprn"] = field_18
attributes["address_line1"] = field_19
attributes["address_line1_as_entered"] = field_19
attributes["address_line2"] = field_20
attributes["address_line2_as_entered"] = field_20
attributes["town_or_city"] = field_21
attributes["town_or_city_as_entered"] = field_21
attributes["county"] = field_22
attributes["county_as_entered"] = field_22
attributes["postcode_full"] = postcode_full
attributes["postcode_full_as_entered"] = postcode_full
attributes["postcode_known"] = postcode_known
attributes["la"] = field_25
attributes["la_as_entered"] = field_25
attributes["address_line1_input"] = address_line1_input
attributes["postcode_full_input"] = postcode_full
attributes["select_best_address_match"] = true if field_18.blank?
end
attributes["uprn_known"] = field_18.present? ? 1 : 0
attributes["uprn_confirmed"] = 1 if field_18.present?
attributes["skip_update_uprn_confirmed"] = true
attributes["uprn"] = field_18
attributes["address_line1"] = field_19
attributes["address_line1_as_entered"] = field_19
attributes["address_line2"] = field_20
attributes["address_line2_as_entered"] = field_20
attributes["town_or_city"] = field_21
attributes["town_or_city_as_entered"] = field_21
attributes["county"] = field_22
attributes["county_as_entered"] = field_22
attributes["postcode_full"] = postcode_full
attributes["postcode_full_as_entered"] = postcode_full
attributes["postcode_known"] = postcode_known
attributes["la"] = field_25
attributes["la_as_entered"] = field_25
attributes["address_line1_input"] = address_line1_input
attributes["postcode_full_input"] = postcode_full
attributes["select_best_address_match"] = true if field_18.blank?
attributes
end

3
config/locales/validations/lettings/property_information.en.yml

@ -6,6 +6,7 @@ en:
invalid: "Enter a postcode in the correct format, for example AA1 1AA."
not_in_england: "It looks like you have an entered a postcode outside of England. Only create logs for lettings in England."
la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date"
does_not_match_scheme_location_postcode: "The postcode in the address does not match the postcode for the location in the scheme. Please review your answers to the address or UPRN question (whichever you answered) and the location question."
rsnvac:
non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as this accommodation is not temporary."
referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as a different source of referral for this letting."
@ -24,6 +25,7 @@ en:
invalid: "UPRN must be 12 digits or less."
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date"
postcode_does_not_match_scheme_location_postcode: "The postcode in the address does not match the postcode for the location in the scheme. Please review your answers to the address or UPRN question (whichever you answered) and the location question."
uprn_confirmation: # legacy question
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
uprn_selection:
@ -38,6 +40,7 @@ en:
location_id:
not_in_england: "It looks like you have selected a location outside of England. Only create logs for lettings in England."
la_not_valid_for_date: "%{la} does not exist on the tenancy start date, due to a change in local authority names and boundaries. Please enter the local authority name in use on the tenancy start date"
postcode_does_not_match_scheme_location_postcode: "The postcode in the address does not match the postcode for the location in the scheme. Please review your answers to the address or UPRN question (whichever you answered) and the location question."
startdate:
postcode_not_in_england: "It looks like you have an entered a postcode outside of England. Only create logs for lettings in England."
address_not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."

2
spec/models/form/lettings/pages/address_fallback_spec.rb

@ -24,6 +24,6 @@ RSpec.describe Form::Lettings::Pages::AddressFallback, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "manual_address_entry_selected" => true, "is_supported_housing?" => false }])
expect(page.depends_on).to eq([{ "manual_address_entry_selected" => true, "is_address_asked?" => true }])
end
end

2
spec/models/form/lettings/pages/address_search_spec.rb

@ -25,7 +25,7 @@ RSpec.describe Form::Lettings::Pages::AddressSearch, type: :model do
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "is_supported_housing?" => false, "manual_address_entry_selected" => false }])
expect(page.depends_on).to eq([{ "is_address_asked?" => true, "manual_address_entry_selected" => false }])
end
it "has the correct question number" do

142
spec/models/lettings_log_derived_fields_spec.rb

@ -1446,4 +1446,146 @@ RSpec.describe LettingsLog, type: :model do
expect { log.set_derived_fields! }.to change(log, :beds).to nil
end
end
describe "resetting address fields and LA" do
# log.read_attribute is used in these tests to read the underlying attribute (which is what
# is being reset) rather than the getter method, which may override the underlying attribute.
let(:uprn) { "123456789" }
let(:uprn_known) { 1 } # A value of 1 is necessary for this test as there is separate logic that resets `uprn` if `uprn_known` is 0.
let(:uprn_confirmed) { 1 } # A value of 1 is necessary for this test as there is separate logic that resets the address fields and LA if `uprn_confirmed` is 0 (and `uprn_known` is 1).
let(:address_line1) { "1 Test Street" }
let(:address_line2) { "Testville" }
let(:town_or_city) { "Testford" }
let(:county) { "Testshire" }
let(:postcode_full) { "SW1 1AA" }
let(:la) { "Test LA" }
let(:manual_address_entry_selected) { false } # A value of `false` is necessary for this test as there is separate logic that resets some of the UPRN fields if `manual_address_entry_selected` is `false`.
context "when it is 2025", metadata: { year: 25 } do
let(:startdate) { collection_start_date_for_year(2025) }
around do |example|
Timecop.freeze(collection_start_date_for_year(2025)) do
Singleton.__init__(FormHandler)
log.save! # This is necessary to prevent `startdate_changed?` returning true; if this happens, some separate logic runs that resets LA to nil.
log.assign_attributes(uprn:, uprn_known:, uprn_confirmed:, address_line1:, address_line2:, town_or_city:, county:, postcode_full:, la:, manual_address_entry_selected:)
example.run
end
end
context "when the log is marked as general needs housing" do
before do
log.needstype = 1
end
it "does not reset the address fields" do
expect { log.set_derived_fields! }
.to not_change { log.read_attribute(:uprn) }
.and not_change { log.read_attribute(:uprn_known) }
.and not_change { log.read_attribute(:uprn_confirmed) }
.and not_change { log.read_attribute(:address_line1) }
.and not_change { log.read_attribute(:address_line2) }
.and not_change { log.read_attribute(:town_or_city) }
.and not_change { log.read_attribute(:county) }
.and(not_change { log.read_attribute(:postcode_full) })
end
it "does not reset LA" do
expect { log.set_derived_fields! }
.to(not_change { log.read_attribute(:la) })
end
end
context "when the log is marked as supported housing" do
before do
log.needstype = 2
end
it "resets the address fields to nil" do
expect { log.set_derived_fields! }
.to change { log.read_attribute(:uprn) }.from(uprn).to(nil)
.and change { log.read_attribute(:uprn_known) }.from(uprn_known).to(nil)
.and change { log.read_attribute(:uprn_confirmed) }.from(uprn_confirmed).to(nil)
.and change { log.read_attribute(:address_line1) }.from(address_line1).to(nil)
.and change { log.read_attribute(:address_line2) }.from(address_line2).to(nil)
.and change { log.read_attribute(:town_or_city) }.from(town_or_city).to(nil)
.and change { log.read_attribute(:county) }.from(county).to(nil)
.and change { log.read_attribute(:postcode_full) }.from(postcode_full).to(nil)
end
it "does not reset LA" do
expect { log.set_derived_fields! }
.to(not_change { log.read_attribute(:la) })
end
end
end
context "when it is 2026", metadata: { year: 26 } do
let(:startdate) { collection_start_date_for_year(2026) }
let(:location_a) { create(:location) }
let(:location_b) { create(:location) }
around do |example|
Timecop.freeze(collection_start_date_for_year(2026)) do
Singleton.__init__(FormHandler)
example.run
end
end
before do
allow(location_a).to receive(:lookup_postcode!).and_return(nil)
log.location = location_a
log.save!
log.assign_attributes(uprn:, uprn_known:, uprn_confirmed:, address_line1:, address_line2:, town_or_city:, county:, postcode_full:, la:, manual_address_entry_selected:)
end
context "when the location is not changed" do
it "does not reset the address fields" do
expect { log.set_derived_fields! }
.to not_change { log.read_attribute(:uprn) }
.and not_change { log.read_attribute(:uprn_known) }
.and not_change { log.read_attribute(:uprn_confirmed) }
.and not_change { log.read_attribute(:address_line1) }
.and not_change { log.read_attribute(:address_line2) }
.and not_change { log.read_attribute(:town_or_city) }
.and not_change { log.read_attribute(:county) }
.and(not_change { log.read_attribute(:postcode_full) })
end
it "does not reset LA" do
expect { log.set_derived_fields! }
.to(not_change { log.read_attribute(:la) })
end
end
context "when the location is changed" do
before do
log.location = location_b
end
it "resets the address fields to nil" do
expect { log.set_derived_fields! }
.to change { log.read_attribute(:uprn) }.from(uprn).to(nil)
.and change { log.read_attribute(:uprn_known) }.from(uprn_known).to(nil)
.and change { log.read_attribute(:uprn_confirmed) }.from(uprn_confirmed).to(nil)
.and change { log.read_attribute(:address_line1) }.from(address_line1).to(nil)
.and change { log.read_attribute(:address_line2) }.from(address_line2).to(nil)
.and change { log.read_attribute(:town_or_city) }.from(town_or_city).to(nil)
.and change { log.read_attribute(:county) }.from(county).to(nil)
.and change { log.read_attribute(:postcode_full) }.from(postcode_full).to(nil)
end
it "resets LA to nil" do
expect { log.set_derived_fields! }
.to change { log.read_attribute(:la) }.from(la).to(nil)
end
end
end
end
end

117
spec/models/lettings_log_spec.rb

@ -590,6 +590,63 @@ RSpec.describe LettingsLog do
end
end
context "and the log has different LAs set on the location and the log itself" do
before do
location.update!(location_code: "E07000030")
Timecop.freeze(startdate)
Singleton.__init__(FormHandler)
lettings_log.update!(startdate:, la: "E09000002")
lettings_log.reload
end
after do
Timecop.unfreeze
Singleton.__init__(FormHandler)
end
context "with 25/26" do
let(:startdate) { Time.zone.local(2025, 4, 2) }
it "returns the LA from the location" do
expect(lettings_log["location_id"]).to eq(location.id)
expect(lettings_log.la).to eq("E07000030")
end
end
context "with 26/27" do
let(:startdate) { Time.zone.local(2026, 4, 2) }
it "returns the LA from the log itself" do
expect(lettings_log["location_id"]).to eq(location.id)
expect(lettings_log.la).to eq("E09000002")
end
end
end
context "and the log only has an LA set on the location" do
before do
location.update!(location_code: "E07000030")
Timecop.freeze(startdate)
Singleton.__init__(FormHandler)
lettings_log.update!(startdate:)
lettings_log.reload
end
after do
Timecop.unfreeze
Singleton.__init__(FormHandler)
end
context "with 26/27" do
let(:startdate) { Time.zone.local(2026, 4, 2) }
it "returns the LA from the location" do
expect(lettings_log["location_id"]).to eq(location.id)
expect(lettings_log.la).to eq("E07000030")
end
end
end
context "and the location no local authorities associated with the location_code" do
before do
Timecop.freeze(Time.zone.local(2022, 4, 2))
@ -607,6 +664,66 @@ RSpec.describe LettingsLog do
expect(lettings_log.la).to eq("E01231231")
end
end
context "and the log has different postcodes set on the location and the log itself" do
before do
location.update!(postcode: "AA1 1AA")
Timecop.freeze(startdate)
Singleton.__init__(FormHandler)
lettings_log.update!(
startdate:,
)
lettings_log.reload
lettings_log.postcode_full = "BB2 2BB"
end
after do
Timecop.unfreeze
Singleton.__init__(FormHandler)
end
context "with 25/26" do
let(:startdate) { Time.zone.local(2025, 4, 2) }
it "returns the postcode from the location" do
expect(lettings_log["location_id"]).to eq(location.id)
expect(lettings_log.postcode_full).to eq("AA1 1AA")
end
end
context "with 26/27" do
let(:startdate) { Time.zone.local(2026, 4, 2) }
it "returns the postcode from the log itself" do
expect(lettings_log["location_id"]).to eq(location.id)
expect(lettings_log.postcode_full).to eq("BB2 2BB")
end
end
end
context "and the log only has a postcode set on the location" do
before do
location.update!(postcode: "AA1 1AA")
Timecop.freeze(startdate)
Singleton.__init__(FormHandler)
lettings_log.update!(startdate:)
lettings_log.reload
end
after do
Timecop.unfreeze
Singleton.__init__(FormHandler)
end
context "with 26/27" do
let(:startdate) { Time.zone.local(2026, 4, 2) }
it "returns the LA from the location" do
expect(lettings_log["location_id"]).to eq(location.id)
expect(lettings_log.postcode_full).to eq("AA1 1AA")
end
end
end
end
context "and not renewal" do

16
spec/models/validations/property_validations_spec.rb

@ -237,10 +237,10 @@ RSpec.describe Validations::PropertyValidations do
expect(log.errors["scheme_id"]).to include(I18n.t("validations.lettings.property.scheme_id.not_in_england"))
expect(log.errors["location_id"]).to include(I18n.t("validations.lettings.property.location_id.not_in_england"))
expect(log.errors["startdate"]).to include(I18n.t("validations.lettings.property.startdate.location_not_in_england"))
expect(log.errors["la"]).to be_empty
expect(log.errors["postcode_full"]).to be_empty
expect(log.errors["uprn"]).to be_empty
expect(log.errors["uprn_selection"]).to be_empty
expect(log.errors["la"]).to include(I18n.t("validations.lettings.property.la.not_in_england"))
expect(log.errors["postcode_full"]).to include(I18n.t("validations.lettings.property.postcode_full.not_in_england"))
expect(log.errors["uprn"]).to include(I18n.t("validations.lettings.property.uprn.not_in_england"))
expect(log.errors["uprn_selection"]).to include(I18n.t("validations.lettings.property.uprn_selection.not_in_england"))
end
end
@ -342,10 +342,10 @@ RSpec.describe Validations::PropertyValidations do
expect(log.errors["scheme_id"]).to include(I18n.t("validations.lettings.property.scheme_id.la_not_valid_for_date", la: local_authority_inactive.name))
expect(log.errors["location_id"]).to include(I18n.t("validations.lettings.property.location_id.la_not_valid_for_date", la: local_authority_inactive.name))
expect(log.errors["startdate"]).to include(I18n.t("validations.lettings.property.startdate.la_not_valid_for_date", la: local_authority_inactive.name))
expect(log.errors["la"]).to be_empty
expect(log.errors["postcode_full"]).to be_empty
expect(log.errors["uprn"]).to be_empty
expect(log.errors["uprn_selection"]).to be_empty
expect(log.errors["la"]).to include(I18n.t("validations.lettings.property.la.la_not_valid_for_date", la: local_authority_inactive.name))
expect(log.errors["postcode_full"]).to include(I18n.t("validations.lettings.property.postcode_full.la_not_valid_for_date", la: local_authority_inactive.name))
expect(log.errors["uprn"]).to include(I18n.t("validations.lettings.property.uprn.la_not_valid_for_date", la: local_authority_inactive.name))
expect(log.errors["uprn_selection"]).to include(I18n.t("validations.lettings.property.uprn_selection.la_not_valid_for_date", la: local_authority_inactive.name))
end
end

479
spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb

@ -12,7 +12,10 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do
let(:owning_org) { create(:organisation, :with_old_visible_id) }
let(:managing_org) { create(:organisation, :with_old_visible_id, rent_periods: [4, 1]) }
let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
let(:location) { create(:location, :with_old_visible_id, scheme:) }
let(:postcode_first_part) { "AA1".freeze }
let(:postcode_second_part) { "1AA".freeze }
let(:postcode) { "#{postcode_first_part} #{postcode_second_part}" }
let(:location) { create(:location, :with_old_visible_id, scheme:, postcode:) }
let(:setup_section_params) do
{
@ -88,7 +91,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\", \"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {})
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.9, BUILDING_NAME: "result address line 1", POST_TOWN: "result town or city", POSTCODE: "AA1 1AA", UPRN: "12345" } }] }.to_json, headers: {})
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.9, BUILDING_NAME: "result address line 1", POST_TOWN: "result town or city", POSTCODE: postcode, UPRN: "12345" } }] }.to_json, headers: {})
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/uprn/)
.to_return(status: 200, body: '{"status":200,"results":[{"DPA":{
@ -101,7 +104,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do
"DEPENDENT_THOROUGHFARE_NAME": "data",
"THOROUGHFARE_NAME": "thing",
"POST_TOWN": "London",
"POSTCODE": "SE2 6RT"
"POSTCODE": "' + postcode + '"
}}]}', headers: {})
end
@ -293,7 +296,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do
:field_13, # tenancycode
:field_23, # postcode_full
:field_24, # postcode_full
:field_25, # postcode_full
:field_25, # LA
:field_42, # age1
:field_43, # sex1
:field_46, # ecstat1
@ -324,7 +327,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do
end
end
context "when a supported housing log already exists in the db" do
context "when a supported housing log already exists in the db" do # TODO: CLDC-4119: Beware! The `postcode_full` method in the `LettingsLog` class may cause issues with these supported housing log duplicate detection tests after postcode is added. See comment on the `postcode_full` method for details.
let(:attributes) { valid_attributes.merge({ field_4: "2", field_5: "S#{scheme.id}", field_6: location.old_visible_id, field_36: 3, field_122: 0 }) }
before do
@ -1484,183 +1487,251 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do
end
describe "UPRN and address fields" do
context "with a general needs log" do
context "when a valid UPRN is given" do
context "and address fields are not given" do
let(:attributes) { setup_section_params.merge({ field_4: 1, field_18: "123456789012" }) }
it "does not add errors" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
end
%i[
general_needs
supported_housing
].each do |needs_type|
context "with a #{needs_type.to_s.tr('_', ' ')} log" do
let(:base_attributes) do
setup_section_params.merge({
field_4: needs_type == :supported_housing ? 2 : 1,
field_5: needs_type == :supported_housing ? "S#{scheme.id}" : nil,
field_6: needs_type == :supported_housing ? location.old_visible_id : nil,
}.compact)
end
end
context "when an invalid UPRN is given" do
context "and address fields are not given" do
let(:attributes) { setup_section_params.merge({ field_4: 1, field_18: "1234567890123" }) }
it "adds an appropriate error to the UPRN field" do
parser.valid?
expect(parser.errors[:field_18]).to eql(["UPRN must be 12 digits or less."])
end
context "when a valid UPRN is given" do
context "and address fields are not given" do
let(:attributes) { base_attributes.merge({ field_18: "123456789012" }) }
it "adds errors to missing key address fields" do
parser.valid?
expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "address line 1.")])
expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "town or city.")])
expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "part 1 of postcode.")])
expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "part 2 of postcode.")])
end
end
context "and address fields are given" do
let(:attributes) { setup_section_params.merge({ field_4: 1, field_18: "1234567890123", field_19: "address line 1", field_21: "town or city", field_23: "AA1", field_24: "1AA" }) }
it "adds an error to the UPRN field only" do
parser.valid?
expect(parser.errors[:field_18]).to eql(["UPRN must be 12 digits or less."])
%i[field_19 field_21 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
it "does not add errors" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
end
end
it "does not do an address search" do
parser.valid?
expect(a_request(:any, /api\.os\.uk\/search\/places\/v1\/find/)).not_to have_been_made
end
end
end
context "when no UPRN is given" do
context "and no address fields are given" do
let(:attributes) { setup_section_params.merge({ field_4: 1 }) }
it "adds appropriate errors to UPRN and key address fields" do
parser.valid?
expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
end
end
context "and some key address field is missing" do
let(:attributes) { setup_section_params.merge({ field_4: 1, field_21: "town or city", field_23: "AA1", field_24: "1AA" }) }
it "adds errors to UPRN and the missing key address field" do
parser.valid?
expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_21]).to be_empty
expect(parser.errors[:field_23]).to be_empty
expect(parser.errors[:field_24]).to be_empty
if needs_type == :supported_housing
context "and the postcode associated with the UPRN does not match the postcode associated with the location" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/uprn/)
.to_return(status: 200, body: '{"status":200,"results":[{"DPA":{
"PO_BOX_NUMBER": "fake",
"ORGANISATION_NAME": "org",
"DEPARTMENT_NAME": "name",
"SUB_BUILDING_NAME": "building",
"BUILDING_NAME": "name",
"BUILDING_NUMBER": "number",
"DEPENDENT_THOROUGHFARE_NAME": "data",
"THOROUGHFARE_NAME": "thing",
"POST_TOWN": "London",
"POSTCODE": "BB2 2BB"
}}]}', headers: {})
end
it "adds an appropriate error to the UPRN and location fields" do
parser.valid?
expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.property.uprn.postcode_does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_6]).to eql([I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")])
end
end
end
end
end
context "and all key address fields are present" do
let(:attributes) { setup_section_params.merge({ field_4: 1, field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: "AA1", field_24: "1AA" }) }
context "and an address can be found with a high enough match rating" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.7, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }] }.to_json, headers: {})
end
context "when an invalid UPRN is given" do
context "and address fields are not given" do
let(:attributes) { base_attributes.merge({ field_18: "1234567890123" }) }
it "does not add errors" do
it "adds an appropriate error to the UPRN field" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
expect(parser.errors[:field_18]).to eql(["UPRN must be 12 digits or less."])
end
it "does not set manual address input" do
it "adds errors to missing key address fields" do
parser.valid?
expect(parser.log.manual_address_entry_selected).to be_falsey
expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "address line 1.")])
expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "town or city.")])
expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "part 1 of postcode.")])
expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "part 2 of postcode.")])
end
end
context "when no address can be found" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [] }.to_json, headers: {})
end
context "and address fields are given" do
let(:attributes) { base_attributes.merge({ field_18: "1234567890123", field_19: "address line 1", field_21: "town or city", field_23: postcode_first_part, field_24: postcode_second_part }) }
it "does not add errors" do
it "adds an error to the UPRN field only" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[:field_18]).to eql(["UPRN must be 12 digits or less."])
%i[field_19 field_21 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
end
it "sets manual address input" do
it "does not do an address search" do
parser.valid?
expect(parser.log.manual_address_entry_selected).to be_truthy
expect(parser.log.address_line1).to eq("address line 1")
expect(parser.log.town_or_city).to eq("town or city")
expect(parser.log.postcode_full).to eq("AA1 1AA")
expect(a_request(:any, /api\.os\.uk\/search\/places\/v1\/find/)).not_to have_been_made
end
end
end
context "when a single address with not a high enough match rating is returned" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }] }.to_json, headers: {})
end
context "when no UPRN is given" do
context "and no address fields are given" do
let(:attributes) { base_attributes.merge({ field_4: 1 }) }
it "does not add errors" do
it "adds appropriate errors to UPRN and key address fields" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
end
end
context "and some key address field is missing" do
let(:attributes) { base_attributes.merge({ field_21: "town or city", field_23: postcode_first_part, field_24: postcode_second_part }) }
it "sets manual address input" do
it "adds errors to UPRN and the missing key address field" do
parser.valid?
expect(parser.log.manual_address_entry_selected).to be_truthy
expect(parser.log.address_line1).to eq("address line 1")
expect(parser.log.town_or_city).to eq("town or city")
expect(parser.log.postcode_full).to eq("AA1 1AA")
expect(parser.errors[:field_18]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2026.bulk_upload.address.not_answered")])
expect(parser.errors[:field_21]).to be_empty
expect(parser.errors[:field_23]).to be_empty
expect(parser.errors[:field_24]).to be_empty
end
end
context "when no addresses have a high enough match rating" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "AA1 1AA", UPRN: "1" } }, { DPA: { MATCH: 0.8, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "BB2 2BB", UPRN: "2" } }] }.to_json, headers: {})
context "and all key address fields are present" do
let(:attributes) { base_attributes.merge({ field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: postcode_first_part, field_24: postcode_second_part }) }
context "and an address can be found with a high enough match rating" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.7, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: postcode, UPRN: "1" } }] }.to_json, headers: {})
end
it "does not add errors" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
end
it "does not set manual address input" do
parser.valid?
expect(parser.log.manual_address_entry_selected).to be_falsey
end
end
it "does not add errors" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
context "when no address can be found" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [] }.to_json, headers: {})
end
it "does not add errors" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
end
it "sets manual address input" do
parser.valid?
expect(parser.log.manual_address_entry_selected).to be_truthy
expect(parser.log.address_line1).to eq("address line 1")
expect(parser.log.town_or_city).to eq("town or city")
expect(parser.log.postcode_full).to eq(postcode)
end
if needs_type == :supported_housing
context "and the postcode entered manually does not match the postcode associated with the location" do
let(:attributes) { base_attributes.merge({ field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: "BB2", field_24: "2BB" }) }
it "adds appropriate errors to the postcode, LA and location fields" do
parser.valid?
expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_25]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_6]).to eql([I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")])
end
end
end
end
it "sets manual address input" do
parser.valid?
expect(parser.log.manual_address_entry_selected).to be_truthy
expect(parser.log.manual_address_entry_selected).to be_truthy
expect(parser.log.address_line1).to eq("address line 1")
expect(parser.log.town_or_city).to eq("town or city")
expect(parser.log.postcode_full).to eq("AA1 1AA")
context "when a single address with not a high enough match rating is returned" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: postcode, UPRN: "1" } }] }.to_json, headers: {})
end
it "does not add errors" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
end
it "sets manual address input" do
parser.valid?
expect(parser.log.manual_address_entry_selected).to be_truthy
expect(parser.log.address_line1).to eq("address line 1")
expect(parser.log.town_or_city).to eq("town or city")
expect(parser.log.postcode_full).to eq(postcode)
end
if needs_type == :supported_housing
context "and the postcode entered manually does not match the postcode associated with the location" do
let(:attributes) { base_attributes.merge({ field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: "BB2", field_24: "2BB" }) }
it "adds appropriate errors to the postcode, LA and location fields" do
parser.valid?
expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_25]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_6]).to eql([I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")])
end
end
end
end
end
end
end
end
context "with a supported housing log" do
context "when neither UPRN nor address fields are provided" do
let(:attributes) { setup_section_params.merge({ field_4: 2, field_5: "S#{scheme.id}", field_6: location.old_visible_id, field_18: nil, field_19: nil, field_21: nil, field_23: nil, field_24: nil }) }
context "when no addresses have a high enough match rating" do
before do
stub_request(:get, /api\.os\.uk\/search\/places\/v1\/find/)
.to_return(status: 200, body: { results: [{ DPA: { MATCH: 0.6, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: postcode, UPRN: "1" } }, { DPA: { MATCH: 0.8, BUILDING_NAME: "", POST_TOWN: "", POSTCODE: "BB2 2BB", UPRN: "2" } }] }.to_json, headers: {})
end
it "does not add errors" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
end
end
it "does not add missing field errors" do
parser.valid?
%i[field_18 field_19 field_20 field_21 field_22 field_23 field_24].each do |field|
expect(parser.errors[field]).to be_empty
it "sets manual address input" do
parser.valid?
expect(parser.log.manual_address_entry_selected).to be_truthy
expect(parser.log.manual_address_entry_selected).to be_truthy
expect(parser.log.address_line1).to eq("address line 1")
expect(parser.log.town_or_city).to eq("town or city")
expect(parser.log.postcode_full).to eq(postcode)
end
if needs_type == :supported_housing
context "and the postcode entered manually does not match the postcode associated with the location" do
let(:attributes) { base_attributes.merge({ field_18: nil, field_19: "address line 1", field_21: "town or city", field_23: "BB2", field_24: "2BB" }) }
it "adds appropriate errors to the postcode, LA and location fields" do
parser.valid?
expect(parser.errors[:field_23]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_24]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_25]).to eql([I18n.t("validations.lettings.property.postcode_full.does_not_match_scheme_location_postcode")])
expect(parser.errors[:field_6]).to eql([I18n.t("validations.lettings.property.location_id.postcode_does_not_match_scheme_location_postcode")])
end
end
end
end
end
end
end
@ -1869,87 +1940,79 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do
end
end
describe "#uprn" do
let(:attributes) { { bulk_upload:, field_4: 1, field_18: "12" } }
describe "address-related fields" do
%i[
general_needs
supported_housing
].each do |needs_type|
context "with a #{needs_type.to_s.tr('_', ' ')} log" do
let(:base_attributes) do
{
bulk_upload:,
field_4: needs_type == :supported_housing ? 2 : 1,
field_5: needs_type == :supported_housing ? "S#{scheme.id}" : nil,
field_6: needs_type == :supported_housing ? location.old_visible_id : nil,
}.compact
end
describe "#uprn" do
let(:attributes) { base_attributes.merge({ field_18: "12" }) }
it "sets to given value" do
expect(parser.log.uprn).to eql("12")
end
end
it "sets to given value" do
expect(parser.log.uprn).to eql("12")
end
end
describe "#uprn_known" do
context "when uprn specified" do
let(:attributes) { { bulk_upload:, field_4: 1, field_18: "12" } }
describe "#uprn_known" do
context "when uprn specified" do
let(:attributes) { base_attributes.merge({ field_18: "12" }) }
it "sets to 1" do
expect(parser.log.uprn_known).to be(1)
expect(parser.log.uprn_confirmed).to be(1)
end
end
it "sets to 1" do
expect(parser.log.uprn_known).to be(1)
expect(parser.log.uprn_confirmed).to be(1)
end
end
context "when uprn blank" do
let(:attributes) { { bulk_upload:, field_4: 1, field_18: "" } }
context "when uprn blank" do
let(:attributes) { base_attributes.merge({ field_18: "" }) }
it "sets to 0" do
expect(parser.log.uprn_known).to be(0)
end
end
end
it "sets to 0" do
expect(parser.log.uprn_known).to be(0)
end
end
end
describe "#address_line1" do
let(:attributes) { { bulk_upload:, field_4: 1, field_19: "123 Sesame Street" } }
describe "#address_line1" do
let(:attributes) { base_attributes.merge({ field_19: "123 Sesame Street" }) }
it "sets to given value" do
expect(parser.log.address_line1).to eql("123 Sesame Street")
end
end
it "sets to given value" do
expect(parser.log.address_line1).to eql("123 Sesame Street")
end
end
describe "#address_line2" do
let(:attributes) { { bulk_upload:, field_4: 1, field_20: "Cookie Town" } }
describe "#address_line2" do
let(:attributes) { base_attributes.merge({ field_20: "Cookie Town" }) }
it "sets to given value" do
expect(parser.log.address_line2).to eql("Cookie Town")
end
end
it "sets to given value" do
expect(parser.log.address_line2).to eql("Cookie Town")
end
end
describe "#town_or_city" do
let(:attributes) { { bulk_upload:, field_4: 1, field_21: "London" } }
describe "#town_or_city" do
let(:attributes) { base_attributes.merge({ field_21: "London" }) }
it "sets to given value" do
expect(parser.log.town_or_city).to eql("London")
end
end
it "sets to given value" do
expect(parser.log.town_or_city).to eql("London")
end
end
describe "#county" do
let(:attributes) { { bulk_upload:, field_4: 1, field_22: "Greater London" } }
describe "#county" do
let(:attributes) { base_attributes.merge({ field_22: "Greater London" }) }
it "sets to given value" do
expect(parser.log.county).to eql("Greater London")
end
end
describe "address related fields for supported housing logs" do
context "when address data is provided for a supported housing log" do
let(:attributes) { { bulk_upload:, field_4: 2, field_18: nil, field_19: "Flat 1", field_20: "Example Place", field_21: "London", field_22: "Greater London", field_23: "SW1A", field_24: "1AA" } }
it "is not set on the log" do
expect(parser.log.uprn).to be_nil
expect(parser.log.uprn_known).to be_nil
expect(parser.log.address_line1).to be_nil
expect(parser.log.address_line1_as_entered).to be_nil
expect(parser.log.address_line2).to be_nil
expect(parser.log.address_line2_as_entered).to be_nil
expect(parser.log.town_or_city).to be_nil
expect(parser.log.town_or_city_as_entered).to be_nil
expect(parser.log.county).to be_nil
expect(parser.log.county_as_entered).to be_nil
expect(parser.log.postcode_full).to be_nil
expect(parser.log.postcode_full_as_entered).to be_nil
expect(parser.log.la).to be_nil
expect(parser.log.la_as_entered).to be_nil
expect(parser.log.address_line1_input).to be_nil
expect(parser.log.postcode_full_input).to be_nil
expect(parser.log.select_best_address_match).to be_nil
it "sets to given value" do
expect(parser.log.county).to eql("Greater London")
end
end
end
end
end

Loading…
Cancel
Save