diff --git a/Gemfile b/Gemfile index f968fecc4..c4e916929 100644 --- a/Gemfile +++ b/Gemfile @@ -69,7 +69,7 @@ gem 'redis' gem 'sidekiq', '~> 7.0' gem 'company_register', github: 'internetee/company_register', - branch: '4-check-for-company-existence' + branch: 'issues-with-upcoming-data' gem 'domain_name' gem 'e_invoice', github: 'internetee/e_invoice', branch: :master gem 'haml', '~> 6.0' @@ -108,3 +108,6 @@ gem 'pg_query', '>= 0.9.0' # token gem 'jwt' gem 'net-ftp' + +# https://stackoverflow.com/questions/79360526/uninitialized-constant-activesupportloggerthreadsafelevellogger-nameerror +gem 'concurrent-ruby', '1.3.4' diff --git a/Gemfile.lock b/Gemfile.lock index 876ed72b7..ace0966c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/internetee/company_register.git - revision: 6465d5c49478b9de5a5fa009cb6b8123b3956dd1 - branch: 4-check-for-company-existence + revision: 1e91fec78212d7e549a1c2362c011761a447bbcd + branch: issues-with-upcoming-data specs: company_register (0.1.0) activesupport @@ -170,7 +170,7 @@ GEM aws-eventstream (~> 1, >= 1.0.2) base64 (0.2.0) bcrypt (3.1.16) - bigdecimal (3.1.8) + bigdecimal (3.1.9) bindata (2.5.0) bootsnap (1.17.1) msgpack (~> 1.2) @@ -270,7 +270,7 @@ GEM mutex_m nkf rack (>= 2.0, < 4) - i18n (1.14.6) + i18n (1.14.7) concurrent-ruby (~> 1.0) i18n_data (0.13.0) isikukood (0.1.2) @@ -282,7 +282,7 @@ GEM thor (>= 0.14, < 2.0) jquery-ui-rails (6.0.1) railties (>= 3.2.16) - json (2.5.1) + json (2.9.1) json-jwt (1.16.6) activesupport (>= 4.2) aes_key_wrap @@ -304,12 +304,15 @@ GEM kaminari-core (= 1.2.1) kaminari-core (1.2.1) libxml-ruby (3.2.1) - logger (1.4.3) + logger (1.6.5) loofah (2.24.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.7.1) + mail (2.8.1) mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp marcel (1.0.4) matrix (0.4.2) method_source (1.1.0) @@ -321,7 +324,7 @@ GEM rake mini_mime (1.1.5) mini_portile2 (2.8.8) - minitest (5.25.1) + minitest (5.25.4) minitest-stub_any_instance (1.0.3) monetize (1.9.4) money (~> 6.12) @@ -333,12 +336,17 @@ GEM money (~> 6.13.2) railties (>= 3.0) msgpack (1.7.2) - mutex_m (0.2.0) + mutex_m (0.3.0) net-ftp (0.3.7) net-protocol time net-http (0.6.0) uri + net-imap (0.4.18) + date + net-protocol + net-pop (0.1.2) + net-protocol net-protocol (0.1.3) timeout net-smtp (0.3.3) @@ -452,7 +460,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.3.9) + rexml (3.4.0) rubyzip (2.3.2) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -534,7 +542,7 @@ GEM simpleidn warden (1.2.9) rack (>= 2.0.9) - wasabi (5.0.3) + wasabi (5.1.0) addressable faraday (>= 1.9, < 3) nokogiri (>= 1.13.9) @@ -571,6 +579,7 @@ DEPENDENCIES capybara (~> 3.40.0) coffee-rails (>= 5.0) company_register! + concurrent-ruby (= 1.3.4) countries data_migrate (~> 9.0) database_cleaner diff --git a/app/controllers/repp/v1/contacts_controller.rb b/app/controllers/repp/v1/contacts_controller.rb index cbb76e77c..a9b0c783c 100644 --- a/app/controllers/repp/v1/contacts_controller.rb +++ b/app/controllers/repp/v1/contacts_controller.rb @@ -94,7 +94,6 @@ module Repp action = Actions::ContactUpdate.new(@contact, contact_params_with_address(required: false), contact_params[:legal_document], contact_ident_params(required: false), current_user) - unless action.call handle_errors(@contact) return diff --git a/app/interactions/actions/contact_create.rb b/app/interactions/actions/contact_create.rb index 39fb3212d..b9d1d6596 100644 --- a/app/interactions/actions/contact_create.rb +++ b/app/interactions/actions/contact_create.rb @@ -12,11 +12,12 @@ module Actions def call maybe_remove_address maybe_attach_legal_doc - validate_ident + maybe_validate_ident maybe_change_email # maybe_company_is_relevant commit - validate_contact + maybe_validate_phone_number + maybe_validate_contact end def maybe_change_email @@ -45,7 +46,7 @@ module Actions contact.country_code = nil end - def validate_ident + def maybe_validate_ident validate_ident_integrity validate_ident_birthday @@ -102,12 +103,16 @@ module Actions contact.save end - def validate_contact + def maybe_validate_contact return if @error || !contact.valid? [:regex, :mx].each do |m| contact.verify_email(check_level: m, single_email: true) end end + + def maybe_validate_phone_number + OrgRegistrantPhoneCheckerJob.perform_later(type: 'single', registrant_user_code: contact.code) + end end end diff --git a/app/interactions/actions/contact_update.rb b/app/interactions/actions/contact_update.rb index a37f81b60..6831c449b 100644 --- a/app/interactions/actions/contact_update.rb +++ b/app/interactions/actions/contact_update.rb @@ -18,7 +18,8 @@ module Actions maybe_change_email if new_attributes[:email].present? maybe_filtering_old_failed_records commit - validate_contact + maybe_validate_phone_number + maybe_validate_contact end def maybe_change_email @@ -125,7 +126,7 @@ module Actions updated end - def validate_contact + def maybe_validate_contact return if @error || !contact.valid? [:regex, :mx].each do |m| @@ -134,5 +135,9 @@ module Actions @contact.remove_force_delete_for_valid_contact end + + def maybe_validate_phone_number + OrgRegistrantPhoneCheckerJob.perform_later(type: 'single', registrant_user_code: contact.code) + end end end diff --git a/app/jobs/org_registrant_phone_checker_job.rb b/app/jobs/org_registrant_phone_checker_job.rb new file mode 100644 index 000000000..84456e992 --- /dev/null +++ b/app/jobs/org_registrant_phone_checker_job.rb @@ -0,0 +1,78 @@ +class OrgRegistrantPhoneCheckerJob < ApplicationJob + queue_as :default + + def perform(type: 'bulk', registrant_user_code: nil, spam_delay: 1) + puts '??? PERFROMED ???' + case type + when 'bulk' + execute_bulk_checker(spam_delay) + when 'single' + execute_single_checker(registrant_user_code) + else + raise "Invalid type: #{type}. Allowed types: 'bulk', 'single'" + end + end + + def execute_bulk_checker(spam_delay) + log("Bulk checker started") + + Contact.where(ident_type: 'org', ident_country_code: 'EE').joins(:registrant_domains).each do |registrant_user| + is_phone_number_matching = check_the_registrant_phone_number(registrant_user) + call_disclosure_action(is_phone_number_matching, registrant_user) + sleep(spam_delay) + end + + log("Bulk checker finished") + end + + def execute_single_checker(registrant_user_code) + registrant_user = Contact.where(ident_type: 'org', ident_country_code: 'EE').joins(:registrant_domains).find_by(code: registrant_user_code) + is_phone_number_matching = check_the_registrant_phone_number(registrant_user) + + call_disclosure_action(is_phone_number_matching, registrant_user) + end + + private + + def call_disclosure_action(is_phone_number_matching, contact) + if is_phone_number_matching + disclose_phone_number(contact) + log("Phone number disclosed for registrant user #{contact.code}. Phone number: #{contact.phone}") + elsif contact.disclosed_attributes.include?('phone') + log("Removing phone number from disclosed attributes for registrant user #{contact.code}. Phone number: #{contact.phone}") + contact.disclosed_attributes.delete('phone') + contact.save! + else + log("Phone number not disclosed for registrant user #{contact.code}. Phone number: #{contact.phone}") + end + end + + def log(message) + Rails.logger.info(message) + end + + def disclose_phone_number(contact) + contact.disclosed_attributes << 'phone' + contact.save! + end + + def company_register + @company_register ||= CompanyRegister::Client.new + end + + def check_the_registrant_phone_number(registrant_user) + phone_numbers = fetch_phone_number_from_company_register(registrant_user.ident) + phone_numbers.any? do |phone_number| + format_phone_number(phone_number) == format_phone_number(registrant_user.phone) + end + end + + def format_phone_number(phone_number) + phone_number.gsub(/\D/, '') + end + + def fetch_phone_number_from_company_register(company_code) + data = company_register.company_details(registration_number: company_code.to_s) + data[0].phone_numbers + end +end diff --git a/test/integration/repp/v1/contacts/create_test.rb b/test/integration/repp/v1/contacts/create_test.rb index e47b96265..bcb196f1a 100644 --- a/test/integration/repp/v1/contacts/create_test.rb +++ b/test/integration/repp/v1/contacts/create_test.rb @@ -253,4 +253,32 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest CompanyRegister::Client.define_singleton_method(:new, original_new_method) end + + def test_validates_phone_number_after_create + request_body = { + contact: { + name: 'Test Company', + phone: '+372.51111112', + email: 'test@company.com', + ident: { + ident_type: 'org', + ident_country_code: 'EE', + ident: '12345678', + }, + }, + } + + assert_enqueued_with(job: OrgRegistrantPhoneCheckerJob) do + post '/repp/v1/contacts', headers: @auth_headers, params: request_body + end + + assert_response :ok + json = JSON.parse(response.body, symbolize_names: true) + assert_equal 1000, json[:code] + assert_equal 'Command completed successfully', json[:message] + + contact = Contact.find_by(code: json[:data][:contact][:code]) + assert contact.present? + assert_equal '+372.51111112', contact.phone + end end diff --git a/test/integration/repp/v1/contacts/update_test.rb b/test/integration/repp/v1/contacts/update_test.rb index d51602c32..f078c4bf3 100644 --- a/test/integration/repp/v1/contacts/update_test.rb +++ b/test/integration/repp/v1/contacts/update_test.rb @@ -142,4 +142,31 @@ class ReppV1ContactsUpdateTest < ActionDispatch::IntegrationTest ENV["shunter_default_threshold"] = '10000' ENV["shunter_enabled"] = 'false' end + + def test_validates_phone_number_after_update + @contact.update!( + phone: '+372.555666777', + ident_type: 'org', + ident_country_code: 'EE', + ident: '12345678' + ) + + request_body = { + contact: { + phone: '+372.123456789' + } + } + + assert_enqueued_with(job: OrgRegistrantPhoneCheckerJob) do + put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body + end + + assert_response :ok + json = JSON.parse(response.body, symbolize_names: true) + assert_equal 1000, json[:code] + assert_equal 'Command completed successfully', json[:message] + + @contact.reload + assert_equal '+372.123456789', @contact.phone + end end diff --git a/test/jobs/org_registrant_phone_checker_job_test.rb b/test/jobs/org_registrant_phone_checker_job_test.rb new file mode 100644 index 000000000..b9e2a764d --- /dev/null +++ b/test/jobs/org_registrant_phone_checker_job_test.rb @@ -0,0 +1,109 @@ +require 'test_helper' + +class OrgRegistrantPhoneCheckerJobTest < ActiveSupport::TestCase + include ActionMailer::TestHelper + + setup do + @contact = contacts(:acme_ltd) + @original_phone = '+372.555666777' + @contact.update!( + phone: @original_phone, + ident_type: 'org', + ident_country_code: 'EE', + ident: '12345678' + ) + end + + def test_bulk_checker_processes_all_ee_org_contacts + original_new_method = CompanyRegister::Client.method(:new) + CompanyRegister::Client.define_singleton_method(:new) do + object = original_new_method.call + def object.company_details(registration_number:) + [OpenStruct.new(phone_numbers: ['+372.555666777'])] + end + object + end + + assert_not @contact.disclosed_attributes.include?('phone') + + OrgRegistrantPhoneCheckerJob.perform_now(type: 'bulk') + @contact.reload + + assert @contact.disclosed_attributes.include?('phone') + + CompanyRegister::Client.define_singleton_method(:new, original_new_method) + end + + def test_single_checker_processes_specific_contact + original_new_method = CompanyRegister::Client.method(:new) + CompanyRegister::Client.define_singleton_method(:new) do + object = original_new_method.call + def object.company_details(registration_number:) + [OpenStruct.new(phone_numbers: ['+372.555666777'])] + end + object + end + + assert_not @contact.disclosed_attributes.include?('phone') + + OrgRegistrantPhoneCheckerJob.perform_now( + type: 'single', + registrant_user_code: @contact.code + ) + @contact.reload + + assert @contact.disclosed_attributes.include?('phone') + + CompanyRegister::Client.define_singleton_method(:new, original_new_method) + end + + def test_removes_phone_disclosure_when_numbers_do_not_match + original_new_method = CompanyRegister::Client.method(:new) + CompanyRegister::Client.define_singleton_method(:new) do + object = original_new_method.call + def object.company_details(registration_number:) + [OpenStruct.new(phone_numbers: ['+372.999888777'])] + end + object + end + + @contact.disclosed_attributes = ['phone'] + @contact.save! + assert @contact.disclosed_attributes.include?('phone') + + OrgRegistrantPhoneCheckerJob.perform_now(type: 'bulk') + @contact.reload + + assert_not @contact.disclosed_attributes.include?('phone') + + CompanyRegister::Client.define_singleton_method(:new, original_new_method) + end + + def test_handles_invalid_job_type + assert_raises(RuntimeError) do + OrgRegistrantPhoneCheckerJob.perform_now(type: 'invalid') + end + end + + def test_phone_number_formatting_matches_different_formats + original_new_method = CompanyRegister::Client.method(:new) + CompanyRegister::Client.define_singleton_method(:new) do + object = original_new_method.call + def object.company_details(registration_number:) + [OpenStruct.new(phone_numbers: ['+372 555 666 777'])] + end + object + end + + @contact.phone = '+372.555666777' + @contact.save(validate: false) + assert_not @contact.disclosed_attributes.include?('phone') + + OrgRegistrantPhoneCheckerJob.perform_now(type: 'bulk') + @contact.reload + + assert @contact.disclosed_attributes.include?('phone') + + CompanyRegister::Client.define_singleton_method(:new, original_new_method) + end +end \ No newline at end of file