Compare commits

...

4 Commits

Author SHA1 Message Date
Nat Dean-Lewis 64de8e6ff3 CLDC-4462: remove routing change as not needed (test update) 3 days ago
Nat Dean-Lewis be4f02be36
CLDC-4462: Remove unnecessary routing change (#3382) 3 days ago
Nat Dean-Lewis 0004559e7b
CLDC-4462: Clear confidential address data (#3378) 3 days ago
Nat Dean-Lewis 64c1bc8a6e
CLDC-4513: update rubyzip and patch breaking changes (#3381) 3 days ago
  1. 9
      Gemfile.lock
  2. 2
      app/services/exports/xml_export_service.rb
  3. 56
      lib/tasks/clear_confidential_address_data.rake
  4. 196
      spec/lib/tasks/clear_confidential_address_data_spec.rb
  5. 4
      spec/services/storage/archive_service_spec.rb

9
Gemfile.lock

@ -433,9 +433,12 @@ GEM
actionpack (>= 7.0)
railties (>= 7.0)
rexml (3.4.4)
roo (2.10.1)
roo (3.0.0)
base64 (~> 0.2)
csv (~> 3)
logger (~> 1)
nokogiri (~> 1)
rubyzip (>= 1.3.0, < 3.0.0)
rubyzip (>= 3.0.0, < 4.0.0)
rotp (6.3.0)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
@ -499,7 +502,7 @@ GEM
faraday-multipart (>= 1)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
rubyzip (3.6.0)
securerandom (0.4.1)
selenium-webdriver (4.43.0)
base64 (~> 0.2)

2
app/services/exports/xml_export_service.rb

@ -48,7 +48,7 @@ module Exports
@logger.info("Creating #{archive} - #{initial_count} resources")
return {} if initial_count.zero?
zip_file = Zip::File.open_buffer(StringIO.new)
zip_file = Zip::File.open_buffer(StringIO.new, create: true)
part_number = 1
last_processed_marker = nil

56
lib/tasks/clear_confidential_address_data.rake

@ -0,0 +1,56 @@
desc "Clears already-collected address/UPRN data for 2026/27 lettings logs in confidential (sensitive) schemes"
task clear_confidential_address_data: :environment do
year = 2026
fields_present_scope = %w[
uprn
uprn_known
uprn_confirmed
uprn_selection
address_line1
address_line2
town_or_city
county
postcode_full
postcode_known
address_line1_input
postcode_full_input
address_line1_as_entered
address_line2_as_entered
town_or_city_as_entered
county_as_entered
postcode_full_as_entered
la_as_entered
address_search_value_check
la
]
scope = LettingsLog
.filter_by_year(year)
.joins(:scheme)
.where(schemes: { sensitive: "Yes" })
.where(fields_present_scope.map { |field| "#{LettingsLog.table_name}.#{field} IS NOT NULL" }.join(" OR "))
puts "Found #{scope.count} confidential-scheme lettings logs in #{year}/#{year + 1} with address data to clear"
scope.find_each do |log|
puts "log_id=#{log.id} scheme_id=#{log.scheme_id} location_id=#{log.location_id} status=#{log.status} la=#{log.la} postcode_full=#{log.postcode_full}"
end
updated = 0
scope.find_each do |log|
original_status = log.status
fields_present_scope.each { |field| log[field] = nil }
log.is_la_inferred = log.la.present?
if log.save(validate: false)
updated += 1
puts "log_id=#{log.id} status changed: #{original_status} -> #{log.status}" if log.status != original_status
else
Rails.logger.error "CLDC-4462: failed to clear address data for log #{log.id}: #{log.errors.full_messages.join(', ')}"
end
end
puts "#{updated} logs updated"
end

196
spec/lib/tasks/clear_confidential_address_data_spec.rb

@ -0,0 +1,196 @@
require "rails_helper"
require "rake"
RSpec.describe "clear_confidential_address_data" do
describe ":clear_confidential_address_data", type: :task do
subject(:task) { Rake::Task["clear_confidential_address_data"] }
before do
Rake.application.rake_require("tasks/clear_confidential_address_data")
Rake::Task.define_task(:environment)
task.reenable
end
let(:owning_organisation) { create(:organisation) }
let(:scheme) { create(:scheme, sensitive: "Yes", owning_organisation:) }
let(:non_confidential_scheme) { create(:scheme, sensitive: "No", owning_organisation:) }
let(:location) { create(:location, scheme:) }
let(:cleared_fields) do
%w[
uprn
uprn_known
uprn_confirmed
uprn_selection
address_line1
address_line2
town_or_city
county
postcode_full
postcode_known
address_line1_input
postcode_full_input
address_line1_as_entered
address_line2_as_entered
town_or_city_as_entered
county_as_entered
postcode_full_as_entered
la_as_entered
address_search_value_check
la
]
end
def create_log_with_address_data(scheme:, location:, startdate:)
log = create(
:lettings_log,
:completed,
:sh,
:ignore_validation_errors,
owning_organisation:,
managing_organisation: owning_organisation,
scheme:,
location:,
startdate:,
)
# Bypass callbacks/validations to simulate a log that already holds property
# address/UPRN data collected before the confidential address feature existed.
log.update_columns(
uprn: "123456789012",
uprn_known: 1,
uprn_confirmed: 1,
uprn_selection: "123456789012",
address_line1: "1 Secret Street",
address_line2: "Flat 2",
town_or_city: "Secretville",
county: "Secretshire",
postcode_full: "SW1A 1AA",
postcode_known: 1,
address_line1_input: "1 Secret Street",
postcode_full_input: "SW1A1AA",
address_line1_as_entered: "1 Secret Street",
address_line2_as_entered: "Flat 2",
town_or_city_as_entered: "Secretville",
county_as_entered: "Secretshire",
postcode_full_as_entered: "SW1A 1AA",
la_as_entered: "E09000003",
address_search_value_check: 1,
la: "E09000003",
)
log
end
context "when a confidential-scheme log in 2026/27 has address data" do
let!(:log) { create_log_with_address_data(scheme:, location:, startdate: Time.zone.local(2026, 5, 1)) }
it "clears the address and UPRN fields" do
task.invoke
log.reload
expect(log.address_line1).to be_nil
expect(log.town_or_city).to be_nil
expect(log.uprn).to be_nil
expect(log.postcode_known).to be_nil
end
it "clears every field the task targets" do
task.invoke
log.reload
cleared_fields.each do |field|
expect(log[field]).to be_nil, "expected #{field} to be nil but was #{log[field].inspect}"
end
end
it "keeps the log's status unchanged when the location's LA can be inferred" do
task.invoke
expect(log.reload.status).to eq("completed")
end
it "resolves the local authority from the scheme's location without persisting it" do
task.invoke
log.reload
expect(log.la).to eq(location.location_code)
expect(log[:la]).to be_nil
expect(log.is_la_inferred).to be true
end
it "does not affect the scheme or location associations" do
task.invoke
log.reload
expect(log.scheme_id).to eq(scheme.id)
expect(log.location_id).to eq(location.id)
end
it "is idempotent" do
task.invoke
task.reenable
expect { task.invoke }.not_to(change { log.reload.updated_at })
end
end
context "when the scheme's location has no resolvable local authority" do
let!(:log) { create_log_with_address_data(scheme:, location:, startdate: Time.zone.local(2026, 5, 1)) }
before { location.update_columns(location_code: nil, location_admin_district: nil, is_la_inferred: false) }
it "clears the address fields but leaves the log's status unchanged" do
task.invoke
log.reload
expect(log.address_line1).to be_nil
expect(log.la).to be_nil
expect(log.status).to eq("completed")
end
end
context "when the confidential-scheme log is outside the 2026/27 collection year" do
let!(:log) { create_log_with_address_data(scheme:, location:, startdate: Time.zone.local(2025, 5, 1)) }
it "does not clear the address fields" do
task.invoke
log.reload
expect(log.address_line1).to eq("1 Secret Street")
expect(log.town_or_city).to eq("Secretville")
expect(log.uprn).to eq("123456789012")
expect(log.postcode_known).to eq(1)
end
end
context "when the log's scheme is not confidential" do
let!(:log) { create_log_with_address_data(scheme: non_confidential_scheme, location:, startdate: Time.zone.local(2026, 5, 1)) }
it "does not clear the address fields" do
task.invoke
log.reload
expect(log.address_line1).to eq("1 Secret Street")
expect(log.town_or_city).to eq("Secretville")
expect(log.uprn).to eq("123456789012")
expect(log.postcode_known).to eq(1)
end
end
context "when a confidential-scheme 2026/27 log has no address data left" do
let!(:log) do
create(
:lettings_log,
:ignore_validation_errors,
needstype: 2,
owning_organisation:,
managing_organisation: owning_organisation,
scheme:,
location:,
startdate: Time.zone.local(2026, 5, 1),
)
end
it "is not touched" do
expect { task.invoke }.not_to(change { log.reload.updated_at })
end
end
end
end

4
spec/services/storage/archive_service_spec.rb

@ -13,7 +13,7 @@ RSpec.describe Storage::ArchiveService do
file
end
let(:archive_content) do
zip_file = Zip::File.open_buffer(StringIO.new)
zip_file = Zip::File.open_buffer(StringIO.new, create: true)
zip_file.mkdir(compressed_folder)
zip_file.add(compressed_filepath, compressed_file)
zip_file.write_buffer
@ -51,7 +51,7 @@ RSpec.describe Storage::ArchiveService do
it "raises an error if the file exists but is too large" do
archive = archive_service.instance_variable_get(:@archive)
allow(archive).to receive(:get_entry).and_return(Zip::Entry.new(nil, "", nil, nil, nil, nil, nil, 100_000_000, nil))
allow(archive).to receive(:get_entry).and_return(Zip::Entry.new(nil, "", size: 100_000_000))
expect { archive_service.get_file_io(compressed_filepath) }
.to raise_error(RuntimeError, "File too large to be extracted")

Loading…
Cancel
Save