From 550d020229f27968fb9e68b78f6f283f891377ed Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Tue, 3 Dec 2019 14:34:40 +0500 Subject: [PATCH 01/12] Add force_delete_start field to ForceDelete & add tests Add new test file new_force_delete_test.rb to check if new ForceDeleteProcedures are correct. ATM it's just a scaffold of some sort, all tests are red, magic numbers and so - just a proof of concept. See #1428 --- app/models/concerns/domain/force_delete.rb | 9 + ...83643_add_force_delete_start_to_domains.rb | 5 + db/structure.sql | 6 +- test/models/domain/force_delete_test.rb | 203 ++++++++---------- 4 files changed, 109 insertions(+), 114 deletions(-) create mode 100644 db/migrate/20191203083643_add_force_delete_start_to_domains.rb diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index d820d8f4b..84c8a559c 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -1,6 +1,8 @@ module Concerns::Domain::ForceDelete extend ActiveSupport::Concern + DAYS_TO_START_HOLD = 15.days + def force_delete_scheduled? statuses.include?(DomainStatus::FORCE_DELETE) end @@ -25,6 +27,12 @@ module Concerns::Domain::ForceDelete save(validate: false) end + def check_hold + if force_delete_start < valid_to && (force_delete_date + DAYS_TO_START_HOLD) > Time.zone.today + statuses << DomainStatus::CLIENT_HOLD + end + end + private def stop_all_pending_actions @@ -62,6 +70,7 @@ module Concerns::Domain::ForceDelete statuses.delete(DomainStatus::SERVER_UPDATE_PROHIBITED) statuses.delete(DomainStatus::PENDING_DELETE) statuses.delete(DomainStatus::SERVER_MANUAL_INZONE) + statuses.delete(DomainStatus::CLIENT_HOLD) end def allow_deletion diff --git a/db/migrate/20191203083643_add_force_delete_start_to_domains.rb b/db/migrate/20191203083643_add_force_delete_start_to_domains.rb new file mode 100644 index 000000000..af2380539 --- /dev/null +++ b/db/migrate/20191203083643_add_force_delete_start_to_domains.rb @@ -0,0 +1,5 @@ +class AddForceDeleteStartToDomains < ActiveRecord::Migration[5.0] + def change + add_column :domains, :force_delete_start, :datetime + end +end diff --git a/db/structure.sql b/db/structure.sql index 6246862ea..dd84254bb 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -8,6 +8,7 @@ SET client_encoding = 'UTF8'; SET standard_conforming_strings = on; SELECT pg_catalog.set_config('search_path', '', false); SET check_function_bodies = false; +SET xmloption = content; SET client_min_messages = warning; -- @@ -742,7 +743,8 @@ CREATE TABLE public.domains ( upid integer, up_date timestamp without time zone, uuid uuid DEFAULT public.gen_random_uuid() NOT NULL, - locked_by_registrant_at timestamp without time zone + locked_by_registrant_at timestamp without time zone, + force_delete_start timestamp without time zone ); @@ -4334,6 +4336,8 @@ INSERT INTO "schema_migrations" (version) VALUES ('20191206183853'), ('20191212133136'), ('20191227110904'), +('20191203083643'), ('20200113091254'); + diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index 9092fad86..c481a7b97 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -1,130 +1,107 @@ -require 'test_helper' +module Concerns::Domain::ForceDelete + extend ActiveSupport::Concern -class DomainForceDeleteTest < ActiveSupport::TestCase - setup do - @domain = domains(:shop) - @original_redemption_grace_period = Setting.redemption_grace_period + DAYS_TO_START_HOLD = 15.days + + def force_delete_scheduled? + statuses.include?(DomainStatus::FORCE_DELETE) end - teardown do - Setting.redemption_grace_period = @original_redemption_grace_period - end + def schedule_force_delete(type: :fast_track) + if discarded? + raise StandardError 'Force delete procedure cannot be scheduled while a domain is discarded' + end - def test_schedules_force_delete - assert_not @domain.force_delete_scheduled? - Setting.redemption_grace_period = 30 - travel_to Time.zone.parse('2010-07-05') - - @domain.schedule_force_delete - @domain.reload - - assert @domain.force_delete_scheduled? - assert_equal Date.parse('2010-08-05'), @domain.force_delete_date - end - - def test_scheduling_force_delete_adds_corresponding_statuses - statuses_to_be_added = [ - DomainStatus::FORCE_DELETE, - DomainStatus::SERVER_RENEW_PROHIBITED, - DomainStatus::SERVER_TRANSFER_PROHIBITED, - DomainStatus::SERVER_UPDATE_PROHIBITED, - DomainStatus::PENDING_DELETE, - ] - - @domain.schedule_force_delete - @domain.reload - assert (@domain.statuses & statuses_to_be_added) == statuses_to_be_added - end - - def test_scheduling_force_delete_allows_domain_deletion - statuses_to_be_removed = [ - DomainStatus::CLIENT_DELETE_PROHIBITED, - DomainStatus::SERVER_DELETE_PROHIBITED, - ] - - @domain.statuses = statuses_to_be_removed + %w[other-status] - @domain.schedule_force_delete - @domain.reload - assert_empty @domain.statuses & statuses_to_be_removed - end - - def test_scheduling_force_delete_stops_pending_actions - statuses_to_be_removed = [ - DomainStatus::PENDING_UPDATE, - DomainStatus::PENDING_TRANSFER, - DomainStatus::PENDING_RENEW, - DomainStatus::PENDING_CREATE, - ] - - @domain.statuses = statuses_to_be_removed + %w[other-status] - @domain.schedule_force_delete - @domain.reload - assert_empty @domain.statuses & statuses_to_be_removed, 'Pending actions should be stopped' - end - - def test_scheduling_force_delete_preserves_current_statuses - @domain.statuses = %w[test1 test2] - @domain.schedule_force_delete - @domain.reload - assert_equal %w[test1 test2], @domain.statuses_before_force_delete - end - - def test_scheduling_force_delete_bypasses_validation - @domain = domains(:invalid) - @domain.schedule_force_delete - assert @domain.force_delete_scheduled? - end - - def test_force_delete_cannot_be_scheduled_when_a_domain_is_discarded - @domain.update!(statuses: [DomainStatus::DELETE_CANDIDATE]) - assert_raises StandardError do - @domain.schedule_force_delete + case type + when :fast_track + force_delete_fast_track + when :soft + force_delete_soft + else + raise StandardError, 'Wrong type for force delete' end end - def test_cancels_force_delete - @domain.update_columns(statuses: [DomainStatus::FORCE_DELETE], force_delete_date: '2010-07-05') - assert @domain.force_delete_scheduled? - - @domain.cancel_force_delete - @domain.reload - - assert_not @domain.force_delete_scheduled? - assert_nil @domain.force_delete_date + def force_delete_fast_track + preserve_current_statuses_for_force_delete + add_force_delete_statuses + self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days + self.force_delete_start = Time.zone.today + stop_all_pending_actions + allow_deletion + save(validate: false) end - def test_cancelling_force_delete_bypasses_validation - @domain = domains(:invalid) - @domain.schedule_force_delete - @domain.cancel_force_delete - assert_not @domain.force_delete_scheduled? + def force_delete_soft + preserve_current_statuses_for_force_delete + add_force_delete_statuses + calculate_soft_delete_date + stop_all_pending_actions + check_hold + allow_deletion + save(validate: false) end - def test_cancelling_force_delete_removes_statuses_that_were_set_on_force_delete - statuses = [ - DomainStatus::FORCE_DELETE, - DomainStatus::SERVER_RENEW_PROHIBITED, - DomainStatus::SERVER_TRANSFER_PROHIBITED, - DomainStatus::SERVER_UPDATE_PROHIBITED, - DomainStatus::PENDING_DELETE, - DomainStatus::SERVER_MANUAL_INZONE - ] - @domain.statuses = @domain.statuses + statuses - @domain.schedule_force_delete - - @domain.cancel_force_delete - @domain.reload - - assert_empty @domain.statuses & statuses + def cancel_force_delete + restore_statuses_before_force_delete + remove_force_delete_statuses + self.force_delete_date = nil + self.force_delete_start = nil + save(validate: false) end - def test_cancelling_force_delete_restores_statuses_that_a_domain_had_before_force_delete - @domain.statuses_before_force_delete = ['test1', DomainStatus::DELETE_CANDIDATE] + private - @domain.cancel_force_delete - @domain.reload + def check_hold + if force_delete_start.present? && + force_delete_start < valid_to && + (force_delete_date + DAYS_TO_START_HOLD) > Time.zone.today + statuses << DomainStatus::CLIENT_HOLD + end + end - assert_equal ['test1', DomainStatus::DELETE_CANDIDATE], @domain.statuses - assert_nil @domain.statuses_before_force_delete + def calculate_soft_delete_date + years = (self.valid_to.to_date - Time.zone.today).to_i / 365 + if years.positive? + self.force_delete_start = self.valid_to - years.years + self.force_delete_date = self.force_delete_start + Setting.redemption_grace_period.days + end + end + + def stop_all_pending_actions + statuses.delete(DomainStatus::PENDING_UPDATE) + statuses.delete(DomainStatus::PENDING_TRANSFER) + statuses.delete(DomainStatus::PENDING_RENEW) + statuses.delete(DomainStatus::PENDING_CREATE) + end + + def preserve_current_statuses_for_force_delete + self.statuses_before_force_delete = statuses.clone + end + + def restore_statuses_before_force_delete + self.statuses = statuses_before_force_delete + self.statuses_before_force_delete = nil + end + + def add_force_delete_statuses + statuses << DomainStatus::FORCE_DELETE + statuses << DomainStatus::SERVER_RENEW_PROHIBITED + statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED + end + + def remove_force_delete_statuses + statuses.delete(DomainStatus::FORCE_DELETE) + statuses.delete(DomainStatus::SERVER_RENEW_PROHIBITED) + statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED) + statuses.delete(DomainStatus::SERVER_UPDATE_PROHIBITED) + statuses.delete(DomainStatus::PENDING_DELETE) + statuses.delete(DomainStatus::SERVER_MANUAL_INZONE) + statuses.delete(DomainStatus::CLIENT_HOLD) + end + + def allow_deletion + statuses.delete(DomainStatus::CLIENT_DELETE_PROHIBITED) + statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED) end end From bbc89e5860173f3b9aa482c07679e97a6c707be6 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Wed, 4 Dec 2019 14:38:18 +0500 Subject: [PATCH 02/12] Add :fast_track & :soft versions of ForceDelete See #1428 --- app/models/concerns/domain/force_delete.rb | 49 +++++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index 84c8a559c..c481a7b97 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -7,33 +7,66 @@ module Concerns::Domain::ForceDelete statuses.include?(DomainStatus::FORCE_DELETE) end - def schedule_force_delete + def schedule_force_delete(type: :fast_track) if discarded? - raise StandardError, 'Force delete procedure cannot be scheduled while a domain is discarded' + raise StandardError 'Force delete procedure cannot be scheduled while a domain is discarded' end + case type + when :fast_track + force_delete_fast_track + when :soft + force_delete_soft + else + raise StandardError, 'Wrong type for force delete' + end + end + + def force_delete_fast_track preserve_current_statuses_for_force_delete add_force_delete_statuses - self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days + 1.day + self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days + self.force_delete_start = Time.zone.today stop_all_pending_actions allow_deletion save(validate: false) end + def force_delete_soft + preserve_current_statuses_for_force_delete + add_force_delete_statuses + calculate_soft_delete_date + stop_all_pending_actions + check_hold + allow_deletion + save(validate: false) + end + def cancel_force_delete restore_statuses_before_force_delete remove_force_delete_statuses self.force_delete_date = nil + self.force_delete_start = nil save(validate: false) end + private + def check_hold - if force_delete_start < valid_to && (force_delete_date + DAYS_TO_START_HOLD) > Time.zone.today + if force_delete_start.present? && + force_delete_start < valid_to && + (force_delete_date + DAYS_TO_START_HOLD) > Time.zone.today statuses << DomainStatus::CLIENT_HOLD end end - private + def calculate_soft_delete_date + years = (self.valid_to.to_date - Time.zone.today).to_i / 365 + if years.positive? + self.force_delete_start = self.valid_to - years.years + self.force_delete_date = self.force_delete_start + Setting.redemption_grace_period.days + end + end def stop_all_pending_actions statuses.delete(DomainStatus::PENDING_UPDATE) @@ -55,12 +88,6 @@ module Concerns::Domain::ForceDelete statuses << DomainStatus::FORCE_DELETE statuses << DomainStatus::SERVER_RENEW_PROHIBITED statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED - statuses << DomainStatus::SERVER_UPDATE_PROHIBITED - statuses << DomainStatus::PENDING_DELETE - - if (statuses & [DomainStatus::SERVER_HOLD, DomainStatus::CLIENT_HOLD]).empty? - statuses << DomainStatus::SERVER_MANUAL_INZONE - end end def remove_force_delete_statuses From 4b4650ee7f43d4ef405ed6a025017b3c0d371116 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Wed, 4 Dec 2019 14:45:08 +0500 Subject: [PATCH 03/12] Revert "Add :fast_track & :soft versions of ForceDelete" This reverts commit 392b145fc7dab7a1581662d377ae0c1ea79d09f3. --- app/models/concerns/domain/force_delete.rb | 49 +++++----------------- 1 file changed, 11 insertions(+), 38 deletions(-) diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index c481a7b97..84c8a559c 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -7,66 +7,33 @@ module Concerns::Domain::ForceDelete statuses.include?(DomainStatus::FORCE_DELETE) end - def schedule_force_delete(type: :fast_track) + def schedule_force_delete if discarded? - raise StandardError 'Force delete procedure cannot be scheduled while a domain is discarded' + raise StandardError, 'Force delete procedure cannot be scheduled while a domain is discarded' end - case type - when :fast_track - force_delete_fast_track - when :soft - force_delete_soft - else - raise StandardError, 'Wrong type for force delete' - end - end - - def force_delete_fast_track preserve_current_statuses_for_force_delete add_force_delete_statuses - self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days - self.force_delete_start = Time.zone.today + self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days + 1.day stop_all_pending_actions allow_deletion save(validate: false) end - def force_delete_soft - preserve_current_statuses_for_force_delete - add_force_delete_statuses - calculate_soft_delete_date - stop_all_pending_actions - check_hold - allow_deletion - save(validate: false) - end - def cancel_force_delete restore_statuses_before_force_delete remove_force_delete_statuses self.force_delete_date = nil - self.force_delete_start = nil save(validate: false) end - private - def check_hold - if force_delete_start.present? && - force_delete_start < valid_to && - (force_delete_date + DAYS_TO_START_HOLD) > Time.zone.today + if force_delete_start < valid_to && (force_delete_date + DAYS_TO_START_HOLD) > Time.zone.today statuses << DomainStatus::CLIENT_HOLD end end - def calculate_soft_delete_date - years = (self.valid_to.to_date - Time.zone.today).to_i / 365 - if years.positive? - self.force_delete_start = self.valid_to - years.years - self.force_delete_date = self.force_delete_start + Setting.redemption_grace_period.days - end - end + private def stop_all_pending_actions statuses.delete(DomainStatus::PENDING_UPDATE) @@ -88,6 +55,12 @@ module Concerns::Domain::ForceDelete statuses << DomainStatus::FORCE_DELETE statuses << DomainStatus::SERVER_RENEW_PROHIBITED statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED + statuses << DomainStatus::SERVER_UPDATE_PROHIBITED + statuses << DomainStatus::PENDING_DELETE + + if (statuses & [DomainStatus::SERVER_HOLD, DomainStatus::CLIENT_HOLD]).empty? + statuses << DomainStatus::SERVER_MANUAL_INZONE + end end def remove_force_delete_statuses From 0e1542609e26f85bbb62ecf86337e6689188720e Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Wed, 4 Dec 2019 14:54:34 +0500 Subject: [PATCH 04/12] Fix local conflict --- app/models/concerns/domain/force_delete.rb | 43 ++-- test/models/domain/force_delete_test.rb | 232 +++++++++++++-------- 2 files changed, 170 insertions(+), 105 deletions(-) diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index 84c8a559c..38875d31c 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -7,33 +7,59 @@ module Concerns::Domain::ForceDelete statuses.include?(DomainStatus::FORCE_DELETE) end - def schedule_force_delete + def schedule_force_delete(type: :fast_track) if discarded? raise StandardError, 'Force delete procedure cannot be scheduled while a domain is discarded' end + type == :fast_track ? force_delete_fast_track : force_delete_soft + end + + def force_delete_fast_track preserve_current_statuses_for_force_delete add_force_delete_statuses - self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days + 1.day + self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days + self.force_delete_start = Time.zone.today stop_all_pending_actions allow_deletion save(validate: false) end + def force_delete_soft + preserve_current_statuses_for_force_delete + add_force_delete_statuses + calculate_soft_delete_date + stop_all_pending_actions + check_hold + allow_deletion + save(validate: false) + end + def cancel_force_delete restore_statuses_before_force_delete remove_force_delete_statuses self.force_delete_date = nil + self.force_delete_start = nil save(validate: false) end + private + def check_hold - if force_delete_start < valid_to && (force_delete_date + DAYS_TO_START_HOLD) > Time.zone.today + if force_delete_start.present? && force_delete_start + DAYS_TO_START_HOLD < valid_to statuses << DomainStatus::CLIENT_HOLD end end - private + def calculate_soft_delete_date + years = (valid_to.to_date - Time.zone.today).to_i / 365 + soft_delete_dates(years) if years.positive? + end + + def soft_delete_dates(years) + self.force_delete_start = valid_to - years.years + self.force_delete_date = force_delete_start + Setting.redemption_grace_period.days + end def stop_all_pending_actions statuses.delete(DomainStatus::PENDING_UPDATE) @@ -55,21 +81,12 @@ module Concerns::Domain::ForceDelete statuses << DomainStatus::FORCE_DELETE statuses << DomainStatus::SERVER_RENEW_PROHIBITED statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED - statuses << DomainStatus::SERVER_UPDATE_PROHIBITED - statuses << DomainStatus::PENDING_DELETE - - if (statuses & [DomainStatus::SERVER_HOLD, DomainStatus::CLIENT_HOLD]).empty? - statuses << DomainStatus::SERVER_MANUAL_INZONE - end end def remove_force_delete_statuses statuses.delete(DomainStatus::FORCE_DELETE) statuses.delete(DomainStatus::SERVER_RENEW_PROHIBITED) statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED) - statuses.delete(DomainStatus::SERVER_UPDATE_PROHIBITED) - statuses.delete(DomainStatus::PENDING_DELETE) - statuses.delete(DomainStatus::SERVER_MANUAL_INZONE) statuses.delete(DomainStatus::CLIENT_HOLD) end diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index c481a7b97..1bbbde206 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -1,107 +1,155 @@ -module Concerns::Domain::ForceDelete - extend ActiveSupport::Concern +require 'test_helper' - DAYS_TO_START_HOLD = 15.days - - def force_delete_scheduled? - statuses.include?(DomainStatus::FORCE_DELETE) +class NewDomainForceDeleteTest < ActiveSupport::TestCase + setup do + @domain = domains(:shop) + Setting.redemption_grace_period = 45 end - def schedule_force_delete(type: :fast_track) - if discarded? - raise StandardError 'Force delete procedure cannot be scheduled while a domain is discarded' - end + def test_schedules_force_delete_fast_track + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') - case type - when :fast_track - force_delete_fast_track - when :soft - force_delete_soft - else - raise StandardError, 'Wrong type for force delete' + @domain.schedule_force_delete(type: :fast_track) + @domain.reload + + assert @domain.force_delete_scheduled? + assert_equal Date.parse('2010-08-19'), @domain.force_delete_date.to_date + assert_equal Date.parse('2010-07-05'), @domain.force_delete_start.to_date + end + + def test_schedules_force_delete_soft_year_ahead + @domain.update(valid_to: Time.zone.parse('2012-08-05')) + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + + @domain.schedule_force_delete(type: :soft) + @domain.reload + + assert @domain.force_delete_scheduled? + assert_equal Date.parse('2010-09-19'), @domain.force_delete_date.to_date + assert_equal Date.parse('2010-08-05'), @domain.force_delete_start.to_date + assert (@domain.statuses.include?(DomainStatus::CLIENT_HOLD)) + end + + def test_schedules_force_delete_soft_less_than_year_ahead + @domain.update_columns(valid_to: Time.zone.parse('2010-08-05'), + force_delete_date: nil) + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + + @domain.schedule_force_delete(type: :soft) + @domain.reload + + assert @domain.force_delete_scheduled? + assert_nil @domain.force_delete_date + assert_nil @domain.force_delete_start + end + + def test_scheduling_soft_force_delete_adds_corresponding_statuses + statuses_to_be_added = [ + DomainStatus::FORCE_DELETE, + DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::SERVER_TRANSFER_PROHIBITED, + ] + + @domain.schedule_force_delete(type: :soft) + @domain.reload + assert (@domain.statuses & statuses_to_be_added) == statuses_to_be_added + end + + def test_scheduling_fast_track_force_delete_adds_corresponding_statuses + statuses_to_be_added = [ + DomainStatus::FORCE_DELETE, + DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::SERVER_TRANSFER_PROHIBITED, + ] + + @domain.schedule_force_delete(type: :fast_track) + @domain.reload + assert (@domain.statuses & statuses_to_be_added) == statuses_to_be_added + end + + def test_scheduling_force_delete_allows_domain_deletion + statuses_to_be_removed = [ + DomainStatus::CLIENT_DELETE_PROHIBITED, + DomainStatus::SERVER_DELETE_PROHIBITED, + ] + + @domain.statuses = statuses_to_be_removed + %w[other-status] + @domain.schedule_force_delete(type: :fast_track) + @domain.reload + assert_empty @domain.statuses & statuses_to_be_removed + end + + def test_scheduling_force_delete_stops_pending_actions + Setting.redemption_grace_period = 45 + statuses_to_be_removed = [ + DomainStatus::PENDING_UPDATE, + DomainStatus::PENDING_TRANSFER, + DomainStatus::PENDING_RENEW, + DomainStatus::PENDING_CREATE, + ] + + @domain.statuses = statuses_to_be_removed + %w[other-status] + @domain.schedule_force_delete(type: :fast_track) + @domain.reload + assert_empty @domain.statuses & statuses_to_be_removed, 'Pending actions should be stopped' + end + + def test_scheduling_force_delete_preserves_current_statuses + @domain.statuses = %w[test1 test2] + @domain.schedule_force_delete(type: :fast_track) + @domain.reload + assert_equal %w[test1 test2], @domain.statuses_before_force_delete + end + + def test_scheduling_force_delete_bypasses_validation + @domain = domains(:invalid) + @domain.schedule_force_delete(type: :fast_track) + assert @domain.force_delete_scheduled? + end + + def test_force_delete_cannot_be_scheduled_when_a_domain_is_discarded + @domain.update!(statuses: [DomainStatus::DELETE_CANDIDATE]) + assert_raises StandardError do + @domain.schedule_force_delete(type: :fast_track) end end - def force_delete_fast_track - preserve_current_statuses_for_force_delete - add_force_delete_statuses - self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days - self.force_delete_start = Time.zone.today - stop_all_pending_actions - allow_deletion - save(validate: false) + def test_cancels_force_delete + @domain.update_columns(statuses: [DomainStatus::FORCE_DELETE], + force_delete_date: Time.zone.parse('2010-07-05'), + force_delete_start: Time.zone.parse('2010-07-05') - 45.days) + assert @domain.force_delete_scheduled? + + @domain.cancel_force_delete + @domain.reload + + assert_not @domain.force_delete_scheduled? + assert_nil @domain.force_delete_date + assert_nil @domain.force_delete_start end - def force_delete_soft - preserve_current_statuses_for_force_delete - add_force_delete_statuses - calculate_soft_delete_date - stop_all_pending_actions - check_hold - allow_deletion - save(validate: false) + def test_cancelling_force_delete_bypasses_validation + @domain = domains(:invalid) + @domain.schedule_force_delete(type: :fast_track) + @domain.cancel_force_delete + assert_not @domain.force_delete_scheduled? end - def cancel_force_delete - restore_statuses_before_force_delete - remove_force_delete_statuses - self.force_delete_date = nil - self.force_delete_start = nil - save(validate: false) - end + def test_cancelling_force_delete_removes_statuses_that_were_set_on_force_delete + statuses = [ + DomainStatus::FORCE_DELETE, + DomainStatus::SERVER_RENEW_PROHIBITED, + DomainStatus::SERVER_TRANSFER_PROHIBITED, + ] + @domain.statuses = @domain.statuses + statuses + @domain.schedule_force_delete(type: :fast_track) - private + @domain.cancel_force_delete + @domain.reload - def check_hold - if force_delete_start.present? && - force_delete_start < valid_to && - (force_delete_date + DAYS_TO_START_HOLD) > Time.zone.today - statuses << DomainStatus::CLIENT_HOLD - end - end - - def calculate_soft_delete_date - years = (self.valid_to.to_date - Time.zone.today).to_i / 365 - if years.positive? - self.force_delete_start = self.valid_to - years.years - self.force_delete_date = self.force_delete_start + Setting.redemption_grace_period.days - end - end - - def stop_all_pending_actions - statuses.delete(DomainStatus::PENDING_UPDATE) - statuses.delete(DomainStatus::PENDING_TRANSFER) - statuses.delete(DomainStatus::PENDING_RENEW) - statuses.delete(DomainStatus::PENDING_CREATE) - end - - def preserve_current_statuses_for_force_delete - self.statuses_before_force_delete = statuses.clone - end - - def restore_statuses_before_force_delete - self.statuses = statuses_before_force_delete - self.statuses_before_force_delete = nil - end - - def add_force_delete_statuses - statuses << DomainStatus::FORCE_DELETE - statuses << DomainStatus::SERVER_RENEW_PROHIBITED - statuses << DomainStatus::SERVER_TRANSFER_PROHIBITED - end - - def remove_force_delete_statuses - statuses.delete(DomainStatus::FORCE_DELETE) - statuses.delete(DomainStatus::SERVER_RENEW_PROHIBITED) - statuses.delete(DomainStatus::SERVER_TRANSFER_PROHIBITED) - statuses.delete(DomainStatus::SERVER_UPDATE_PROHIBITED) - statuses.delete(DomainStatus::PENDING_DELETE) - statuses.delete(DomainStatus::SERVER_MANUAL_INZONE) - statuses.delete(DomainStatus::CLIENT_HOLD) - end - - def allow_deletion - statuses.delete(DomainStatus::CLIENT_DELETE_PROHIBITED) - statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED) + assert_empty @domain.statuses & statuses end end From b9575661eb2d45f444e0fae4b60ee1ce721ab528 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Thu, 5 Dec 2019 12:12:49 +0500 Subject: [PATCH 05/12] Add both types of delete procedure to the view/controller --- .../admin/domains/force_delete_controller.rb | 10 +++++++++- .../admin/domains/_force_delete_dialog.html.erb | 12 ++++++++++++ config/locales/admin/domains.en.yml | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/domains/force_delete_controller.rb b/app/controllers/admin/domains/force_delete_controller.rb index b01962fc7..36835bfe5 100644 --- a/app/controllers/admin/domains/force_delete_controller.rb +++ b/app/controllers/admin/domains/force_delete_controller.rb @@ -5,7 +5,7 @@ module Admin authorize! :manage, domain domain.transaction do - domain.schedule_force_delete + domain.schedule_force_delete(type: force_delete_type) domain.registrar.notifications.create!(text: t('force_delete_set_on_domain', domain_name: domain.name)) @@ -35,6 +35,14 @@ module Admin def notify_by_email? ActiveRecord::Type::Boolean.new.cast(params[:notify_by_email]) end + + def force_delete_type + soft_delete? ? :soft : :fast_track + end + + def soft_delete? + ActiveRecord::Type::Boolean.new.cast(params[:soft_delete]) + end end end end diff --git a/app/views/admin/domains/_force_delete_dialog.html.erb b/app/views/admin/domains/_force_delete_dialog.html.erb index 26edf3756..6287abffe 100644 --- a/app/views/admin/domains/_force_delete_dialog.html.erb +++ b/app/views/admin/domains/_force_delete_dialog.html.erb @@ -10,6 +10,18 @@ <%= form_tag admin_domain_force_delete_path(domain), id: 'domain-force-delete-form', class: 'modal-body form-horizontal' do %> +
+
+
+ +
+
+
+
diff --git a/config/locales/admin/domains.en.yml b/config/locales/admin/domains.en.yml index 2abdaee91..c6e96bb15 100644 --- a/config/locales/admin/domains.en.yml +++ b/config/locales/admin/domains.en.yml @@ -31,6 +31,7 @@ en: force_delete_dialog: title: Force delete notify_by_email: Notify registrant and administrative contacts by email + use_soft_delete: Use soft delete procedure email_template: Email template close_btn: Close dialog submit_btn: Force delete domain From a815e943d3ede98125630deef11257a9a8283ffe Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Mon, 16 Dec 2019 16:50:25 +0500 Subject: [PATCH 06/12] Modify force_delete procedure to set clientHold only by job --- app/models/concerns/domain/force_delete.rb | 35 ++++++++++++------ app/models/domain_cron.rb | 42 ++++++++++++++++++++++ db/structure.sql | 2 +- test/models/domain/force_delete_test.rb | 3 +- 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index 38875d31c..81b792106 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -1,12 +1,29 @@ module Concerns::Domain::ForceDelete extend ActiveSupport::Concern - DAYS_TO_START_HOLD = 15.days + class_methods do + def force_delete_scheduled + where('force_delete_start <= ?', Time.zone.now) + end + end def force_delete_scheduled? statuses.include?(DomainStatus::FORCE_DELETE) end + def client_holdable? + force_delete_scheduled? && !statuses.include?(DomainStatus::CLIENT_HOLD) && + force_delete_start.present? && force_delete_lte_today && force_delete_lte_valid_date + end + + def force_delete_lte_today + force_delete_start + Setting.expire_warning_period.days <= Time.zone.now + end + + def force_delete_lte_valid_date + force_delete_start + Setting.expire_warning_period.days <= valid_to + end + def schedule_force_delete(type: :fast_track) if discarded? raise StandardError, 'Force delete procedure cannot be scheduled while a domain is discarded' @@ -18,7 +35,7 @@ module Concerns::Domain::ForceDelete def force_delete_fast_track preserve_current_statuses_for_force_delete add_force_delete_statuses - self.force_delete_date = Time.zone.today + Setting.redemption_grace_period.days + self.force_delete_date = force_delete_fast_track_start_date self.force_delete_start = Time.zone.today stop_all_pending_actions allow_deletion @@ -30,7 +47,6 @@ module Concerns::Domain::ForceDelete add_force_delete_statuses calculate_soft_delete_date stop_all_pending_actions - check_hold allow_deletion save(validate: false) end @@ -45,12 +61,6 @@ module Concerns::Domain::ForceDelete private - def check_hold - if force_delete_start.present? && force_delete_start + DAYS_TO_START_HOLD < valid_to - statuses << DomainStatus::CLIENT_HOLD - end - end - def calculate_soft_delete_date years = (valid_to.to_date - Time.zone.today).to_i / 365 soft_delete_dates(years) if years.positive? @@ -58,7 +68,8 @@ module Concerns::Domain::ForceDelete def soft_delete_dates(years) self.force_delete_start = valid_to - years.years - self.force_delete_date = force_delete_start + Setting.redemption_grace_period.days + self.force_delete_date = force_delete_start + Setting.expire_warning_period.days + + Setting.redemption_grace_period.days end def stop_all_pending_actions @@ -94,4 +105,8 @@ module Concerns::Domain::ForceDelete statuses.delete(DomainStatus::CLIENT_DELETE_PROHIBITED) statuses.delete(DomainStatus::SERVER_DELETE_PROHIBITED) end + + def force_delete_fast_track_start_date + Time.zone.today + Setting.expire_warning_period.days + Setting.redemption_grace_period.days + end end diff --git a/app/models/domain_cron.rb b/app/models/domain_cron.rb index 578538e17..e3edf9f4c 100644 --- a/app/models/domain_cron.rb +++ b/app/models/domain_cron.rb @@ -78,4 +78,46 @@ class DomainCron STDOUT << "#{Time.zone.now.utc} - Successfully set server_hold to #{marked} of #{real} domains\n" unless Rails.env.test? marked end + + def self.start_client_hold + log_prepare_client_hold + + ::PaperTrail.whodunnit = "cron - #{__method__}" + + marked = 0 + real = 0 + + Domain.force_delete_scheduled.each do |domain| + next unless domain.client_holdable? + + real += 1 + domain.statuses << DomainStatus::CLIENT_HOLD + log_start_client_hold(domain) + + domain.save(validate: false) and marked += 1 + end + + log_end_end_client_hold(marked: marked, real: real) + marked + end + + def self.log_prepare_client_hold + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} - Setting client_hold to domains\n" + end + + def self.log_start_client_hold(domain) + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} DomainCron.start_client_hold: ##{domain.id} (#{domain.name})"\ + "#{domain.changes}\n" + end + + def self.log_end_end_client_hold(marked:, real:) + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} - Successfully set client_hold to #{marked} of #{real} "\ + "domains\n" + end end diff --git a/db/structure.sql b/db/structure.sql index dd84254bb..f7401285a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -4337,7 +4337,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20191212133136'), ('20191227110904'), ('20191203083643'), +('20191206183853'), ('20200113091254'); - diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index 1bbbde206..be97c0f38 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -3,7 +3,7 @@ require 'test_helper' class NewDomainForceDeleteTest < ActiveSupport::TestCase setup do @domain = domains(:shop) - Setting.redemption_grace_period = 45 + Setting.redemption_grace_period = 30 end def test_schedules_force_delete_fast_track @@ -29,7 +29,6 @@ class NewDomainForceDeleteTest < ActiveSupport::TestCase assert @domain.force_delete_scheduled? assert_equal Date.parse('2010-09-19'), @domain.force_delete_date.to_date assert_equal Date.parse('2010-08-05'), @domain.force_delete_start.to_date - assert (@domain.statuses.include?(DomainStatus::CLIENT_HOLD)) end def test_schedules_force_delete_soft_less_than_year_ahead From d20821dd38aa02f6f02ac513b6a0238ab903c002 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Tue, 17 Dec 2019 12:52:21 +0500 Subject: [PATCH 07/12] Add tests for clientHold status --- test/models/domain/force_delete_test.rb | 60 +++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index be97c0f38..a36615113 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -151,4 +151,64 @@ class NewDomainForceDeleteTest < ActiveSupport::TestCase assert_empty @domain.statuses & statuses end + + def test_force_delete_soft_year_ahead_sets_client_hold + asserted_status = DomainStatus::CLIENT_HOLD + + @domain.update(valid_to: Time.zone.parse('2012-08-05')) + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + @domain.schedule_force_delete(type: :soft) + + travel_to Time.zone.parse('2010-08-20') + DomainCron.start_client_hold + @domain.reload + assert_includes(@domain.statuses, asserted_status) + end + + def test_force_delete_soft_year_ahead_not_sets_client_hold_before_threshold + asserted_status = DomainStatus::CLIENT_HOLD + + @domain.update_columns(valid_to: Time.zone.parse('2010-08-05'), + force_delete_date: nil) + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + @domain.schedule_force_delete(type: :soft) + + travel_to Time.zone.parse('2010-07-06') + DomainCron.start_client_hold + @domain.reload + + assert_not_includes(@domain.statuses, asserted_status) + end + + def test_force_delete_fast_track_sets_client_hold + asserted_status = DomainStatus::CLIENT_HOLD + @domain.update_columns(valid_to: Time.zone.parse('2010-10-05'), + force_delete_date: nil) + + travel_to Time.zone.parse('2010-07-05') + + @domain.schedule_force_delete(type: :fast_track) + travel_to Time.zone.parse('2010-07-25') + DomainCron.start_client_hold + @domain.reload + + assert_includes(@domain.statuses, asserted_status) + end + + def test_not_sets_hold_before_treshold + asserted_status = DomainStatus::CLIENT_HOLD + @domain.update_columns(valid_to: Time.zone.parse('2010-10-05'), + force_delete_date: nil) + + travel_to Time.zone.parse('2010-07-05') + + @domain.schedule_force_delete(type: :fast_track) + travel_to Time.zone.parse('2010-07-06') + DomainCron.start_client_hold + @domain.reload + + assert_not_includes(@domain.statuses, asserted_status) + end end From c863cb26786e0c80d38e8099ec8a19a92efad32b Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Thu, 19 Dec 2019 17:09:40 +0500 Subject: [PATCH 08/12] Move force delete dates to one day ahead --- app/models/concerns/domain/force_delete.rb | 6 +++--- db/structure.sql | 2 -- test/models/domain/force_delete_test.rb | 10 +++++----- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index 81b792106..c8cbe6b0a 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -35,8 +35,8 @@ module Concerns::Domain::ForceDelete def force_delete_fast_track preserve_current_statuses_for_force_delete add_force_delete_statuses - self.force_delete_date = force_delete_fast_track_start_date - self.force_delete_start = Time.zone.today + self.force_delete_date = force_delete_fast_track_start_date + 1.day + self.force_delete_start = Time.zone.today + 1.day stop_all_pending_actions allow_deletion save(validate: false) @@ -67,7 +67,7 @@ module Concerns::Domain::ForceDelete end def soft_delete_dates(years) - self.force_delete_start = valid_to - years.years + self.force_delete_start = valid_to - years.years + 1.day self.force_delete_date = force_delete_start + Setting.expire_warning_period.days + Setting.redemption_grace_period.days end diff --git a/db/structure.sql b/db/structure.sql index f7401285a..7d15804a4 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -4336,8 +4336,6 @@ INSERT INTO "schema_migrations" (version) VALUES ('20191206183853'), ('20191212133136'), ('20191227110904'), -('20191203083643'), -('20191206183853'), ('20200113091254'); diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index a36615113..f59cf58bd 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -14,8 +14,8 @@ class NewDomainForceDeleteTest < ActiveSupport::TestCase @domain.reload assert @domain.force_delete_scheduled? - assert_equal Date.parse('2010-08-19'), @domain.force_delete_date.to_date - assert_equal Date.parse('2010-07-05'), @domain.force_delete_start.to_date + assert_equal Date.parse('2010-08-20'), @domain.force_delete_date.to_date + assert_equal Date.parse('2010-07-06'), @domain.force_delete_start.to_date end def test_schedules_force_delete_soft_year_ahead @@ -27,8 +27,8 @@ class NewDomainForceDeleteTest < ActiveSupport::TestCase @domain.reload assert @domain.force_delete_scheduled? - assert_equal Date.parse('2010-09-19'), @domain.force_delete_date.to_date - assert_equal Date.parse('2010-08-05'), @domain.force_delete_start.to_date + assert_equal Date.parse('2010-09-20'), @domain.force_delete_date.to_date + assert_equal Date.parse('2010-08-06'), @domain.force_delete_start.to_date end def test_schedules_force_delete_soft_less_than_year_ahead @@ -160,7 +160,7 @@ class NewDomainForceDeleteTest < ActiveSupport::TestCase travel_to Time.zone.parse('2010-07-05') @domain.schedule_force_delete(type: :soft) - travel_to Time.zone.parse('2010-08-20') + travel_to Time.zone.parse('2010-08-21') DomainCron.start_client_hold @domain.reload assert_includes(@domain.statuses, asserted_status) From 222ac639acc63da5dad12c541bc0b55cfaddee3d Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Mon, 13 Jan 2020 16:40:40 +0500 Subject: [PATCH 09/12] Add pull messages on soft force delete & force delete cancel --- app/models/concerns/domain/force_delete.rb | 1 + app/models/domain_cron.rb | 8 +++++++- config/locales/en.yml | 2 ++ config/schedule.rb | 4 ++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index c8cbe6b0a..d09ea3704 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -57,6 +57,7 @@ module Concerns::Domain::ForceDelete self.force_delete_date = nil self.force_delete_start = nil save(validate: false) + registrar.notifications.create!(text: I18n.t('force_delete_cancelled', domain_name: name)) end private diff --git a/app/models/domain_cron.rb b/app/models/domain_cron.rb index e3edf9f4c..154c4ca19 100644 --- a/app/models/domain_cron.rb +++ b/app/models/domain_cron.rb @@ -94,7 +94,7 @@ class DomainCron domain.statuses << DomainStatus::CLIENT_HOLD log_start_client_hold(domain) - domain.save(validate: false) and marked += 1 + domain.save(validate: false) and marked += 1 and notify_client_hold(domain) end log_end_end_client_hold(marked: marked, real: real) @@ -120,4 +120,10 @@ class DomainCron STDOUT << "#{Time.zone.now.utc} - Successfully set client_hold to #{marked} of #{real} "\ "domains\n" end + + def self.notify_client_hold(domain) + domain.registrar.notifications.create!(text: I18n.t('soft_force_delete_set_on_domain', + domain_name: domain.name, + start_date: domain.force_delete_start)) + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 75f9a6542..b4a5d1c18 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -619,6 +619,8 @@ en: created_at_until: 'Created at until' is_registrant: 'Is registrant' force_delete_set_on_domain: 'Force delete set on domain %{domain_name}' + soft_force_delete_set_on_domain: 'clientHold status is set for domain %{domain_name}, ForceDelete is in effect from %{start_date}' + force_delete_cancelled: 'Force delete is cancelled on domain %{domain_name}' contact_is_not_valid: 'Contact %{value} is not valid, please fix the invalid contact' next: 'Next' previous: 'Previous' diff --git a/config/schedule.rb b/config/schedule.rb index fe920dc6d..07a04fa39 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -46,6 +46,10 @@ if @cron_group == 'registry' runner 'DomainCron.start_redemption_grace_period' end + every 1.hour do + runner 'DomainCron.start_client_hold' + end + every '0 0 1 * *' do runner 'Directo.send_monthly_invoices' end From 9916ca52cbdcfc4771898dedc51e74b505450ecb Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Tue, 14 Jan 2020 17:03:23 +0500 Subject: [PATCH 10/12] Add poll message on start of grace period for soft delete Add removing forceDelete on registrant change. Restore email template choosing on safe force delete via admin dashboard. See https://github.com/internetee/registry/issues/1428#issuecomment-573766153 --- app/models/concerns/domain/force_delete.rb | 5 ++ app/models/concerns/job/force_delete.rb | 31 ++++++++++++ .../concerns/job/force_delete_messages.rb | 40 +++++++++++++++ app/models/domain.rb | 2 + app/models/domain_cron.rb | 50 +------------------ .../domains/_force_delete_dialog.html.erb | 3 +- config/locales/en.yml | 3 +- config/schedule.rb | 2 +- 8 files changed, 84 insertions(+), 52 deletions(-) create mode 100644 app/models/concerns/job/force_delete.rb create mode 100644 app/models/concerns/job/force_delete_messages.rb diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index d09ea3704..ffe997322 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -11,6 +11,11 @@ module Concerns::Domain::ForceDelete statuses.include?(DomainStatus::FORCE_DELETE) end + def should_notify_on_soft_force_delete? + force_delete_scheduled? && !statuses.include?(DomainStatus::CLIENT_HOLD) && + force_delete_start.present? && force_delete_start.to_date == Time.zone.now.to_date + end + def client_holdable? force_delete_scheduled? && !statuses.include?(DomainStatus::CLIENT_HOLD) && force_delete_start.present? && force_delete_lte_today && force_delete_lte_valid_date diff --git a/app/models/concerns/job/force_delete.rb b/app/models/concerns/job/force_delete.rb new file mode 100644 index 000000000..727ddaa3b --- /dev/null +++ b/app/models/concerns/job/force_delete.rb @@ -0,0 +1,31 @@ +module Concerns + module Job + module ForceDelete + extend ActiveSupport::Concern + + class_methods do + def start_client_hold + log_prepare_client_hold + + ::PaperTrail.whodunnit = "cron - #{__method__}" + + ::Domain.force_delete_scheduled.each do |domain| + proceed_client_hold(domain: domain) + log_end_end_client_hold(domain) + end + end + + def proceed_client_hold(domain:) + notify_on_grace_period(domain) if domain.should_notify_on_soft_force_delete? + return unless domain.client_holdable? + + domain.statuses << DomainStatus::CLIENT_HOLD + log_start_client_hold(domain) + + domain.save(validate: false) + notify_client_hold(domain) + end + end + end + end +end diff --git a/app/models/concerns/job/force_delete_messages.rb b/app/models/concerns/job/force_delete_messages.rb new file mode 100644 index 000000000..cb4f298a8 --- /dev/null +++ b/app/models/concerns/job/force_delete_messages.rb @@ -0,0 +1,40 @@ +module Concerns + module Job + module ForceDeleteMessages + extend ActiveSupport::Concern + + class_methods do + def log_prepare_client_hold + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} - Setting client_hold to domains\n" + end + + def log_start_client_hold(domain) + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} DomainCron.start_client_hold: ##{domain.id} "\ + "(#{domain.name}) #{domain.changes}\n" + end + + def log_end_end_client_hold(domain) + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} - Successfully set client_hold on (#{domain.name})" + end + + def notify_client_hold(domain) + domain.registrar.notifications.create!(text: I18n.t('client_hold_set_on_domain', + domain_name: domain.name, + date: domain.force_delete_start)) + end + + def notify_on_grace_period(domain) + domain.registrar.notifications.create!(text: I18n.t('grace_period_started_domain', + domain_name: domain.name, + date: domain.force_delete_start)) + end + end + end + end +end diff --git a/app/models/domain.rb b/app/models/domain.rb index 50f41c38a..734243c0d 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -547,6 +547,8 @@ class Domain < ApplicationRecord activate if nameservers.reject(&:marked_for_destruction?).size >= Setting.ns_min_count end + cancel_force_delete if force_delete_scheduled? && pending_json['new_registrant_id'] + if statuses.empty? && valid? statuses << DomainStatus::OK elsif (statuses.length > 1 && active?) || !valid? diff --git a/app/models/domain_cron.rb b/app/models/domain_cron.rb index 154c4ca19..47e5a99b5 100644 --- a/app/models/domain_cron.rb +++ b/app/models/domain_cron.rb @@ -1,4 +1,6 @@ class DomainCron + include Concerns::Job::ForceDelete + include Concerns::Job::ForceDeleteMessages def self.clean_expired_pendings STDOUT << "#{Time.zone.now.utc} - Clean expired domain pendings\n" unless Rails.env.test? @@ -78,52 +80,4 @@ class DomainCron STDOUT << "#{Time.zone.now.utc} - Successfully set server_hold to #{marked} of #{real} domains\n" unless Rails.env.test? marked end - - def self.start_client_hold - log_prepare_client_hold - - ::PaperTrail.whodunnit = "cron - #{__method__}" - - marked = 0 - real = 0 - - Domain.force_delete_scheduled.each do |domain| - next unless domain.client_holdable? - - real += 1 - domain.statuses << DomainStatus::CLIENT_HOLD - log_start_client_hold(domain) - - domain.save(validate: false) and marked += 1 and notify_client_hold(domain) - end - - log_end_end_client_hold(marked: marked, real: real) - marked - end - - def self.log_prepare_client_hold - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} - Setting client_hold to domains\n" - end - - def self.log_start_client_hold(domain) - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} DomainCron.start_client_hold: ##{domain.id} (#{domain.name})"\ - "#{domain.changes}\n" - end - - def self.log_end_end_client_hold(marked:, real:) - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} - Successfully set client_hold to #{marked} of #{real} "\ - "domains\n" - end - - def self.notify_client_hold(domain) - domain.registrar.notifications.create!(text: I18n.t('soft_force_delete_set_on_domain', - domain_name: domain.name, - start_date: domain.force_delete_start)) - end end diff --git a/app/views/admin/domains/_force_delete_dialog.html.erb b/app/views/admin/domains/_force_delete_dialog.html.erb index 6287abffe..a76c14edd 100644 --- a/app/views/admin/domains/_force_delete_dialog.html.erb +++ b/app/views/admin/domains/_force_delete_dialog.html.erb @@ -14,8 +14,7 @@
diff --git a/config/locales/en.yml b/config/locales/en.yml index b4a5d1c18..0d76a030d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -619,7 +619,8 @@ en: created_at_until: 'Created at until' is_registrant: 'Is registrant' force_delete_set_on_domain: 'Force delete set on domain %{domain_name}' - soft_force_delete_set_on_domain: 'clientHold status is set for domain %{domain_name}, ForceDelete is in effect from %{start_date}' + client_hold_set_on_domain: 'clientHold status is set for domain %{domain_name}, ForceDelete is in effect from %{date}' + grace_period_started_domain: 'For domain %{domain_name} started 45-days redemption grace period, ForceDelete will be in effect from %{date}' force_delete_cancelled: 'Force delete is cancelled on domain %{domain_name}' contact_is_not_valid: 'Contact %{value} is not valid, please fix the invalid contact' next: 'Next' diff --git a/config/schedule.rb b/config/schedule.rb index 07a04fa39..089ce93f9 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -46,7 +46,7 @@ if @cron_group == 'registry' runner 'DomainCron.start_redemption_grace_period' end - every 1.hour do + every 1.day do runner 'DomainCron.start_client_hold' end From c252d801f98bd1b10004a9f407625083fcce8ed0 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Wed, 15 Jan 2020 16:00:56 +0500 Subject: [PATCH 11/12] Add mail notification on soft force delete --- .../admin/domains/force_delete_controller.rb | 23 ++++++++++---- .../registrant/domains_controller.rb | 2 +- app/models/concerns/domain/force_delete.rb | 29 ++++++++++++++++-- .../concerns/job/force_delete_logging.rb | 28 +++++++++++++++++ ...ete_messages.rb => force_delete_notify.rb} | 30 +++++++------------ app/models/domain_cron.rb | 3 +- ...102202_add_force_delete_data_to_domains.rb | 5 ++++ db/structure.sql | 7 +++-- test/models/domain/force_delete_test.rb | 1 + .../admin_area/domains/force_delete_test.rb | 3 +- 10 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 app/models/concerns/job/force_delete_logging.rb rename app/models/concerns/job/{force_delete_messages.rb => force_delete_notify.rb} (56%) create mode 100644 db/migrate/20200115102202_add_force_delete_data_to_domains.rb diff --git a/app/controllers/admin/domains/force_delete_controller.rb b/app/controllers/admin/domains/force_delete_controller.rb index 36835bfe5..28a058b4d 100644 --- a/app/controllers/admin/domains/force_delete_controller.rb +++ b/app/controllers/admin/domains/force_delete_controller.rb @@ -9,17 +9,21 @@ module Admin domain.registrar.notifications.create!(text: t('force_delete_set_on_domain', domain_name: domain.name)) - if notify_by_email? - DomainDeleteMailer.forced(domain: domain, - registrar: domain.registrar, - registrant: domain.registrant, - template_name: params[:template_name]).deliver_now - end + notify_by_email if notify_by_email? end redirect_to edit_admin_domain_url(domain), notice: t('.scheduled') end + def notify_by_email + if force_delete_type == :fast_track + send_email + domain.update(contact_notification_sent_date: Time.zone.today) + else + domain.update(template_name: params[:template_name]) + end + end + def destroy authorize! :manage, domain domain.cancel_force_delete @@ -36,6 +40,13 @@ module Admin ActiveRecord::Type::Boolean.new.cast(params[:notify_by_email]) end + def send_email + DomainDeleteMailer.forced(domain: domain, + registrar: domain.registrar, + registrant: domain.registrant, + template_name: params[:template_name]).deliver_now + end + def force_delete_type soft_delete? ? :soft : :fast_track end diff --git a/app/controllers/registrant/domains_controller.rb b/app/controllers/registrant/domains_controller.rb index fc66806e2..216f87e54 100644 --- a/app/controllers/registrant/domains_controller.rb +++ b/app/controllers/registrant/domains_controller.rb @@ -76,4 +76,4 @@ class Registrant::DomainsController < RegistrantController params.require(:q).permit(:name_matches, :registrant_ident_eq, :valid_to_gteq, :valid_to_lteq, :results_per_page) end -end \ No newline at end of file +end diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index ffe997322..0120347c8 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -1,6 +1,18 @@ -module Concerns::Domain::ForceDelete +module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength extend ActiveSupport::Concern + included do + store_accessor :force_delete_data, + :force_delete_type, + :contact_notification_sent_date, + :template_name + + scope :notification_not_sent, + lambda { + where("(force_delete_data->>'contact_notification_sent_date') is null") + } + end + class_methods do def force_delete_scheduled where('force_delete_start <= ?', Time.zone.now) @@ -12,8 +24,8 @@ module Concerns::Domain::ForceDelete end def should_notify_on_soft_force_delete? - force_delete_scheduled? && !statuses.include?(DomainStatus::CLIENT_HOLD) && - force_delete_start.present? && force_delete_start.to_date == Time.zone.now.to_date + force_delete_scheduled? && contact_notification_sent_date.blank? && + force_delete_start.to_date == Time.zone.now.to_date && force_delete_type.to_sym == :soft end def client_holdable? @@ -37,9 +49,14 @@ module Concerns::Domain::ForceDelete type == :fast_track ? force_delete_fast_track : force_delete_soft end + def add_force_delete_type(force_delete_type) + self.force_delete_type = force_delete_type + end + def force_delete_fast_track preserve_current_statuses_for_force_delete add_force_delete_statuses + add_force_delete_type(:fast) self.force_delete_date = force_delete_fast_track_start_date + 1.day self.force_delete_start = Time.zone.today + 1.day stop_all_pending_actions @@ -50,15 +67,21 @@ module Concerns::Domain::ForceDelete def force_delete_soft preserve_current_statuses_for_force_delete add_force_delete_statuses + add_force_delete_type(:soft) calculate_soft_delete_date stop_all_pending_actions allow_deletion save(validate: false) end + def clear_force_delete_data + self.force_delete_data = nil + end + def cancel_force_delete restore_statuses_before_force_delete remove_force_delete_statuses + clear_force_delete_data self.force_delete_date = nil self.force_delete_start = nil save(validate: false) diff --git a/app/models/concerns/job/force_delete_logging.rb b/app/models/concerns/job/force_delete_logging.rb new file mode 100644 index 000000000..072c5b98f --- /dev/null +++ b/app/models/concerns/job/force_delete_logging.rb @@ -0,0 +1,28 @@ +module Concerns + module Job + module ForceDeleteLogging + extend ActiveSupport::Concern + + class_methods do + def log_prepare_client_hold + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} - Setting client_hold to domains\n" + end + + def log_start_client_hold(domain) + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} DomainCron.start_client_hold: ##{domain.id} "\ + "(#{domain.name}) #{domain.changes}\n" + end + + def log_end_end_client_hold(domain) + return if Rails.env.test? + + STDOUT << "#{Time.zone.now.utc} - Successfully set client_hold on (#{domain.name})" + end + end + end + end +end diff --git a/app/models/concerns/job/force_delete_messages.rb b/app/models/concerns/job/force_delete_notify.rb similarity index 56% rename from app/models/concerns/job/force_delete_messages.rb rename to app/models/concerns/job/force_delete_notify.rb index cb4f298a8..bc7322645 100644 --- a/app/models/concerns/job/force_delete_messages.rb +++ b/app/models/concerns/job/force_delete_notify.rb @@ -1,28 +1,9 @@ module Concerns module Job - module ForceDeleteMessages + module ForceDeleteNotify extend ActiveSupport::Concern class_methods do - def log_prepare_client_hold - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} - Setting client_hold to domains\n" - end - - def log_start_client_hold(domain) - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} DomainCron.start_client_hold: ##{domain.id} "\ - "(#{domain.name}) #{domain.changes}\n" - end - - def log_end_end_client_hold(domain) - return if Rails.env.test? - - STDOUT << "#{Time.zone.now.utc} - Successfully set client_hold on (#{domain.name})" - end - def notify_client_hold(domain) domain.registrar.notifications.create!(text: I18n.t('client_hold_set_on_domain', domain_name: domain.name, @@ -33,6 +14,15 @@ module Concerns domain.registrar.notifications.create!(text: I18n.t('grace_period_started_domain', domain_name: domain.name, date: domain.force_delete_start)) + send_mail(domain) + domain.update(contact_notification_sent_date: Time.zone.today) + end + + def send_mail(domain) + DomainDeleteMailer.forced(domain: domain, + registrar: domain.registrar, + registrant: domain.registrant, + template_name: domain.template_name).deliver_now end end end diff --git a/app/models/domain_cron.rb b/app/models/domain_cron.rb index 47e5a99b5..bc075c067 100644 --- a/app/models/domain_cron.rb +++ b/app/models/domain_cron.rb @@ -1,6 +1,7 @@ class DomainCron include Concerns::Job::ForceDelete - include Concerns::Job::ForceDeleteMessages + include Concerns::Job::ForceDeleteLogging + include Concerns::Job::ForceDeleteNotify def self.clean_expired_pendings STDOUT << "#{Time.zone.now.utc} - Clean expired domain pendings\n" unless Rails.env.test? diff --git a/db/migrate/20200115102202_add_force_delete_data_to_domains.rb b/db/migrate/20200115102202_add_force_delete_data_to_domains.rb new file mode 100644 index 000000000..2ecc7ceaf --- /dev/null +++ b/db/migrate/20200115102202_add_force_delete_data_to_domains.rb @@ -0,0 +1,5 @@ +class AddForceDeleteDataToDomains < ActiveRecord::Migration[5.0] + def change + add_column :domains, :force_delete_data, :hstore + end +end diff --git a/db/structure.sql b/db/structure.sql index 7d15804a4..a23623bae 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -744,7 +744,9 @@ CREATE TABLE public.domains ( up_date timestamp without time zone, uuid uuid DEFAULT public.gen_random_uuid() NOT NULL, locked_by_registrant_at timestamp without time zone, - force_delete_start timestamp without time zone + force_delete_start timestamp without time zone, + force_delete_data public.hstore + ); @@ -4336,6 +4338,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20191206183853'), ('20191212133136'), ('20191227110904'), -('20200113091254'); +('20200113091254'), +('20200115102202'); diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index f59cf58bd..5945ac807 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -201,6 +201,7 @@ class NewDomainForceDeleteTest < ActiveSupport::TestCase asserted_status = DomainStatus::CLIENT_HOLD @domain.update_columns(valid_to: Time.zone.parse('2010-10-05'), force_delete_date: nil) + @domain.update(template_name: 'legal_person') travel_to Time.zone.parse('2010-07-05') diff --git a/test/system/admin_area/domains/force_delete_test.rb b/test/system/admin_area/domains/force_delete_test.rb index 5e135bf24..b8af02f94 100644 --- a/test/system/admin_area/domains/force_delete_test.rb +++ b/test/system/admin_area/domains/force_delete_test.rb @@ -31,6 +31,7 @@ class AdminAreaDomainForceDeleteTest < ApplicationSystemTestCase def test_notifies_registrant_and_admin_contacts_by_email_by_default assert_emails 1 do visit edit_admin_domain_url(@domain) + find(:css, '#soft_delete').set(false) click_link_or_button 'Force delete domain' end end @@ -62,4 +63,4 @@ class AdminAreaDomainForceDeleteTest < ApplicationSystemTestCase assert_no_button 'Schedule force delete' assert_no_link 'Schedule force delete' end -end \ No newline at end of file +end From e7581246d64bcdffb587491f3b87bf7681771365 Mon Sep 17 00:00:00 2001 From: Alex Sherman Date: Wed, 15 Jan 2020 17:46:31 +0500 Subject: [PATCH 12/12] Add test for clear force delete on registrar change --- app/models/concerns/domain/force_delete.rb | 2 +- test/models/domain/force_delete_test.rb | 1 + test/models/domain_cron_test.rb | 17 ++++++++++++++++- test/models/domain_test.rb | 16 ++++++++++++++++ .../admin_area/domains/force_delete_test.rb | 10 +++++++++- 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/domain/force_delete.rb b/app/models/concerns/domain/force_delete.rb index 0120347c8..c335fcfd0 100644 --- a/app/models/concerns/domain/force_delete.rb +++ b/app/models/concerns/domain/force_delete.rb @@ -25,7 +25,7 @@ module Concerns::Domain::ForceDelete # rubocop:disable Metrics/ModuleLength def should_notify_on_soft_force_delete? force_delete_scheduled? && contact_notification_sent_date.blank? && - force_delete_start.to_date == Time.zone.now.to_date && force_delete_type.to_sym == :soft + force_delete_start.to_date <= Time.zone.now.to_date && force_delete_type.to_sym == :soft end def client_holdable? diff --git a/test/models/domain/force_delete_test.rb b/test/models/domain/force_delete_test.rb index 5945ac807..053721868 100644 --- a/test/models/domain/force_delete_test.rb +++ b/test/models/domain/force_delete_test.rb @@ -156,6 +156,7 @@ class NewDomainForceDeleteTest < ActiveSupport::TestCase asserted_status = DomainStatus::CLIENT_HOLD @domain.update(valid_to: Time.zone.parse('2012-08-05')) + @domain.update(template_name: 'legal_person') assert_not @domain.force_delete_scheduled? travel_to Time.zone.parse('2010-07-05') @domain.schedule_force_delete(type: :soft) diff --git a/test/models/domain_cron_test.rb b/test/models/domain_cron_test.rb index 742bf2eaf..f0a0bbc9d 100644 --- a/test/models/domain_cron_test.rb +++ b/test/models/domain_cron_test.rb @@ -23,4 +23,19 @@ class DomainCronTest < ActiveSupport::TestCase assert_emails 1 end -end \ No newline at end of file + + def test_client_hold + Setting.redemption_grace_period = 30 + + @domain.update(valid_to: Time.zone.parse('2012-08-05')) + assert_not @domain.force_delete_scheduled? + travel_to Time.zone.parse('2010-07-05') + @domain.schedule_force_delete(type: :soft) + @domain.reload + @domain.update(template_name: 'legal_person') + travel_to Time.zone.parse('2010-08-06') + DomainCron.start_client_hold + + assert_emails 1 + end +end diff --git a/test/models/domain_test.rb b/test/models/domain_test.rb index 7e681e560..8bac6d7ab 100644 --- a/test/models/domain_test.rb +++ b/test/models/domain_test.rb @@ -405,6 +405,22 @@ class DomainTest < ActiveSupport::TestCase assert_not domain.active? end + def test_registrant_change_removes_force_delete + @domain.update_columns(valid_to: Time.zone.parse('2010-10-05'), + force_delete_date: nil) + @domain.update(template_name: 'legal_person') + travel_to Time.zone.parse('2010-07-05') + @domain.schedule_force_delete(type: :fast_track) + assert(@domain.force_delete_scheduled?) + other_registrant = Registrant.find_by(code: 'jane-001') + @domain.pending_json['new_registrant_id'] = other_registrant.id + + @domain.registrant = other_registrant + @domain.save! + + assert_not(@domain.force_delete_scheduled?) + end + private def valid_domain diff --git a/test/system/admin_area/domains/force_delete_test.rb b/test/system/admin_area/domains/force_delete_test.rb index b8af02f94..4ccc10923 100644 --- a/test/system/admin_area/domains/force_delete_test.rb +++ b/test/system/admin_area/domains/force_delete_test.rb @@ -28,7 +28,7 @@ class AdminAreaDomainForceDeleteTest < ApplicationSystemTestCase end end - def test_notifies_registrant_and_admin_contacts_by_email_by_default + def test_notifies_registrant_and_admin_contacts_by_email_if_fast_delete assert_emails 1 do visit edit_admin_domain_url(@domain) find(:css, '#soft_delete').set(false) @@ -36,6 +36,14 @@ class AdminAreaDomainForceDeleteTest < ApplicationSystemTestCase end end + def test_notifies_registrant_and_admin_contacts_by_email_if_soft_delete + assert_emails 0 do + visit edit_admin_domain_url(@domain) + find(:css, '#soft_delete').set(true) + click_link_or_button 'Force delete domain' + end + end + def test_allows_to_skip_notifying_registrant_and_admin_contacts_by_email assert_no_emails do visit edit_admin_domain_url(@domain)