This commit is contained in:
OlegPhenomenon 2025-08-11 12:31:56 +00:00 committed by GitHub
commit 3c71100195
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 937 additions and 17 deletions

View file

@ -80,7 +80,7 @@ module Repp
handle_errors(@domain) and return unless action.call handle_errors(@domain) and return unless action.call
# rubocop:enable Style/AndOr # rubocop:enable Style/AndOr
render_success(data: { domain: { name: @domain.name, render_success(message: message, data: { domain: { name: @domain.name,
transfer_code: @domain.transfer_code, transfer_code: @domain.transfer_code,
id: @domain.reload.uuid } }) id: @domain.reload.uuid } })
end end
@ -104,7 +104,7 @@ module Repp
return return
end end
render_success(data: { domain: { name: @domain.name } }) render_success(message: message, data: { domain: { name: @domain.name } })
end end
api :GET, '/repp/v1/domains/:domain_name/transfer_info' api :GET, '/repp/v1/domains/:domain_name/transfer_info'
@ -239,6 +239,10 @@ module Repp
index_params[:offset] || 0 index_params[:offset] || 0
end end
def message
"Command completed successfully#{@domain.skipped_domain_contacts_validation if @domain.skipped_domain_contacts_validation.present?}"
end
def index_params def index_params
params.permit(:limit, :offset, :details, :simple, :q, params.permit(:limit, :offset, :details, :simple, :q,
q: %i[s name_matches registrant_code_eq contacts_ident_eq q: %i[s name_matches registrant_code_eq contacts_ident_eq

View file

@ -15,7 +15,6 @@ module Actions
assign_registrant assign_registrant
assign_nameservers assign_nameservers
assign_domain_contacts assign_domain_contacts
# domain.attach_default_contacts
assign_expiry_time assign_expiry_time
maybe_attach_legal_doc maybe_attach_legal_doc
@ -38,7 +37,69 @@ module Actions
false false
end end
# Check if domain is eligible for new registration def check_for_cross_role_duplicates
@removed_duplicates = []
registrant_contact = domain.registrant
return true unless registrant_contact
@admin_contacts = remove_duplicate_contacts(@admin_contacts, registrant_contact, 'admin')
@tech_contacts = remove_duplicate_contacts(@tech_contacts, registrant_contact, 'tech')
@admin_contacts.each do |admin|
contact = Contact.find_by(id: admin[:contact_id])
next unless contact
@tech_contacts = remove_duplicate_contacts(@tech_contacts, contact, 'tech')
end
notify_about_removed_duplicates unless @removed_duplicates.empty?
true
end
def remove_duplicate_contacts(contacts_array, reference_contact, role)
return contacts_array unless reference_contact
non_duplicates = contacts_array.reject do |contact_hash|
contact = Contact.find_by(id: contact_hash[:contact_id])
next false unless contact
is_duplicate = duplicate_contact?(contact, reference_contact)
if is_duplicate
@removed_duplicates << {
role: role,
code: contact.code,
duplicate_of: reference_contact.code
}
end
is_duplicate
end
non_duplicates
end
def duplicate_contact?(contact1, contact2)
return false unless contact1 && contact2
contact1.code == contact2.code ||
(contact1.name == contact2.name &&
contact1.ident == contact2.ident &&
contact1.email == contact2.email &&
contact1.phone == contact2.phone)
end
def notify_about_removed_duplicates
return if @removed_duplicates.empty?
message = ''
@removed_duplicates.each do |duplicate|
message += ". #{duplicate[:role].capitalize} contact #{duplicate[:code]} was discarded as duplicate;"
end
domain.skipped_domain_contacts_validation = message
end
def validate_domain_integrity def validate_domain_integrity
return unless Domain.release_to_auction return unless Domain.release_to_auction
@ -132,9 +193,11 @@ module Actions
params[:admin_contacts]&.each { |c| assign_contact(c) } params[:admin_contacts]&.each { |c| assign_contact(c) }
params[:tech_contacts]&.each { |c| assign_contact(c, admin: false) } params[:tech_contacts]&.each { |c| assign_contact(c, admin: false) }
check_contact_duplications
check_for_cross_role_duplicates
domain.admin_domain_contacts_attributes = @admin_contacts domain.admin_domain_contacts_attributes = @admin_contacts
domain.tech_domain_contacts_attributes = @tech_contacts domain.tech_domain_contacts_attributes = @tech_contacts
check_contact_duplications
end end
def assign_expiry_time def assign_expiry_time

View file

@ -29,6 +29,7 @@ module Actions
assign_admin_contact_changes assign_admin_contact_changes
assign_tech_contact_changes assign_tech_contact_changes
check_for_cross_role_duplicates
end end
def check_for_same_contacts(contacts, contact_type) def check_for_same_contacts(contacts, contact_type)
@ -37,6 +38,150 @@ module Actions
domain.add_epp_error('2306', contact_type, nil, %i[domain_contacts invalid]) domain.add_epp_error('2306', contact_type, nil, %i[domain_contacts invalid])
end end
def check_for_cross_role_duplicates
@removed_duplicates = []
registrant_contact = domain.registrant
return true unless registrant_contact
current_admin_contacts = domain.admin_domain_contacts.map { |dc| { contact_id: dc.contact_id, contact_code: dc.contact.code } }
current_tech_contacts = domain.tech_domain_contacts.map { |dc| { contact_id: dc.contact_id, contact_code: dc.contact.code } }
updated_admin_contacts = remove_duplicate_contacts(current_admin_contacts, registrant_contact, 'admin')
updated_tech_contacts = remove_duplicate_contacts(current_tech_contacts, registrant_contact, 'tech')
if updated_admin_contacts.present?
updated_admin_contacts.each do |admin_hash|
admin_contact_object = Contact.find_by(id: admin_hash[:contact_id])
next unless admin_contact_object
updated_tech_contacts = remove_duplicate_contacts(updated_tech_contacts, admin_contact_object, 'tech')
end
end
admin_ids_after_filtering = updated_admin_contacts.map { |c| c[:contact_id] }
current_admin_ids_from_map = current_admin_contacts.map { |c| c[:contact_id] }
domain.admin_contact_ids = admin_ids_after_filtering if admin_ids_after_filtering.sort != current_admin_ids_from_map.sort
tech_ids_after_filtering = updated_tech_contacts.map { |c| c[:contact_id] }
current_tech_ids_from_map = current_tech_contacts.map { |c| c[:contact_id] }
domain.tech_contact_ids = tech_ids_after_filtering if tech_ids_after_filtering.sort != current_tech_ids_from_map.sort
notify_about_removed_duplicates unless @removed_duplicates.empty?
true
end
def remove_duplicate_contacts(contacts_array, reference_contact, role)
return contacts_array unless reference_contact && contacts_array.present?
contacts_array.reject do |contact_hash|
contact = Contact.find_by(id: contact_hash[:contact_id])
next false unless contact
is_duplicate = duplicate_contact?(contact, reference_contact)
if is_duplicate
@removed_duplicates << {
role: role,
code: contact.code,
duplicate_of: reference_contact.code
}
end
is_duplicate
end
end
def duplicate_contact?(contact1, contact2)
return false unless contact1 && contact2
contact1.code == contact2.code ||
(contact1.name == contact2.name &&
contact1.ident == contact2.ident &&
contact1.email == contact2.email &&
contact1.phone == contact2.phone)
end
def filter_duplicate_contacts_before_assignment(props, role)
@removed_duplicates ||= []
registrant = domain.registrant
# Get existing contacts
existing_admin_contacts = domain.admin_domain_contacts.map(&:contact)
existing_tech_contacts = domain.tech_domain_contacts.map(&:contact)
# Filter new contacts being added
filtered_props = props.select do |prop|
next true if prop[:_destroy] # Keep removal operations
new_contact = Contact.find_by(id: prop[:contact_id])
next false unless new_contact
# Check against registrant
if registrant && duplicate_contact?(new_contact, registrant)
@removed_duplicates << {
role: role,
code: new_contact.code,
duplicate_of: registrant.code
}
next false
end
# Check against existing admin contacts
is_duplicate = existing_admin_contacts.any? { |existing| duplicate_contact?(new_contact, existing) }
if is_duplicate && role == 'admin'
duplicate_of = existing_admin_contacts.find { |existing| duplicate_contact?(new_contact, existing) }
@removed_duplicates << {
role: role,
code: new_contact.code,
duplicate_of: duplicate_of.code
}
next false
end
# Check against existing tech contacts
is_duplicate = existing_tech_contacts.any? { |existing| duplicate_contact?(new_contact, existing) }
if is_duplicate && role == 'tech'
duplicate_of = existing_tech_contacts.find { |existing| duplicate_contact?(new_contact, existing) }
@removed_duplicates << {
role: role,
code: new_contact.code,
duplicate_of: duplicate_of.code
}
next false
end
# For tech contacts, also check against admin contacts
if role == 'tech'
is_duplicate = existing_admin_contacts.any? { |existing| duplicate_contact?(new_contact, existing) }
if is_duplicate
duplicate_of = existing_admin_contacts.find { |existing| duplicate_contact?(new_contact, existing) }
@removed_duplicates << {
role: role,
code: new_contact.code,
duplicate_of: duplicate_of.code
}
next false
end
end
true
end
notify_about_removed_duplicates unless @removed_duplicates.empty?
filtered_props
end
def notify_about_removed_duplicates
return if @removed_duplicates.empty?
# Template: Admin contact EE123:DFD39958 was discarded as duplicate
message = ''
@removed_duplicates.each do |duplicate|
message += ". #{duplicate[:role].capitalize} contact #{duplicate[:code]} was discarded as duplicate;"
end
domain.skipped_domain_contacts_validation = message
end
def validate_domain_integrity def validate_domain_integrity
domain.auth_info = params[:transfer_code] if params[:transfer_code] domain.auth_info = params[:transfer_code] if params[:transfer_code]
@ -161,8 +306,10 @@ module Actions
domain.add_epp_error('2304', 'admin', DomainStatus::SERVER_ADMIN_CHANGE_PROHIBITED, domain.add_epp_error('2304', 'admin', DomainStatus::SERVER_ADMIN_CHANGE_PROHIBITED,
I18n.t(:object_status_prohibits_operation)) I18n.t(:object_status_prohibits_operation))
elsif props.present? elsif props.present?
domain.admin_domain_contacts_attributes = props # Filter duplicates before assignment
check_for_same_contacts(props, 'admin') props = filter_duplicate_contacts_before_assignment(props, 'admin')
domain.admin_domain_contacts_attributes = props if props.present?
check_for_same_contacts(props, 'admin') if props.present?
end end
end end
@ -176,8 +323,10 @@ module Actions
domain.add_epp_error('2304', 'tech', DomainStatus::SERVER_TECH_CHANGE_PROHIBITED, domain.add_epp_error('2304', 'tech', DomainStatus::SERVER_TECH_CHANGE_PROHIBITED,
I18n.t(:object_status_prohibits_operation)) I18n.t(:object_status_prohibits_operation))
elsif props.present? elsif props.present?
domain.tech_domain_contacts_attributes = props # Filter duplicates before assignment
check_for_same_contacts(props, 'tech') props = filter_duplicate_contacts_before_assignment(props, 'tech')
domain.tech_domain_contacts_attributes = props if props.present?
check_for_same_contacts(props, 'tech') if props.present?
end end
end end

View file

@ -93,6 +93,7 @@ class Domain < ApplicationRecord
has_one :csync_record, dependent: :destroy has_one :csync_record, dependent: :destroy
attribute :skip_whois_record_update, :boolean, default: false attribute :skip_whois_record_update, :boolean, default: false
attribute :skipped_domain_contacts_validation, :string, default: ''
after_initialize do after_initialize do
self.pending_json = {} if pending_json.blank? self.pending_json = {} if pending_json.blank?

View file

@ -1,7 +1,7 @@
xml.epp_head do xml.epp_head do
xml.response do xml.response do
xml.result('code' => '1000') do xml.result('code' => '1000') do
xml.msg 'Command completed successfully' xml.msg "Command completed successfully#{@domain.skipped_domain_contacts_validation if @domain.skipped_domain_contacts_validation.present?}"
end end
xml.resData do xml.resData do

View file

@ -1,7 +1,7 @@
xml.epp_head do xml.epp_head do
xml.response do xml.response do
xml.result('code' => '1000') do xml.result('code' => '1000') do
xml.msg 'Command completed successfully' xml.msg "Command completed successfully#{@domain.skipped_domain_contacts_validation if @domain && @domain.respond_to?(:skipped_domain_contacts_validation) && @domain.skipped_domain_contacts_validation.present?}"
end end
render('epp/shared/trID', builder: xml) render('epp/shared/trID', builder: xml)

View file

@ -603,7 +603,8 @@ class EppDomainCreateBaseTest < EppTestCase
travel_to now travel_to now
name = "new.#{dns_zones(:one).origin}" name = "new.#{dns_zones(:one).origin}"
contact = contacts(:john) contact = contacts(:john)
registrant = contact.becomes(Registrant) # registrant = contact.becomes(Registrant)
registrant = contacts(:william)
registrant.update!(ident_type: 'org') registrant.update!(ident_type: 'org')
registrant.reload registrant.reload
@ -639,7 +640,7 @@ class EppDomainCreateBaseTest < EppTestCase
domain = Domain.find_by(name: name) domain = Domain.find_by(name: name)
assert_equal name, domain.name assert_equal name, domain.name
assert_equal registrant, domain.registrant assert_equal registrant.code, domain.registrant.code
assert_equal [contact], domain.admin_contacts assert_equal [contact], domain.admin_contacts
assert_empty domain.tech_contacts assert_empty domain.tech_contacts
assert_not_empty domain.transfer_code assert_not_empty domain.transfer_code
@ -1186,4 +1187,434 @@ class EppDomainCreateBaseTest < EppTestCase
assert_correct_against_schema response_xml assert_correct_against_schema response_xml
assert_epp_response :completed_successfully assert_epp_response :completed_successfully
end end
def test_registers_domain_with_duplicate_registrant_and_admin
duplicate_contact = Contact.create!(
name: 'Duplicate Test',
code: 'duplicate-001',
email: 'duplicate@test.com',
phone: '+123.4567890',
ident: '12345X',
ident_type: 'priv',
ident_country_code: 'US',
registrar: registrars(:bestnames)
)
registrant = duplicate_contact.becomes(Registrant)
admin_contact = Contact.create!(
name: duplicate_contact.name,
code: 'duplicate-admin-001',
email: duplicate_contact.email,
phone: duplicate_contact.phone,
ident: duplicate_contact.ident,
ident_type: duplicate_contact.ident_type,
ident_country_code: duplicate_contact.ident_country_code,
registrar: registrars(:bestnames)
)
name = "domain-reg-admin-duplicate-#{Time.now.to_i}.#{dns_zones(:one).origin}"
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<create>
<domain:create xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{name}</domain:name>
<domain:registrant>#{registrant.code}</domain:registrant>
<domain:contact type="admin">#{admin_contact.code}</domain:contact>
</domain:create>
</create>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
assert_difference 'Domain.count', 1 do
post epp_create_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
response_xml = Nokogiri::XML(response.body)
assert_correct_against_schema response_xml
assert_epp_response :completed_successfully
domain = Domain.find_by(name: name)
assert_not_nil domain, "Domain should have been created"
assert response.body.include? "Admin contact #{admin_contact.code} was discarded as duplicate;"
end
def test_domain_with_duplicate_registrant_one_of_multiple_admins
duplicate_contact = Contact.create!(
name: 'Duplicate Test',
code: 'duplicate-002',
email: 'duplicate@test.com',
phone: '+123.4567890',
ident: '12345X',
ident_type: 'priv',
ident_country_code: 'US',
registrar: registrars(:bestnames)
)
registrant = duplicate_contact.becomes(Registrant)
admin1 = Contact.create!(
name: duplicate_contact.name,
code: 'duplicate-admin-002',
email: duplicate_contact.email,
phone: duplicate_contact.phone,
ident: duplicate_contact.ident,
ident_type: duplicate_contact.ident_type,
ident_country_code: duplicate_contact.ident_country_code,
registrar: registrars(:bestnames)
)
admin2 = contacts(:william)
name = "domain-reg-admin-multiple-#{Time.now.to_i}.#{dns_zones(:one).origin}"
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<create>
<domain:create xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{name}</domain:name>
<domain:registrant>#{registrant.code}</domain:registrant>
<domain:contact type="admin">#{admin1.code}</domain:contact>
<domain:contact type="admin">#{admin2.code}</domain:contact>
</domain:create>
</create>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
assert_difference 'Domain.count', 1 do
post epp_create_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
response_xml = Nokogiri::XML(response.body)
assert_correct_against_schema response_xml
assert_epp_response :completed_successfully
domain = Domain.find_by(name: name)
assert_not_nil domain, "Domain should have been created"
assert_equal 1, domain.admin_contacts.count, "Should have only one admin contact"
assert_equal admin2.code, domain.admin_contacts.first.code, "Should keep the non-duplicate admin"
assert response.body.include? "Admin contact #{admin1.code} was discarded as duplicate;"
end
def test_domain_with_duplicate_admin_and_tech
registrant = contacts(:acme_ltd).becomes(Registrant)
admin = Contact.create!(
name: 'Duplicate Admin Tech Test',
code: 'duplicate-admin-003',
email: 'admin-tech@test.com',
phone: '+123.4567890',
ident: '12346X',
ident_type: 'priv',
ident_country_code: 'US',
registrar: registrars(:bestnames)
)
tech = Contact.create!(
name: admin.name,
code: 'duplicate-tech-003',
email: admin.email,
phone: admin.phone,
ident: admin.ident,
ident_type: admin.ident_type,
ident_country_code: admin.ident_country_code,
registrar: registrars(:bestnames)
)
name = "domain-admin-tech-duplicate-#{Time.now.to_i}.#{dns_zones(:one).origin}"
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<create>
<domain:create xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{name}</domain:name>
<domain:registrant>#{registrant.code}</domain:registrant>
<domain:contact type="admin">#{admin.code}</domain:contact>
<domain:contact type="tech">#{tech.code}</domain:contact>
</domain:create>
</create>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
assert_difference 'Domain.count', 1 do
post epp_create_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
response_xml = Nokogiri::XML(response.body)
assert_correct_against_schema response_xml
assert_epp_response :completed_successfully
domain = Domain.find_by(name: name)
assert_not_nil domain, "Domain should have been created"
assert_equal 1, domain.admin_contacts.count, "Should have one admin contact"
assert_equal admin.code, domain.admin_contacts.first.code, "Should keep the admin contact"
assert_empty domain.tech_contacts, "Tech contacts should be empty due to duplication with admin"
assert response.body.include? "Tech contact #{tech.code} was discarded as duplicate;"
end
def test_domain_with_duplicate_one_admin_one_tech
registrant = contacts(:acme_ltd).becomes(Registrant)
admin1 = Contact.create!(
name: 'First Admin',
code: 'duplicate-admin-004',
email: 'first-admin@test.com',
phone: '+123.4567890',
ident: '12347X',
ident_type: 'priv',
ident_country_code: 'US',
registrar: registrars(:bestnames)
)
admin2 = contacts(:william)
tech1 = Contact.create!(
name: admin1.name,
code: 'duplicate-tech-004',
email: admin1.email,
phone: admin1.phone,
ident: admin1.ident,
ident_type: admin1.ident_type,
ident_country_code: admin1.ident_country_code,
registrar: registrars(:bestnames)
)
tech2 = contacts(:jack)
name = "domain-one-admin-one-tech-dup-#{Time.now.to_i}.#{dns_zones(:one).origin}"
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<create>
<domain:create xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{name}</domain:name>
<domain:registrant>#{registrant.code}</domain:registrant>
<domain:contact type="admin">#{admin1.code}</domain:contact>
<domain:contact type="admin">#{admin2.code}</domain:contact>
<domain:contact type="tech">#{tech1.code}</domain:contact>
<domain:contact type="tech">#{tech2.code}</domain:contact>
</domain:create>
</create>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
assert_difference 'Domain.count', 1 do
post epp_create_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
response_xml = Nokogiri::XML(response.body)
assert_correct_against_schema response_xml
assert_epp_response :completed_successfully
domain = Domain.find_by(name: name)
assert_not_nil domain, "Domain should have been created"
assert_equal 2, domain.admin_contacts.count, "Should have both admin contacts"
tech_contacts = domain.tech_contacts
assert_equal 1, tech_contacts.count, "Should have only the non-duplicate tech contact"
assert_equal tech2.code, tech_contacts.first.code, "Should keep the non-duplicate tech contact"
assert response.body.include? "Tech contact #{tech1.code} was discarded as duplicate;"
end
def test_domain_with_duplicate_registrant_admin_tech
duplicate_contact = Contact.create!(
name: 'Full Duplicate Test',
code: 'duplicate-005',
email: 'full-duplicate@test.com',
phone: '+123.5678901',
ident: '12348X',
ident_type: 'priv',
ident_country_code: 'US',
registrar: registrars(:bestnames)
)
registrant = duplicate_contact.becomes(Registrant)
admin = Contact.create!(
name: duplicate_contact.name,
code: 'duplicate-admin-005',
email: duplicate_contact.email,
phone: duplicate_contact.phone,
ident: duplicate_contact.ident,
ident_type: duplicate_contact.ident_type,
ident_country_code: duplicate_contact.ident_country_code,
registrar: registrars(:bestnames)
)
tech = Contact.create!(
name: duplicate_contact.name,
code: 'duplicate-tech-005',
email: duplicate_contact.email,
phone: duplicate_contact.phone,
ident: duplicate_contact.ident,
ident_type: duplicate_contact.ident_type,
ident_country_code: duplicate_contact.ident_country_code,
registrar: registrars(:bestnames)
)
name = "domain-all-duplicates-#{Time.now.to_i}.#{dns_zones(:one).origin}"
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<create>
<domain:create xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{name}</domain:name>
<domain:registrant>#{registrant.code}</domain:registrant>
<domain:contact type="admin">#{admin.code}</domain:contact>
<domain:contact type="tech">#{tech.code}</domain:contact>
</domain:create>
</create>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
assert_difference 'Domain.count', 1 do
post epp_create_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
response_xml = Nokogiri::XML(response.body)
assert_correct_against_schema response_xml
assert_epp_response :completed_successfully
domain = Domain.find_by(name: name)
assert_not_nil domain, "Domain should have been created"
assert_empty domain.admin_contacts, "Admin contacts should be empty due to duplication"
assert_empty domain.tech_contacts, "Tech contacts should be empty due to duplication"
assert response.body.include? "Admin contact #{admin.code} was discarded as duplicate;"
assert response.body.include? "Tech contact #{tech.code} was discarded as duplicate;"
end
def test_domain_with_duplicate_registrant_one_admin_one_tech
duplicate_contact = Contact.create!(
name: 'Partial Duplicate Test',
code: 'duplicate-006',
email: 'partial-duplicate@test.com',
phone: '+123.6789012',
ident: '12349X',
ident_type: 'priv',
ident_country_code: 'US',
registrar: registrars(:bestnames)
)
registrant = duplicate_contact.becomes(Registrant)
admin1 = Contact.create!(
name: duplicate_contact.name,
code: 'duplicate-admin-006',
email: duplicate_contact.email,
phone: duplicate_contact.phone,
ident: duplicate_contact.ident,
ident_type: 'priv',
ident_country_code: duplicate_contact.ident_country_code,
registrar: registrars(:bestnames)
)
admin2 = contacts(:jack)
admin2.ident_type = 'priv'
admin2.save!
tech1 = Contact.create!(
name: duplicate_contact.name,
code: 'duplicate-tech-006',
email: duplicate_contact.email,
phone: duplicate_contact.phone,
ident: duplicate_contact.ident,
ident_type: duplicate_contact.ident_type,
ident_country_code: duplicate_contact.ident_country_code,
registrar: registrars(:bestnames)
)
tech2 = contacts(:william)
name = "domain-partial-duplicates-#{Time.now.to_i}.#{dns_zones(:one).origin}"
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<create>
<domain:create xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{name}</domain:name>
<domain:registrant>#{registrant.code}</domain:registrant>
<domain:contact type="admin">#{admin1.code}</domain:contact>
<domain:contact type="admin">#{admin2.code}</domain:contact>
<domain:contact type="tech">#{tech1.code}</domain:contact>
<domain:contact type="tech">#{tech2.code}</domain:contact>
</domain:create>
</create>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
assert_difference 'Domain.count', 1 do
post epp_create_path, params: { frame: request_xml },
headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
end
response_xml = Nokogiri::XML(response.body)
assert_correct_against_schema response_xml
assert_epp_response :completed_successfully
domain = Domain.find_by(name: name)
assert_not_nil domain, "Domain should have been created"
assert_equal 1, domain.admin_contacts.count, "Should have only the non-duplicate admin contact"
assert_equal admin2.code, domain.admin_contacts.first.code, "Should keep the non-duplicate admin contact"
assert_equal 1, domain.tech_contacts.count, "Should have only the non-duplicate tech contact"
assert_equal tech2.code, domain.tech_contacts.first.code, "Should keep the non-duplicate tech contact"
assert response.body.include? "Admin contact #{admin1.code} was discarded as duplicate;"
assert response.body.include? "Tech contact #{tech1.code} was discarded as duplicate;"
end
end end

View file

@ -1091,6 +1091,229 @@ class EppDomainUpdateBaseTest < EppTestCase
assert_epp_response :object_status_prohibits_operation assert_epp_response :object_status_prohibits_operation
end end
# UPDATE TESTS FOR DUPLICATE CONTACTS
def test_update_domain_with_duplicate_registrant_and_single_admin
domain = domains(:shop)
duplicate_contact = Contact.create!(
name: 'Partial Duplicate Test',
code: 'duplicate-006',
email: 'partial-duplicate@test.com',
phone: '+123.6789012',
ident: '12349X',
ident_type: 'priv',
ident_country_code: 'US',
registrar: registrars(:bestnames)
)
registrant = duplicate_contact.becomes(Registrant)
new_admin = Contact.create!(
name: duplicate_contact.name,
code: 'duplicate-admin-006',
email: duplicate_contact.email,
phone: duplicate_contact.phone,
ident: duplicate_contact.ident,
ident_type: 'priv',
ident_country_code: duplicate_contact.ident_country_code,
registrar: registrars(:bestnames)
)
domain.update(registrant: registrant) && domain.reload
old_admin = domain.admin_contacts.first
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<update>
<domain:update xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{domain.name}</domain:name>
<domain:chg/>
<domain:add>
<domain:contact type="admin">#{new_admin.code}</domain:contact>
</domain:add>
<domain:rem>
<domain:contact type="admin">#{old_admin.code}</domain:contact>
</domain:rem>
</domain:update>
</update>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
post epp_update_path(domain_name: domain.name), params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' }
domain.reload
assert_epp_response :completed_successfully
assert response.body.include? "Admin contact #{new_admin.code} was discarded as duplicate;"
end
def test_update_domain_with_registrant_admin_tech_all_duplicates
domain = domains(:airport)
initial_admin_contact = contacts(:jane)
initial_tech_contact = contacts(:william)
domain.admin_contacts = [initial_admin_contact]
domain.tech_contacts = [initial_tech_contact]
domain.save!
domain.reload
base_duplicate_data_contact = Contact.create!(
name: 'All Roles Are The Same Person',
code: "base-all-roles-#{SecureRandom.hex(3)}",
email: 'all.roles.same.person@example.com',
phone: '+1.999888777',
ident: 'ARSAMEP123',
ident_type: 'priv',
ident_country_code: 'US',
registrar: domain.registrar
)
new_registrant = base_duplicate_data_contact.becomes(Registrant)
domain.update!(registrant: new_registrant)
domain.reload
new_admin_being_added = Contact.create!(
name: base_duplicate_data_contact.name,
code: "admin-dup-all-roles-#{SecureRandom.hex(3)}",
email: base_duplicate_data_contact.email,
phone: base_duplicate_data_contact.phone,
ident: base_duplicate_data_contact.ident,
ident_type: base_duplicate_data_contact.ident_type,
ident_country_code: base_duplicate_data_contact.ident_country_code,
registrar: domain.registrar
)
new_tech_being_added = Contact.create!(
name: base_duplicate_data_contact.name,
code: "tech-dup-all-roles-#{SecureRandom.hex(3)}",
email: base_duplicate_data_contact.email,
phone: base_duplicate_data_contact.phone,
ident: base_duplicate_data_contact.ident,
ident_type: base_duplicate_data_contact.ident_type,
ident_country_code: base_duplicate_data_contact.ident_country_code,
registrar: domain.registrar
)
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<update>
<domain:update xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{domain.name}</domain:name>
<domain:chg/>
<domain:add>
<domain:contact type="admin">#{new_admin_being_added.code}</domain:contact>
<domain:contact type="tech">#{new_tech_being_added.code}</domain:contact>
</domain:add>
<domain:rem>
<domain:contact type="admin">#{initial_admin_contact.code}</domain:contact>
<domain:contact type="tech">#{initial_tech_contact.code}</domain:contact>
</domain:rem>
</domain:update>
</update>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
post epp_update_path(domain_name: domain.name), params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => "session=api_bestnames" }
domain.reload
assert_epp_response :completed_successfully
assert response.body.include? "Admin contact #{new_admin_being_added.code} was discarded as duplicate;"
assert response.body.include? "Tech contact #{new_tech_being_added.code} was discarded as duplicate;"
end
def test_update_domain_with_duplicate_admin_and_tech_registrant_is_different
domain = domains(:library)
initial_admin_contact = contacts(:john)
initial_tech_contact = contacts(:william)
domain.admin_contacts = [initial_admin_contact]
domain.tech_contacts = [initial_tech_contact]
domain.save!
domain.reload
admin_tech_duplicate_base = Contact.create!(
name: 'Admin And Tech Are Same Person',
code: "base-adm-tech-#{SecureRandom.hex(3)}",
email: 'admin.tech.same.person@example.com',
phone: '+1.555444333',
ident: 'ATSAMEP456',
ident_type: 'priv',
ident_country_code: 'US',
registrar: domain.registrar
)
new_admin_being_added = Contact.create!(
name: admin_tech_duplicate_base.name,
code: "admin-dup-adm-tech-#{SecureRandom.hex(3)}",
email: admin_tech_duplicate_base.email,
phone: admin_tech_duplicate_base.phone,
ident: admin_tech_duplicate_base.ident,
ident_type: admin_tech_duplicate_base.ident_type,
ident_country_code: admin_tech_duplicate_base.ident_country_code,
registrar: domain.registrar
)
new_tech_being_added_and_skipped = Contact.create!(
name: admin_tech_duplicate_base.name,
code: "tech-dup-adm-tech-#{SecureRandom.hex(3)}",
email: admin_tech_duplicate_base.email,
phone: admin_tech_duplicate_base.phone,
ident: admin_tech_duplicate_base.ident,
ident_type: admin_tech_duplicate_base.ident_type,
ident_country_code: admin_tech_duplicate_base.ident_country_code,
registrar: domain.registrar
)
request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="#{Xsd::Schema.filename(for_prefix: 'epp-ee', for_version: '1.0')}">
<command>
<update>
<domain:update xmlns:domain="#{Xsd::Schema.filename(for_prefix: 'domain-ee', for_version: '1.2')}">
<domain:name>#{domain.name}</domain:name>
<domain:chg/>
<domain:add>
<domain:contact type="admin">#{new_admin_being_added.code}</domain:contact>
<domain:contact type="tech">#{new_tech_being_added_and_skipped.code}</domain:contact>
</domain:add>
<domain:rem>
<domain:contact type="admin">#{initial_admin_contact.code}</domain:contact>
<domain:contact type="tech">#{initial_tech_contact.code}</domain:contact>
</domain:rem>
</domain:update>
</update>
<extension>
<eis:extdata xmlns:eis="#{Xsd::Schema.filename(for_prefix: 'eis', for_version: '1.0')}">
<eis:legalDocument type="pdf">#{'test' * 2000}</eis:legalDocument>
</eis:extdata>
</extension>
</command>
</epp>
XML
post epp_update_path(domain_name: domain.name), params: { frame: request_xml }, headers: { 'HTTP_COOKIE' => "session=api_bestnames" }
domain.reload
assert_epp_response :completed_successfully
assert response.body.include? "Tech contact #{new_tech_being_added_and_skipped.code} was discarded as duplicate;"
end
private private
def assert_verification_and_notification_emails def assert_verification_and_notification_emails

View file

@ -51,7 +51,10 @@ class ReppV1DomainsContactsTest < ActionDispatch::IntegrationTest
assert_response :ok assert_response :ok
assert_equal 1000, json[:code] assert_equal 1000, json[:code]
assert @domain.admin_contacts.find_by(code: new_contact.code).present? # Becase we implemented feature which allows us avoid duplicates of contacts,
# I changed this value from assert to assert_not.
# PS! Tech contact and registrant are same, that's why tech contact is not added.
assert_not @domain.admin_contacts.find_by(code: new_contact.code).present?
end end
def test_can_add_new_tech_contacts def test_can_add_new_tech_contacts
@ -66,13 +69,16 @@ class ReppV1DomainsContactsTest < ActionDispatch::IntegrationTest
assert_equal 1000, json[:code] assert_equal 1000, json[:code]
@domain.reload @domain.reload
assert @domain.tech_contacts.find_by(code: new_contact.code).present? # Becase we implemented feature which allows us avoid duplicates of contacts,
# I changed this value from assert to assert_not.
# PS! Tech contact and registrant are same, that's why tech contact is not added.
assert_not @domain.tech_contacts.find_by(code: new_contact.code).present?
end end
def test_can_remove_admin_contacts def test_can_remove_admin_contacts
Spy.on_instance_method(Actions::DomainUpdate, :validate_email).and_return(true) Spy.on_instance_method(Actions::DomainUpdate, :validate_email).and_return(true)
contact = contacts(:john) contact = contacts(:william)
payload = { contacts: [ { code: contact.code, type: 'admin' } ] } payload = { contacts: [ { code: contact.code, type: 'admin' } ] }
post "/repp/v1/domains/#{@domain.name}/contacts", headers: @auth_headers, params: payload post "/repp/v1/domains/#{@domain.name}/contacts", headers: @auth_headers, params: payload
assert @domain.admin_contacts.find_by(code: contact.code).present? assert @domain.admin_contacts.find_by(code: contact.code).present?
@ -90,7 +96,7 @@ class ReppV1DomainsContactsTest < ActionDispatch::IntegrationTest
def test_can_remove_tech_contacts def test_can_remove_tech_contacts
Spy.on_instance_method(Actions::DomainUpdate, :validate_email).and_return(true) Spy.on_instance_method(Actions::DomainUpdate, :validate_email).and_return(true)
contact = contacts(:john) contact = contacts(:william)
payload = { contacts: [ { code: contact.code, type: 'tech' } ] } payload = { contacts: [ { code: contact.code, type: 'tech' } ] }
post "/repp/v1/domains/#{@domain.name}/contacts", headers: @auth_headers, params: payload post "/repp/v1/domains/#{@domain.name}/contacts", headers: @auth_headers, params: payload
assert @domain.tech_contacts.find_by(code: contact.code).present? assert @domain.tech_contacts.find_by(code: contact.code).present?
@ -240,4 +246,47 @@ class ReppV1DomainsContactsTest < ActionDispatch::IntegrationTest
assert_equal 1000, json[:code] assert_equal 1000, json[:code]
assert_empty @domain.admin_contacts assert_empty @domain.admin_contacts
end end
def test_updates_add_duplicate_admin_contact
domain = domains(:shop)
duplicate_contact = Contact.create!(
name: 'Partial Duplicate Test',
code: 'duplicate-006',
email: 'partial-duplicate@test.com',
phone: '+123.6789012',
ident: '12349X',
ident_type: 'priv',
ident_country_code: 'US',
registrar: registrars(:bestnames)
)
registrant = duplicate_contact.becomes(Registrant)
new_admin = Contact.create!(
name: duplicate_contact.name,
code: 'duplicate-admin-006',
email: duplicate_contact.email,
phone: duplicate_contact.phone,
ident: duplicate_contact.ident,
ident_type: 'priv',
ident_country_code: duplicate_contact.ident_country_code,
registrar: registrars(:bestnames)
)
domain.update(registrant: registrant) && domain.reload
old_admin = domain.admin_contacts.first
assert_includes domain.admin_contacts, old_admin
payload = { contacts: [ { code: new_admin.code, type: 'admin' } ] }
post "/repp/v1/domains/#{domain.name}/contacts", headers: @auth_headers, params: payload
json = JSON.parse(response.body, symbolize_names: true)
assert_response :ok
assert_equal 1000, json[:code]
domain.reload
assert_not domain.admin_contacts.find_by(code: new_admin.code).present?
end
end end