From 611795b06a1d46c2e1418ac5eca1b6671f2e2779 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Mon, 5 Aug 2024 12:34:32 +0300 Subject: [PATCH] added addition name variants and added punycode support for domain reservation --- app/models/reserved_domain.rb | 9 ++- app/models/reserved_domain_status.rb | 7 +++ .../domain_name_generator_service.rb | 34 +++++++---- app/validators/domain_name_validator.rb | 3 +- test/models/reserved_domain_status_test.rb | 15 +++++ test/models/reserved_domain_test.rb | 19 ++++++ .../domain_name_generator_service_test.rb | 58 ++++++++++++++++--- 7 files changed, 125 insertions(+), 20 deletions(-) diff --git a/app/models/reserved_domain.rb b/app/models/reserved_domain.rb index 98ac0d46cb..43f6171d55 100644 --- a/app/models/reserved_domain.rb +++ b/app/models/reserved_domain.rb @@ -6,6 +6,7 @@ class ReservedDomain < ApplicationRecord before_save :sync_dispute_password after_destroy :remove_data + before_validation :normalize_name, on: %w[create, update] validates :name, domain_name: true, uniqueness: true alias_attribute :registration_code, :password @@ -40,7 +41,7 @@ def new_password_for(name) end def name=(val) - super SimpleIDN.to_unicode(val) + super SimpleIDN.to_unicode(val).mb_chars.downcase.strip end def fill_empty_passwords @@ -69,4 +70,10 @@ def generate_data def remove_data UpdateWhoisRecordJob.perform_later name, 'reserved' end + + private + + def normalize_name + self.name = SimpleIDN.to_unicode(name).mb_chars.downcase.strip + end end diff --git a/app/models/reserved_domain_status.rb b/app/models/reserved_domain_status.rb index a715f76cc7..d9fbba4d0f 100644 --- a/app/models/reserved_domain_status.rb +++ b/app/models/reserved_domain_status.rb @@ -6,6 +6,9 @@ class ReservedDomainStatus < ApplicationRecord enum status: { pending: 0, paid: 1, canceled: 2, failed: 3 } + before_validation :normalize_name, on: %w[create, update] + validates :name, domain_name: true + INITIATOR = 'business_registry'.freeze OK = '200'.freeze CREATED = '201'.freeze @@ -36,6 +39,10 @@ def reserve_domain private + def normalize_name + self.name = SimpleIDN.to_unicode(name).mb_chars.downcase.strip + end + def set_token_created_at self.token_created_at = Time.current end diff --git a/app/services/business_registry/domain_name_generator_service.rb b/app/services/business_registry/domain_name_generator_service.rb index 84fb674c8c..75662da677 100644 --- a/app/services/business_registry/domain_name_generator_service.rb +++ b/app/services/business_registry/domain_name_generator_service.rb @@ -1,11 +1,12 @@ module BusinessRegistry class DomainNameGeneratorService - LEGAL_FORMS = %w[AS OU FIE OÜ].freeze + LEGAL_FORMS = %w[AS OU FIE OÜ MTÜ].freeze def self.generate(name) - base_name = remove_legal_forms(sanitize_input(name)) - variants = generate_variants(base_name) - variants + generate_additional_variants(variants) + base_name = sanitize_input(name) + legal_form = extract_legal_form(base_name) + base_variants = generate_variants(base_name.sub(/\s+#{legal_form}\s*$/i, '')) + base_variants + generate_variants_with_legal_form(base_name, legal_form) end private @@ -14,9 +15,9 @@ def self.sanitize_input(name) name.gsub(/[^[:alnum:]\s\-]/, '').strip end - def self.remove_legal_forms(name) + def self.extract_legal_form(name) words = name.split - words.reject { |word| LEGAL_FORMS.include?(word.upcase) }.join(' ').strip + LEGAL_FORMS.find { |form| words.last.upcase == form } end def self.generate_variants(name) @@ -27,9 +28,22 @@ def self.generate_variants(name) ] end - def self.generate_additional_variants(variants) - current_year = Time.current.year - variants.map { |v| "#{v}#{current_year}" } + def self.generate_variants_with_legal_form(name, legal_form) + return [] unless legal_form + + base_name = name.sub(/\s+#{legal_form}\s*$/i, '').downcase + base_variants = generate_variants(base_name) + + legal_form_variants = [ + legal_form.downcase, + legal_form.downcase.tr('ü', 'u'), + "-#{legal_form.downcase}", + "-#{legal_form.downcase.tr('ü', 'u')}", + "_#{legal_form.downcase}", + "_#{legal_form.downcase.tr('ü', 'u')}" + ] + + base_variants.product(legal_form_variants).map(&:join) end end -end +end \ No newline at end of file diff --git a/app/validators/domain_name_validator.rb b/app/validators/domain_name_validator.rb index 5ca4e93a7f..99959064d0 100644 --- a/app/validators/domain_name_validator.rb +++ b/app/validators/domain_name_validator.rb @@ -18,7 +18,7 @@ def validate_format(value) origins = DNS::Zone.origins # if someone tries to register an origin domain, let this validation pass # the error will be caught in blocked domains validator - return true if origins.include?(value) + return true if origins.include?(value.split('.').last) general_domains = /(#{origins.join('|')})/ @@ -28,6 +28,7 @@ def validate_format(value) return false unless value.match?(regexp) value = SimpleIDN.to_unicode(value).mb_chars.downcase.strip + end unicode_chars = /\u00E4\u00F5\u00F6\u00FC\u0161\u017E/ # äõöüšž diff --git a/test/models/reserved_domain_status_test.rb b/test/models/reserved_domain_status_test.rb index d300242a7c..cf4b384de9 100644 --- a/test/models/reserved_domain_status_test.rb +++ b/test/models/reserved_domain_status_test.rb @@ -89,6 +89,21 @@ def setup assert_equal @reserved_domain_status.access_token, invoice.token end + def test_create_reserved_domain_with_punycode_name + reserved_domain = ReservedDomainStatus.create(name: 'xn--4ca7aey.test') + assert reserved_domain.valid? + end + + def test_create_reserved_domain_with_unicode_name + reserved_domain = ReservedDomainStatus.create(name: 'õäöü.test') + assert reserved_domain.valid? + end + + def test_cannot_to_register_invalid_domain_format + reserved_domain = ReservedDomainStatus.new(name: 'example') + assert_not reserved_domain.valid? + end + private def stub_eis_billing_requests diff --git a/test/models/reserved_domain_test.rb b/test/models/reserved_domain_test.rb index 2515a1e5d3..be249d8282 100644 --- a/test/models/reserved_domain_test.rb +++ b/test/models/reserved_domain_test.rb @@ -13,4 +13,23 @@ def test_aliases_registration_code_to_password reserved_domain = ReservedDomain.new(password: 'reserved-001') assert_equal 'reserved-001', reserved_domain.registration_code end + + def test_create_reserved_domain_with_punycode_name + reserved_domain = ReservedDomain.create(name: 'xn--4ca7aey.test') + assert reserved_domain.valid? + end + + def test_create_reserved_domain_with_unicode_name + reserved_domain = ReservedDomain.create(name: 'õäöü.test') + assert reserved_domain.valid? + end + + def test_cannot_create_the_same_domain_twicde_with_punycode_and_unicode + punycode_reserved_domain = ReservedDomain.new(name: 'xn--4ca7aey.test') + assert punycode_reserved_domain.valid? + punycode_reserved_domain.save && punycode_reserved_domain.reload + + unicode_reserved_domain = ReservedDomain.new(name: 'õäöü.test') + assert_not unicode_reserved_domain.valid? + end end diff --git a/test/services/business_registry/domain_name_generator_service_test.rb b/test/services/business_registry/domain_name_generator_service_test.rb index 46f12af7b2..d1ed7088c5 100644 --- a/test/services/business_registry/domain_name_generator_service_test.rb +++ b/test/services/business_registry/domain_name_generator_service_test.rb @@ -6,18 +6,60 @@ class BusinessRegistry::DomainNameGeneratorServiceTest < ActiveSupport::TestCase assert_includes variants, "testcompany" assert_includes variants, "test-company" assert_includes variants, "test_company" - assert_includes variants, "testcompany#{Time.current.year}" end - test "should remove legal forms" do - variants = BusinessRegistry::DomainNameGeneratorService.generate("Test Company AS") + test "should generate variants with legal forms" do + variants = BusinessRegistry::DomainNameGeneratorService.generate("Test Company OÜ") assert_includes variants, "testcompany" - refute_includes variants, "testcompanyas" + assert_includes variants, "test-company" + assert_includes variants, "test_company" + assert_includes variants, "testcompanyoü" + assert_includes variants, "testcompanyou" + assert_includes variants, "testcompany-oü" + assert_includes variants, "testcompany-ou" + assert_includes variants, "testcompany_oü" + assert_includes variants, "testcompany_ou" + assert_includes variants, "test-companyoü" + assert_includes variants, "test-companyou" + assert_includes variants, "test-company-oü" + assert_includes variants, "test-company-ou" + assert_includes variants, "test-company_oü" + assert_includes variants, "test-company_ou" + assert_includes variants, "test_companyoü" + assert_includes variants, "test_companyou" + assert_includes variants, "test_company-oü" + assert_includes variants, "test_company-ou" + assert_includes variants, "test_company_oü" + assert_includes variants, "test_company_ou" end - test "should generate variants with current year" do - variants = BusinessRegistry::DomainNameGeneratorService.generate("Test") - assert_includes variants, "test#{Time.current.year}" + test "should handle different legal forms" do + legal_forms = %w[AS OU FIE OÜ MTÜ] + legal_forms.each do |form| + variants = BusinessRegistry::DomainNameGeneratorService.generate("Test Company #{form}") + assert_includes variants, "testcompany#{form.downcase}" + assert_includes variants, "testcompany-#{form.downcase}" + assert_includes variants, "testcompany_#{form.downcase}" + assert_includes variants, "test-company#{form.downcase}" + assert_includes variants, "test-company-#{form.downcase}" + assert_includes variants, "test-company_#{form.downcase}" + assert_includes variants, "test_company#{form.downcase}" + assert_includes variants, "test_company-#{form.downcase}" + assert_includes variants, "test_company_#{form.downcase}" + + if form.include?('Ü') + alternative_form = form.tr('Ü', 'U').downcase + assert_includes variants, "testcompany#{alternative_form}" + assert_includes variants, "testcompany-#{alternative_form}" + assert_includes variants, "testcompany_#{alternative_form}" + assert_includes variants, "test-company#{alternative_form}" + assert_includes variants, "test-company-#{alternative_form}" + assert_includes variants, "test-company_#{alternative_form}" + assert_includes variants, "test_company#{alternative_form}" + assert_includes variants, "test_company-#{alternative_form}" + assert_includes variants, "test_company_#{alternative_form}" + end + end end test "should handle special characters" do @@ -25,4 +67,4 @@ class BusinessRegistry::DomainNameGeneratorServiceTest < ActiveSupport::TestCase assert_includes variants, "testcompany" refute_includes variants, "test&company" end -end \ No newline at end of file +end