diff --git a/.gitignore b/.gitignore
index cb4bf2c3b5..d72d03e6a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,4 +37,5 @@ CLAUDE.md
AGENT.md
own
AGENT.md
-
+/docs/
+.repowise
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b88ba60f11..a58f32aee5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+10.08.2026
+* Registrars can now cancel a pending registrant change https://github.com/internetee/registry/issues/2939
+
23.07.2026
* Operations with pending status now return result code 1001 in REPP https://github.com/internetee/registry/issues/2940
* Fixed case sensitivity issue for REPP requests https://github.com/internetee/registry/issues/2943
diff --git a/app/controllers/epp/domains_controller.rb b/app/controllers/epp/domains_controller.rb
index 094fe595a7..e588e844c7 100644
--- a/app/controllers/epp/domains_controller.rb
+++ b/app/controllers/epp/domains_controller.rb
@@ -9,6 +9,12 @@ class DomainsController < BaseController
THROTTLED_ACTIONS = %i[info create check renew update transfer delete].freeze
include Shunter::Integration::Throttle
+ # Everything Deserializers::Xml::DomainUpdate can emit besides the registrant itself.
+ # :domain and :registrar_id are always present, :legal_document may be mandatory for
+ # the registrar even when cancelling.
+ UPDATE_KEYS_BESIDES_REGISTRANT = %i[contacts nameservers dns_keys statuses transfer_code
+ reserved_pw].freeze
+
def info
authorize! :info, @domain
@@ -47,6 +53,9 @@ def update
registrar_id = current_user.registrar.id
update_params = ::Deserializers::Xml::DomainUpdate.new(params[:parsed_frame],
registrar_id).call
+
+ return cancel_pending_update if cancels_pending_update?(update_params)
+
action = Actions::DomainUpdate.new(@domain, update_params, false)
unless action.call
handle_errors(@domain)
@@ -134,6 +143,33 @@ def transfer
private
+ # EPP has no command to manipulate pending operations (RFC 3731 covers transfer only),
+ # so a domain:update requesting the registrant the domain already has is treated as a
+ # request to cancel the pending registrant change. Same idea as domain:renew cancelling
+ # pendingDelete in Epp::Domain#renew.
+ def cancels_pending_update?(update_params)
+ return false unless @domain.pending_update?
+ return false if update_params[:registrant].blank?
+
+ requested = Registrant.find_by(code: update_params[:registrant][:code])
+ return false unless requested&.id == @domain.registrant_id
+
+ (update_params.keys & UPDATE_KEYS_BESIDES_REGISTRANT).empty?
+ end
+
+ def cancel_pending_update
+ result = ::Domains::CancelPendingUpdate.run(domain: @domain,
+ initiator: current_user.username)
+ unless result.valid?
+ @domain.add_epp_error('2304', 'status', DomainStatus::PENDING_UPDATE,
+ result.errors.full_messages.join(', '))
+ handle_errors(@domain)
+ return
+ end
+
+ render_epp_response('/epp/domains/success')
+ end
+
def validate_info
@prefix = 'info > info >'
requires('name')
diff --git a/app/controllers/repp/v1/domains/pending_updates_controller.rb b/app/controllers/repp/v1/domains/pending_updates_controller.rb
new file mode 100644
index 0000000000..0e36ec1847
--- /dev/null
+++ b/app/controllers/repp/v1/domains/pending_updates_controller.rb
@@ -0,0 +1,29 @@
+module Repp
+ module V1
+ module Domains
+ class PendingUpdatesController < BaseController
+ before_action :set_domain
+
+ THROTTLED_ACTIONS = %i[destroy].freeze
+ include Shunter::Integration::Throttle
+
+ api :DELETE, '/repp/v1/domains/:domain_name/pending_update'
+ param :domain_name, String, desc: 'Domain name'
+ desc 'Cancel a pending registrant change of a specific domain'
+ def destroy
+ authorize!(:update, @domain)
+
+ result = ::Domains::CancelPendingUpdate.run(domain: @domain,
+ initiator: current_user.username)
+ unless result.valid?
+ @domain.add_epp_error('2304', 'status', DomainStatus::PENDING_UPDATE,
+ result.errors.full_messages.join(', '))
+ return handle_errors(@domain)
+ end
+
+ render_success(data: { domain: { name: @domain.name } })
+ end
+ end
+ end
+ end
+end
diff --git a/app/interactions/domains/cancel_pending_update.rb b/app/interactions/domains/cancel_pending_update.rb
new file mode 100644
index 0000000000..1a740ec2b0
--- /dev/null
+++ b/app/interactions/domains/cancel_pending_update.rb
@@ -0,0 +1,52 @@
+module Domains
+ class CancelPendingUpdate < ActiveInteraction::Base
+ object :domain,
+ class: Domain,
+ description: 'Domain with a pending registrant change'
+ string :initiator,
+ default: nil
+
+ validate :domain_has_pending_update
+
+ def execute
+ ::PaperTrail.request.whodunnit = "interaction - #{self.class.name} - cancelled by"\
+ " #{initiator}"
+
+ ActiveRecord::Base.transaction do
+ notify_registrants
+ clean_pendings!
+ end
+
+ UpdateWhoisRecordJob.perform_later(domain.name, 'domain')
+ end
+
+ private
+
+ def domain_has_pending_update
+ return if domain&.pending_update?
+
+ errors.add(:domain, I18n.t(:object_status_prohibits_operation))
+ end
+
+ # Both parties already got a confirmation link that is about to become invalid,
+ # so they are notified before the verification data is wiped.
+ def notify_registrants
+ RegistrantChangeMailer.cancelled(domain: domain,
+ registrar: domain.registrar,
+ registrant: domain.registrant,
+ send_to: [domain.new_registrant_email,
+ domain.registrant.email]).deliver_later
+ end
+
+ def clean_pendings!
+ domain.is_admin = true
+ # Has to happen before save, otherwise before_update reinstates pendingUpdate
+ domain.registrant_verification_token = nil
+ domain.registrant_verification_asked_at = nil
+ domain.pending_json = {}
+ domain.statuses.delete(DomainStatus::PENDING_UPDATE)
+ domain.status_notes[DomainStatus::PENDING_UPDATE] = ''
+ domain.save!
+ end
+ end
+end
diff --git a/app/interactions/domains/update_confirm/process_action.rb b/app/interactions/domains/update_confirm/process_action.rb
index 07c3a6dd31..03d2463476 100644
--- a/app/interactions/domains/update_confirm/process_action.rb
+++ b/app/interactions/domains/update_confirm/process_action.rb
@@ -2,6 +2,10 @@ module Domains
module UpdateConfirm
class ProcessAction < Base
def execute
+ # The registrant decision arrives asynchronously, so the pending update may already
+ # be gone by now - cancelled by the registrar or cleaned up by the expiry cron.
+ return unless domain.pending_update?
+
::PaperTrail.request.whodunnit = "interaction - #{self.class.name} - #{action} by"\
" #{initiator}"
diff --git a/app/mailers/registrant_change_mailer.rb b/app/mailers/registrant_change_mailer.rb
index 101e6c3e17..dd0a8cb980 100644
--- a/app/mailers/registrant_change_mailer.rb
+++ b/app/mailers/registrant_change_mailer.rb
@@ -38,6 +38,15 @@ def rejected(domain:, registrar:, registrant:)
mail(to: domain.new_registrant_email, subject: subject)
end
+ def cancelled(domain:, registrar:, registrant:, send_to:)
+ @domain = DomainPresenter.new(domain: domain, view: view_context)
+ @registrar = RegistrarPresenter.new(registrar: registrar, view: view_context)
+ @registrant = RegistrantPresenter.new(registrant: registrant, view: view_context)
+
+ subject = default_i18n_subject(domain_name: domain.name)
+ mail(to: send_to, subject: subject)
+ end
+
def expired(domain:, registrar:, registrant:, send_to:)
@domain = DomainPresenter.new(domain: domain, view: view_context)
@registrar = RegistrarPresenter.new(registrar: registrar, view: view_context)
diff --git a/app/views/mailers/registrant_change_mailer/cancelled.html.erb b/app/views/mailers/registrant_change_mailer/cancelled.html.erb
new file mode 100644
index 0000000000..9e12d4bde3
--- /dev/null
+++ b/app/views/mailers/registrant_change_mailer/cancelled.html.erb
@@ -0,0 +1,18 @@
+Tere
+
+Registripidaja tühistas domeeni <%= @domain.name %> registreerija vahetuse taotluse. Varem saadetud kinnituslink ei kehti enam.
+
+Küsimuste korral palun võtke ühendust oma registripidajaga:
+
+<%= render 'mailers/shared/registrar/registrar.et.html', registrar: @registrar %>
+<%= render 'mailers/shared/signatures/signature.et.html' %>
+
+
+Hi,
+
+The registrar has cancelled the registrant change request for the domain <%= @domain.name %>. The confirmation link sent earlier is no longer valid.
+
+Please contact your registrar if you have any questions:
+
+<%= render 'mailers/shared/registrar/registrar.en.html', registrar: @registrar %>
+<%= render 'mailers/shared/signatures/signature.en.html' %>
diff --git a/app/views/mailers/registrant_change_mailer/cancelled.text.erb b/app/views/mailers/registrant_change_mailer/cancelled.text.erb
new file mode 100644
index 0000000000..2a15598ced
--- /dev/null
+++ b/app/views/mailers/registrant_change_mailer/cancelled.text.erb
@@ -0,0 +1,20 @@
+Tere
+
+Registripidaja tühistas domeeni <%= @domain.name %> registreerija vahetuse taotluse. Varem saadetud kinnituslink ei kehti enam.
+
+Küsimuste korral palun võtke ühendust oma registripidajaga:
+
+<%= render 'mailers/shared/registrar/registrar.et.text', registrar: @registrar %>
+<%= render 'mailers/shared/signatures/signature.et.text' %>
+
+--------------------------------------
+
+Hi,
+
+The registrar has cancelled the registrant change request for the domain <%= @domain.name %>. The confirmation link sent earlier is no longer valid.
+
+Please contact your registrar if you have any questions:
+
+<%= render 'mailers/shared/registrar/registrar.en.text', registrar: @registrar %>
+
+<%= render 'mailers/shared/signatures/signature.en.text' %>
diff --git a/config/locales/mailers/registrant_change.en.yml b/config/locales/mailers/registrant_change.en.yml
index 744715807d..e3b8cf820e 100644
--- a/config/locales/mailers/registrant_change.en.yml
+++ b/config/locales/mailers/registrant_change.en.yml
@@ -19,4 +19,8 @@ en:
expired:
subject: >-
Domeeni %{domain_name} registreerija vahetuse taotlus on tühistatud
- / %{domain_name} registrant change cancelled
\ No newline at end of file
+ / %{domain_name} registrant change cancelled
+ cancelled:
+ subject: >-
+ Registripidaja tühistas domeeni %{domain_name} registreerija vahetuse taotluse
+ / %{domain_name} registrant change request was cancelled by the registrar
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 05af94cda3..19d67731b3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -167,6 +167,7 @@
resources :renew, only: %i[create], constraints: { id: /.*/ }, controller: 'domains/renews'
resources :transfer, only: %i[create], constraints: { id: /.*/ }, controller: 'domains/transfers'
resources :statuses, only: %i[update destroy], constraints: { id: /.*/ }, controller: 'domains/statuses'
+ resource :pending_update, only: %i[destroy], controller: 'domains/pending_updates'
match 'dnssec', to: 'domains/dnssec#destroy', via: 'delete', defaults: { id: nil }
match 'contacts', to: 'domains/contacts#destroy', via: 'delete', defaults: { id: nil }
collection do
diff --git a/test/integration/epp/domain/update/cancel_pending_test.rb b/test/integration/epp/domain/update/cancel_pending_test.rb
new file mode 100644
index 0000000000..957157c969
--- /dev/null
+++ b/test/integration/epp/domain/update/cancel_pending_test.rb
@@ -0,0 +1,129 @@
+require 'test_helper'
+
+class EppDomainUpdateCancelPendingTest < EppTestCase
+ include ActionMailer::TestHelper
+ include ActiveJob::TestHelper
+
+ setup do
+ @domain = domains(:shop)
+ @original_registrant_change_verification =
+ Setting.request_confirmation_on_registrant_change_enabled
+ Setting.request_confirmation_on_registrant_change_enabled = true
+ ActionMailer::Base.deliveries.clear
+
+ adapter = ENV['shunter_default_adapter'].constantize.new
+ adapter&.clear!
+ end
+
+ teardown do
+ Setting.request_confirmation_on_registrant_change_enabled =
+ @original_registrant_change_verification
+ end
+
+ def test_cancels_pending_update_when_current_registrant_is_requested_again
+ request_registrant_change
+ old_registrant = @domain.registrant
+
+ post_domain_update(old_registrant)
+
+ assert_epp_response :completed_successfully
+ assert_equal old_registrant, @domain.registrant
+ assert_not_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ assert_empty @domain.pending_json
+ assert_not @domain.registrant_verification_asked?
+ end
+
+ def test_notifies_both_registrants_when_pending_update_is_cancelled
+ request_registrant_change
+ new_registrant_email = @domain.new_registrant_email
+ registrant_email = @domain.registrant.email
+ ActionMailer::Base.deliveries.clear
+
+ perform_enqueued_jobs { post_domain_update(@domain.registrant) }
+
+ email = ActionMailer::Base.deliveries.last
+ assert_includes email.to, new_registrant_email
+ assert_includes email.to, registrant_email
+ end
+
+ def test_rejects_update_of_pending_domain_when_another_registrant_is_requested
+ request_registrant_change
+ old_registrant = @domain.registrant
+
+ post_domain_update(contacts(:jack))
+
+ assert_epp_response :object_status_prohibits_operation
+ assert_equal old_registrant, @domain.registrant
+ assert_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ end
+
+ def test_does_not_cancel_pending_update_when_other_changes_are_requested
+ request_registrant_change
+ old_transfer_code = @domain.transfer_code
+
+ post_domain_update(@domain.registrant, transfer_code: 'new-transfer-code')
+
+ assert_epp_response :object_status_prohibits_operation
+ assert_equal old_transfer_code, @domain.transfer_code
+ assert_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ end
+
+ def test_keeps_regular_update_intact_when_domain_has_no_pending_update
+ assert_not_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+
+ post_domain_update(@domain.registrant)
+
+ assert_epp_response :completed_successfully
+ assert_not_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ end
+
+ private
+
+ def request_registrant_change
+ new_registrant = contacts(:william)
+ assert_not_equal new_registrant, @domain.registrant
+
+ post_domain_update(new_registrant)
+
+ assert_epp_response :completed_successfully_action_pending
+ assert_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ end
+
+ def post_domain_update(registrant, transfer_code: nil)
+ post epp_update_path,
+ params: { frame: registrant_change_xml(registrant, transfer_code: transfer_code) },
+ headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
+
+ # assert_epp_response memoizes the parsed response, reset it between requests
+ @epp_response = nil
+ @domain.reload
+ end
+
+ def registrant_change_xml(registrant, transfer_code: nil)
+ auth_info = if transfer_code
+ "#{transfer_code}"
+ end
+
+ <<-XML
+
+
+
+
+
+ #{@domain.name}
+
+ #{registrant.code}
+ #{auth_info}
+
+
+
+
+
+ #{'test' * 2000}
+
+
+
+
+ XML
+ end
+end
diff --git a/test/integration/repp/v1/domains/cancel_pending_update_test.rb b/test/integration/repp/v1/domains/cancel_pending_update_test.rb
new file mode 100644
index 0000000000..a5474c8634
--- /dev/null
+++ b/test/integration/repp/v1/domains/cancel_pending_update_test.rb
@@ -0,0 +1,94 @@
+require 'test_helper'
+
+class ReppV1DomainsCancelPendingUpdateTest < ActionDispatch::IntegrationTest
+ def setup
+ @user = users(:api_bestnames)
+ @domain = domains(:shop)
+ token = Base64.encode64("#{@user.username}:#{@user.plain_text_password}")
+ @auth_headers = { 'Authorization' => "Basic #{token}" }
+ Setting.request_confirmation_on_registrant_change_enabled = true
+ end
+
+ def test_cancels_pending_registrant_change
+ request_registrant_change
+
+ json = cancel_pending_update
+
+ assert_response :ok
+ assert_equal 1000, json[:code]
+ assert_equal @domain.name, json[:data][:domain][:name]
+ refute_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ assert_empty @domain.pending_json
+ assert_nil @domain.registrant_verification_token
+ assert_nil @domain.registrant_verification_asked_at
+ end
+
+ def test_keeps_current_registrant_on_cancel
+ old_registrant = @domain.registrant
+ request_registrant_change
+
+ cancel_pending_update
+
+ assert_equal old_registrant, @domain.registrant
+ end
+
+ def test_notifies_both_registrants_on_cancel
+ request_registrant_change
+ new_registrant_email = @domain.new_registrant_email
+ registrant_email = @domain.registrant.email
+ ActionMailer::Base.deliveries.clear
+
+ perform_enqueued_jobs { cancel_pending_update }
+
+ email = ActionMailer::Base.deliveries.last
+ assert_includes email.to, new_registrant_email
+ assert_includes email.to, registrant_email
+ end
+
+ def test_returns_error_when_domain_has_no_pending_update
+ refute_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+
+ json = cancel_pending_update
+
+ assert_response :bad_request
+ assert_equal 2304, json[:code]
+ end
+
+ def test_does_not_cancel_pending_update_of_another_registrar
+ request_registrant_change
+ other_user = users(:api_goodnames)
+ token = Base64.encode64("#{other_user.username}:#{other_user.plain_text_password}")
+ @auth_headers = { 'Authorization' => "Basic #{token}" }
+
+ json = cancel_pending_update
+
+ assert_response :not_found
+ assert_equal 2303, json[:code]
+ assert_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ end
+
+ private
+
+ def request_registrant_change
+ new_registrant = contacts(:william)
+ refute_equal new_registrant, @domain.registrant
+
+ put "/repp/v1/domains/#{@domain.name}",
+ headers: json_headers,
+ params: { domain: { registrant: { code: new_registrant.code } } }.to_json
+
+ @domain.reload
+ assert_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ end
+
+ def cancel_pending_update
+ delete "/repp/v1/domains/#{@domain.name}/pending_update", headers: json_headers
+
+ @domain.reload
+ JSON.parse(response.body, symbolize_names: true)
+ end
+
+ def json_headers
+ @auth_headers.merge('Content-Type' => 'application/json')
+ end
+end
diff --git a/test/interactions/domains/cancel_pending_update_test.rb b/test/interactions/domains/cancel_pending_update_test.rb
new file mode 100644
index 0000000000..a9e8215814
--- /dev/null
+++ b/test/interactions/domains/cancel_pending_update_test.rb
@@ -0,0 +1,80 @@
+require 'test_helper'
+
+module Domains
+ class CancelPendingUpdateTest < ActiveSupport::TestCase
+ include ActionMailer::TestHelper
+ include ActiveJob::TestHelper
+
+ setup do
+ @domain = domains(:shop)
+ @new_registrant = contacts(:william)
+ @domain.update!(registrant_verification_asked_at: Time.zone.now,
+ registrant_verification_token: 'test')
+ @domain.pending_json = { 'new_registrant_id' => @new_registrant.id,
+ 'new_registrant_email' => @new_registrant.email,
+ 'new_registrant_name' => @new_registrant.name }
+ @domain.statuses = [DomainStatus::PENDING_UPDATE]
+ @domain.save(validate: false)
+ ActionMailer::Base.deliveries.clear
+ end
+
+ def test_clears_pending_update_data
+ CancelPendingUpdate.run!(domain: @domain, initiator: 'test')
+ @domain.reload
+
+ assert_not_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ assert_empty @domain.pending_json
+ assert_nil @domain.registrant_verification_token
+ assert_nil @domain.registrant_verification_asked_at
+ assert_equal '', @domain.status_notes[DomainStatus::PENDING_UPDATE]
+ end
+
+ def test_keeps_registrant_untouched
+ old_registrant = @domain.registrant
+
+ CancelPendingUpdate.run!(domain: @domain, initiator: 'test')
+ @domain.reload
+
+ assert_equal old_registrant, @domain.registrant
+ end
+
+ def test_notifies_both_registrants
+ registrant_email = @domain.registrant.email
+
+ perform_enqueued_jobs do
+ CancelPendingUpdate.run!(domain: @domain, initiator: 'test')
+ end
+
+ email = ActionMailer::Base.deliveries.last
+ assert_includes email.to, @new_registrant.email
+ assert_includes email.to, registrant_email
+ end
+
+ def test_updates_whois_record
+ assert_enqueued_with(job: UpdateWhoisRecordJob, args: [@domain.name, 'domain']) do
+ CancelPendingUpdate.run!(domain: @domain, initiator: 'test')
+ end
+ end
+
+ def test_fails_when_domain_has_no_pending_update
+ @domain.statuses = []
+ @domain.save(validate: false)
+
+ result = CancelPendingUpdate.run(domain: @domain, initiator: 'test')
+
+ assert_not result.valid?
+ assert_no_enqueued_emails
+ end
+
+ def test_does_not_touch_other_statuses
+ @domain.statuses = [DomainStatus::PENDING_UPDATE, DomainStatus::CLIENT_HOLD]
+ @domain.save(validate: false)
+
+ CancelPendingUpdate.run!(domain: @domain, initiator: 'test')
+ @domain.reload
+
+ assert_includes @domain.statuses, DomainStatus::CLIENT_HOLD
+ assert_not_includes @domain.statuses, DomainStatus::PENDING_UPDATE
+ end
+ end
+end
diff --git a/test/jobs/domain_update_confirm_job_test.rb b/test/jobs/domain_update_confirm_job_test.rb
index 158729ae3c..380ec494e7 100644
--- a/test/jobs/domain_update_confirm_job_test.rb
+++ b/test/jobs/domain_update_confirm_job_test.rb
@@ -36,7 +36,23 @@ def test_registrant_unlocked_domain
assert_equal(@domain.registrar.notifications.last.text, "Domain #{@domain.name} has been unlocked by registrant")
end
+ # The registrant decision may reach us after the registrar has already cancelled
+ # the pending update, or after the expiry cron has cleaned it up.
+ def test_skips_confirmation_when_pending_update_is_already_gone
+ set_pending_update
+ old_registrant_code = @domain.registrant.code
+ @domain.update!(statuses: [DomainStatus::OK])
+
+ assert_no_difference '@domain.registrar.notifications.count' do
+ DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED)
+ end
+ @domain.reload
+
+ assert_equal old_registrant_code, @domain.registrant.code
+ end
+
def test_rejected_registrant_verification_notifies_registrar
+ set_pending_update
DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED)
last_registrar_notification = @domain.registrar.notifications.last
@@ -45,6 +61,7 @@ def test_rejected_registrant_verification_notifies_registrar
end
def test_accepted_registrant_verification_notifies_registrar
+ set_pending_update
DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED)
last_registrar_notification = @domain.registrar.notifications.last
@@ -61,6 +78,7 @@ def test_changes_domain_registrant_after_approval
@domain.pending_json['frame'] = parsed_frame
@domain.update(pending_json: @domain.pending_json)
+ set_pending_update
@domain.reload
DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED)
@@ -79,6 +97,7 @@ def test_clears_pending_update_after_denial
@domain.pending_json['frame'] = parsed_frame
@domain.update(pending_json: @domain.pending_json)
+ set_pending_update
DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED)
@domain.reload
@@ -96,7 +115,8 @@ def test_protects_statuses_after_denial
@domain.pending_json['frame'] = parsed_frame
@domain.update(pending_json: @domain.pending_json)
- @domain.update(statuses: [DomainStatus::DELETE_CANDIDATE, DomainStatus::DISPUTED])
+ @domain.update(statuses: [DomainStatus::DELETE_CANDIDATE, DomainStatus::DISPUTED,
+ DomainStatus::PENDING_UPDATE])
DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::REJECTED)
@domain.reload
@@ -116,7 +136,8 @@ def test_protects_statuses_after_confirm
@domain.pending_json['frame'] = parsed_frame
@domain.update(pending_json: @domain.pending_json)
- @domain.update(statuses: [DomainStatus::DELETE_CANDIDATE, DomainStatus::DISPUTED])
+ @domain.update(statuses: [DomainStatus::DELETE_CANDIDATE, DomainStatus::DISPUTED,
+ DomainStatus::PENDING_UPDATE])
DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED)
@domain.reload
@@ -137,7 +158,8 @@ def test_works_id_current_user_id_broken
@domain.pending_json['frame'] = parsed_frame
@domain.pending_json['current_user_id'] = { key: 'some_value'}
@domain.update(pending_json: @domain.pending_json)
- @domain.update(statuses: [DomainStatus::DELETE_CANDIDATE, DomainStatus::DISPUTED])
+ @domain.update(statuses: [DomainStatus::DELETE_CANDIDATE, DomainStatus::DISPUTED,
+ DomainStatus::PENDING_UPDATE])
assert_nothing_raised do
DomainUpdateConfirmJob.perform_now(@domain.id, RegistrantVerification::CONFIRMED)
@@ -191,4 +213,12 @@ def test_clears_pending_update_and_sets_ok_after_denial
assert_not @domain.statuses.include? DomainStatus::PENDING_UPDATE
assert @domain.statuses.include? DomainStatus::OK
end
+
+ private
+
+ def set_pending_update
+ @domain.statuses = [DomainStatus::PENDING_UPDATE]
+ @domain.save(validate: false)
+ @domain.reload
+ end
end