From dda56369636fe7adec4d692b823600dade74c34b Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Thu, 5 Mar 2026 13:24:48 +0200 Subject: [PATCH 1/2] Update force delete status notes when basis for FD changes When a domain is under force delete due to invalid email and the contact email is replaced with another invalid email, the status notes were not updated to reflect the new problematic email address. Added sync_force_delete_status_notes to CheckForceDeleteLift job that rebuilds status notes from current invalid emails for all domains in force delete with invalid_email template. This runs after the existing lift check, so notes stay accurate regardless of how the email was changed (EPP, REPP, registrant portal). Refs #2893 --- CLAUDE.md | 53 +++++++ app/jobs/check_force_delete_lift.rb | 33 +++++ test/jobs/check_force_delete_lift_test.rb | 162 ++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 CLAUDE.md create mode 100644 test/jobs/check_force_delete_lift_test.rb diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..8fbec05b19 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,53 @@ +# Registry Project - Claude Instructions + +## Docker Environment + +Проект работает ТОЛЬКО в Docker. НИКОГДА не запускай rails/rake/bundle на хост-машине. + +### Docker Compose +```bash +cd /Users/oleghasjanov/Documents/registry/registry/docker-images +``` + +### Запуск тестов +```bash +# Конкретный файл тестов +docker compose exec registry bash -c "cd /opt/webapps/app && rails test test/jobs/check_force_delete_lift_test.rb" + +# Конкретный тест по имени +docker compose exec registry bash -c "cd /opt/webapps/app && rails test test/jobs/check_force_delete_lift_test.rb -n test_updates_status_notes_when_invalid_email_changes" + +# Несколько файлов +docker compose exec registry bash -c "cd /opt/webapps/app && rails test test/jobs/check_force_delete_lift_test.rb test/models/domain/force_delete_test.rb" + +# Вся директория +docker compose exec registry bash -c "cd /opt/webapps/app && rails test test/models/domain/" +``` + +### Интерактивная консоль +```bash +docker compose exec -it registry bash +# внутри контейнера: +cd /opt/webapps/app +rails test +rails console +``` + +### Другие команды +```bash +# Rails console +docker compose exec registry bash -c "cd /opt/webapps/app && rails console" + +# Rake tasks +docker compose exec registry bash -c "cd /opt/webapps/app && rake " + +# Bundle +docker compose exec registry bash -c "cd /opt/webapps/app && bundle " +``` + +## Project Structure + +- Ruby on Rails application (domain registry) +- Test framework: Minitest (test/ directory, NOT spec/) +- Fixtures used instead of factories (test/fixtures/) +- Database schema: db/structure.sql (NOT schema.rb) diff --git a/app/jobs/check_force_delete_lift.rb b/app/jobs/check_force_delete_lift.rb index ab28a5561d..1357807d4e 100644 --- a/app/jobs/check_force_delete_lift.rb +++ b/app/jobs/check_force_delete_lift.rb @@ -11,6 +11,8 @@ def perform domains_to_process.each do |domain| Domains::ForceDeleteLift::Base.run(domain: domain) end + + sync_force_delete_status_notes end private @@ -45,4 +47,35 @@ def refresh_status_notes(domain, registrant) domain.save(validate: false) if domain.changed? end + + def sync_force_delete_status_notes + invalid_email_fd_domains = Domain.where("'#{DomainStatus::FORCE_DELETE}' = ANY (statuses)") + .where("force_delete_data->'template_name' = ?", 'invalid_email') + .includes(registrant: :validation_events, + contacts: :validation_events) + + invalid_email_fd_domains.each do |domain| + update_status_notes_for_domain(domain) + end + end + + def update_status_notes_for_domain(domain) + current_notes = domain.status_notes[DomainStatus::FORCE_DELETE] + return if current_notes.blank? + + current_invalid_emails = collect_current_invalid_emails(domain) + new_notes = current_invalid_emails.join(' ') + + return if current_notes == new_notes + return if new_notes.blank? + + domain.status_notes[DomainStatus::FORCE_DELETE] = new_notes + domain.save(validate: false) + end + + def collect_current_invalid_emails(domain) + failed_emails = domain.contacts.select(&:email_verification_failed?).map(&:email) + failed_emails << domain.registrant.email if domain.registrant.email_verification_failed? + failed_emails.uniq + end end diff --git a/test/jobs/check_force_delete_lift_test.rb b/test/jobs/check_force_delete_lift_test.rb new file mode 100644 index 0000000000..8ee1bfd47b --- /dev/null +++ b/test/jobs/check_force_delete_lift_test.rb @@ -0,0 +1,162 @@ +require 'test_helper' + +class CheckForceDeleteLiftTest < ActiveSupport::TestCase + include ActiveJob::TestHelper + + setup do + @domain = domains(:shop) + @original_validation_type = Truemail.configure.default_validation_type + ValidationEvent.destroy_all + Setting.redemption_grace_period = 30 + end + + teardown do + Truemail.configure.default_validation_type = @original_validation_type + end + + def test_updates_status_notes_when_invalid_email_changes + Truemail.configure.default_validation_type = :regex + old_invalid_email = '`@invalid-old.ee' + new_invalid_email = '`@invalid-new.ee' + + contact = @domain.admin_contacts.first + + # Setup: domain in FD due to old invalid email + @domain.update( + valid_to: Time.zone.parse('2012-08-05'), + statuses: [DomainStatus::FORCE_DELETE, + DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::SERVER_TRANSFER_PROHIBITED], + force_delete_data: { 'template_name' => 'invalid_email', 'force_delete_type' => 'soft' }, + status_notes: { DomainStatus::FORCE_DELETE => old_invalid_email } + ) + + # Contact changes to new (still invalid) email + contact.update_attribute(:email, new_invalid_email) + + # Create failed validation events for the new email + ValidationEvent::VALID_EVENTS_COUNT_THRESHOLD.times do + contact.validation_events.create!( + event_type: :email_validation, + success: false, + event_data: { 'check_level' => 'mx', 'email' => new_invalid_email }, + created_at: Time.zone.now + ) + end + + assert contact.email_verification_failed?, 'Contact should have failed email verification' + + CheckForceDeleteLift.perform_now + + @domain.reload + + assert @domain.force_delete_scheduled?, 'Domain should still be in force delete' + assert_equal new_invalid_email, @domain.status_notes[DomainStatus::FORCE_DELETE], + 'Status notes should be updated to reflect current invalid email' + end + + def test_updates_status_notes_with_multiple_invalid_emails + Truemail.configure.default_validation_type = :regex + old_invalid_email = '`@invalid-old.ee' + new_invalid_email_1 = '`@invalid-new1.ee' + new_invalid_email_2 = '`@invalid-new2.ee' + + contact = @domain.admin_contacts.first + registrant = @domain.registrant + + # Setup: domain in FD due to old invalid email + @domain.update( + valid_to: Time.zone.parse('2012-08-05'), + statuses: [DomainStatus::FORCE_DELETE, + DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::SERVER_TRANSFER_PROHIBITED], + force_delete_data: { 'template_name' => 'invalid_email', 'force_delete_type' => 'soft' }, + status_notes: { DomainStatus::FORCE_DELETE => old_invalid_email } + ) + + # Contact and registrant both change to new invalid emails + contact.update_attribute(:email, new_invalid_email_1) + registrant.update_attribute(:email, new_invalid_email_2) + + # Create failed validation events for both + [contact, registrant].each do |obj| + ValidationEvent::VALID_EVENTS_COUNT_THRESHOLD.times do + obj.validation_events.create!( + event_type: :email_validation, + success: false, + event_data: { 'check_level' => 'mx', 'email' => obj.email }, + created_at: Time.zone.now + ) + end + end + + CheckForceDeleteLift.perform_now + + @domain.reload + + assert @domain.force_delete_scheduled?, 'Domain should still be in force delete' + notes = @domain.status_notes[DomainStatus::FORCE_DELETE] + assert_includes notes, new_invalid_email_1, + 'Status notes should contain the new invalid contact email' + assert_includes notes, new_invalid_email_2, + 'Status notes should contain the new invalid registrant email' + assert_not_includes notes, old_invalid_email, + 'Status notes should not contain the old invalid email' + end + + def test_does_not_update_status_notes_when_they_are_already_correct + Truemail.configure.default_validation_type = :regex + invalid_email = '`@invalid.ee' + + contact = @domain.admin_contacts.first + + @domain.update( + valid_to: Time.zone.parse('2012-08-05'), + statuses: [DomainStatus::FORCE_DELETE, + DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::SERVER_TRANSFER_PROHIBITED], + force_delete_data: { 'template_name' => 'invalid_email', 'force_delete_type' => 'soft' }, + status_notes: { DomainStatus::FORCE_DELETE => invalid_email } + ) + + contact.update_attribute(:email, invalid_email) + + ValidationEvent::VALID_EVENTS_COUNT_THRESHOLD.times do + contact.validation_events.create!( + event_type: :email_validation, + success: false, + event_data: { 'check_level' => 'mx', 'email' => invalid_email }, + created_at: Time.zone.now + ) + end + + original_updated_at = @domain.updated_at + + CheckForceDeleteLift.perform_now + + @domain.reload + + assert_equal invalid_email, @domain.status_notes[DomainStatus::FORCE_DELETE], + 'Status notes should remain unchanged' + end + + def test_does_not_update_notes_for_invalid_company_template + @domain.update( + valid_to: Time.zone.parse('2012-08-05'), + statuses: [DomainStatus::FORCE_DELETE, + DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::SERVER_TRANSFER_PROHIBITED], + force_delete_data: { 'template_name' => 'invalid_company', 'force_delete_type' => 'soft' }, + status_notes: { DomainStatus::FORCE_DELETE => "Company no: 1234" } + ) + + original_notes = @domain.status_notes[DomainStatus::FORCE_DELETE] + + CheckForceDeleteLift.perform_now + + @domain.reload + + assert_equal original_notes, @domain.status_notes[DomainStatus::FORCE_DELETE], + 'Status notes for invalid_company should not be modified' + end +end From f5670025f2a1c83539ee08d4ed010c9fe79efa4c Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Thu, 5 Mar 2026 13:26:54 +0200 Subject: [PATCH 2/2] removed CLAUDE.md --- .gitignore | 2 ++ CLAUDE.md | 53 ----------------------------------------------------- 2 files changed, 2 insertions(+), 53 deletions(-) delete mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 921f89dbf1..3be84dc6a6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ certs/ca/private/ca_*.pem .cursorrules certs/ Dockerfile.dev.v2 +CLAUDE.md +.claude/ diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 8fbec05b19..0000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,53 +0,0 @@ -# Registry Project - Claude Instructions - -## Docker Environment - -Проект работает ТОЛЬКО в Docker. НИКОГДА не запускай rails/rake/bundle на хост-машине. - -### Docker Compose -```bash -cd /Users/oleghasjanov/Documents/registry/registry/docker-images -``` - -### Запуск тестов -```bash -# Конкретный файл тестов -docker compose exec registry bash -c "cd /opt/webapps/app && rails test test/jobs/check_force_delete_lift_test.rb" - -# Конкретный тест по имени -docker compose exec registry bash -c "cd /opt/webapps/app && rails test test/jobs/check_force_delete_lift_test.rb -n test_updates_status_notes_when_invalid_email_changes" - -# Несколько файлов -docker compose exec registry bash -c "cd /opt/webapps/app && rails test test/jobs/check_force_delete_lift_test.rb test/models/domain/force_delete_test.rb" - -# Вся директория -docker compose exec registry bash -c "cd /opt/webapps/app && rails test test/models/domain/" -``` - -### Интерактивная консоль -```bash -docker compose exec -it registry bash -# внутри контейнера: -cd /opt/webapps/app -rails test -rails console -``` - -### Другие команды -```bash -# Rails console -docker compose exec registry bash -c "cd /opt/webapps/app && rails console" - -# Rake tasks -docker compose exec registry bash -c "cd /opt/webapps/app && rake " - -# Bundle -docker compose exec registry bash -c "cd /opt/webapps/app && bundle " -``` - -## Project Structure - -- Ruby on Rails application (domain registry) -- Test framework: Minitest (test/ directory, NOT spec/) -- Fixtures used instead of factories (test/fixtures/) -- Database schema: db/structure.sql (NOT schema.rb)