mirror of
https://github.com/internetee/registry.git
synced 2025-07-20 01:36:02 +02:00
Add a rake task running job & test for job
This commit is contained in:
parent
e110924968
commit
5f0c031410
8 changed files with 92 additions and 105 deletions
|
@ -19,7 +19,7 @@ module Actions
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_email(parsed_email)
|
def check_email(parsed_email)
|
||||||
Truemail.validate(parsed_email, with: check_level).result
|
Truemail.validate(parsed_email, with: check_level.to_sym).result
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_result(result)
|
def save_result(result)
|
||||||
|
|
|
@ -1,47 +1,34 @@
|
||||||
class VerifyEmailsJob < ApplicationJob
|
class VerifyEmailsJob < ApplicationJob
|
||||||
discard_on StandardError
|
discard_on StandardError
|
||||||
|
VALID_CHECK_LEVELS = %w[regex mx smtp].freeze
|
||||||
|
|
||||||
def perform(verification_id)
|
def perform(contact_id:, check_level: 'regex')
|
||||||
email_address_verification = EmailAddressVerification.find(verification_id)
|
contact = Contact.find_by(id: contact_id)
|
||||||
return unless need_to_verify?(email_address_verification)
|
contact_not_found(contact_id) unless contact
|
||||||
|
validate_check_level(check_level)
|
||||||
|
|
||||||
process(email_address_verification)
|
action = Actions::EmailCheck.new(email: contact.email,
|
||||||
|
validation_eventable: contact,
|
||||||
|
check_level: check_level)
|
||||||
|
action.call
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
log_error(verification: email_address_verification, error: e)
|
logger.error e.message
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def need_to_verify?(email_address_verification)
|
def contact_not_found(contact_id)
|
||||||
return false if email_address_verification.blank?
|
raise StandardError, "Contact with contact_id #{contact_id} not found"
|
||||||
return false if email_address_verification.recently_verified?
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def process(email_address_verification)
|
def validate_check_level(check_level)
|
||||||
email_address_verification.verify
|
return if VALID_CHECK_LEVELS.include? check_level
|
||||||
log_success(email_address_verification)
|
|
||||||
|
raise StandardError, "Check level #{check_level} is invalid"
|
||||||
end
|
end
|
||||||
|
|
||||||
def logger
|
def logger
|
||||||
@logger ||= Logger.new(Rails.root.join('log/email_verification.log'))
|
@logger ||= Rails.logger
|
||||||
end
|
|
||||||
|
|
||||||
def log_success(verification)
|
|
||||||
email = verification.try(:email) || verification
|
|
||||||
message = "Email address #{email} verification done"
|
|
||||||
logger.info message
|
|
||||||
end
|
|
||||||
|
|
||||||
def log_error(verification:, error:)
|
|
||||||
email = verification.try(:email) || verification
|
|
||||||
message = <<~TEXT.squish
|
|
||||||
There was an error verifying email #{email}.
|
|
||||||
The error message was the following: #{error}
|
|
||||||
This job will retry.
|
|
||||||
TEXT
|
|
||||||
logger.error message
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
module EmailVerifable
|
module EmailVerifable
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
scope :recently_not_validated, -> { where.not(id: ValidationEvent.validated_ids_by(name)) }
|
||||||
|
end
|
||||||
|
|
||||||
def email_verification
|
def email_verification
|
||||||
EmailAddressVerification.find_or_create_by(email: unicode_email, domain: domain(email))
|
EmailAddressVerification.find_or_create_by(email: unicode_email, domain: domain(email))
|
||||||
end
|
end
|
||||||
|
@ -16,6 +20,7 @@ module EmailVerifable
|
||||||
email_verification&.failed?
|
email_verification&.failed?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: The following methods are deprecated and need to be moved to ValidationEvent class
|
||||||
class_methods do
|
class_methods do
|
||||||
def domain(email)
|
def domain(email)
|
||||||
Mail::Address.new(email).domain&.downcase || 'not_found'
|
Mail::Address.new(email).domain&.downcase || 'not_found'
|
||||||
|
|
|
@ -6,11 +6,20 @@
|
||||||
# For email_validation event kind also check_level (regex/mx/smtp) is stored in the event_data
|
# For email_validation event kind also check_level (regex/mx/smtp) is stored in the event_data
|
||||||
class ValidationEvent < ApplicationRecord
|
class ValidationEvent < ApplicationRecord
|
||||||
enum event_type: ValidationEvent::EventType::TYPES, _suffix: true
|
enum event_type: ValidationEvent::EventType::TYPES, _suffix: true
|
||||||
|
VALIDATION_PERIOD = 1.month.ago.freeze
|
||||||
|
|
||||||
store_accessor :event_data, :errors, :check_level, :email
|
store_accessor :event_data, :errors, :check_level, :email
|
||||||
|
|
||||||
belongs_to :validation_eventable, polymorphic: true
|
belongs_to :validation_eventable, polymorphic: true
|
||||||
|
|
||||||
|
scope :recent, -> { where('created_at > ?', VALIDATION_PERIOD) }
|
||||||
|
scope :successful, -> { where(success: true) }
|
||||||
|
|
||||||
|
def self.validated_ids_by(klass)
|
||||||
|
recent.successful.where('validation_eventable_type = ?', klass)
|
||||||
|
.pluck(:validation_eventable_id)
|
||||||
|
end
|
||||||
|
|
||||||
def event_type
|
def event_type
|
||||||
@event_type ||= ValidationEvent::EventType.new(self[:event_kind])
|
@event_type ||= ValidationEvent::EventType.new(self[:event_kind])
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,8 +17,6 @@ module EmailAddressConverter
|
||||||
"#{local}@#{domain}"&.downcase
|
"#{local}@#{domain}"&.downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def domain(email)
|
def domain(email)
|
||||||
Mail::Address.new(email).domain&.downcase || 'not_found'
|
Mail::Address.new(email).domain&.downcase || 'not_found'
|
||||||
rescue Mail::Field::IncompleteParseError
|
rescue Mail::Field::IncompleteParseError
|
||||||
|
|
|
@ -1,35 +1,16 @@
|
||||||
require 'optparse'
|
require 'optparse'
|
||||||
require 'rake_option_parser_boilerplate'
|
require 'rake_option_parser_boilerplate'
|
||||||
|
require 'syslog/logger'
|
||||||
|
|
||||||
namespace :verify_email do
|
namespace :verify_email do
|
||||||
desc 'Stars verifying email jobs for all the domain'
|
|
||||||
task all_domains: :environment do
|
|
||||||
verifications_by_domain = EmailAddressVerification.not_verified_recently.group_by(&:domain)
|
|
||||||
verifications_by_domain.each do |_domain, verifications|
|
|
||||||
ver = verifications.sample # Verify random email to not to clog the SMTP servers
|
|
||||||
VerifyEmailsJob.perform_later(ver.id)
|
|
||||||
next
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Need to be run like 'bundle exec rake verify_email:domain['gmail.com']'
|
|
||||||
# In zsh syntax will be 'bundle exec rake verify_email:domain\['gmail.com'\]'
|
|
||||||
# Default 'bundle exec rake verify_email:domain' wil use 'internet.ee' domain
|
|
||||||
desc 'Stars verifying email jobs for domain stated in argument'
|
|
||||||
task :domain, [:domain_name] => [:environment] do |_task, args|
|
|
||||||
args.with_defaults(domain_name: 'internet.ee')
|
|
||||||
|
|
||||||
verifications_by_domain = EmailAddressVerification.not_verified_recently
|
|
||||||
.by_domain(args[:domain_name])
|
|
||||||
verifications_by_domain.map { |ver| VerifyEmailsJob.perform_later(ver.id) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# bundle exec rake verify_email:check_all -- -d=shop.test --check_level=mx --spam_protect=true
|
# bundle exec rake verify_email:check_all -- -d=shop.test --check_level=mx --spam_protect=true
|
||||||
# bundle exec rake verify_email:check_all -- -dshop.test -cmx -strue
|
# bundle exec rake verify_email:check_all -- -dshop.test -cmx -strue
|
||||||
desc 'Starts verifying email jobs with optional check level and spam protection'
|
desc 'Starts verifying email jobs with optional check level and spam protection'
|
||||||
task :check_all do
|
task check_all: :environment do
|
||||||
|
SPAM_PROTECT_TIMEOUT = 30.seconds
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
domain_name: 'shop.test',
|
domain_name: nil,
|
||||||
check_level: 'regex',
|
check_level: 'regex',
|
||||||
spam_protect: false,
|
spam_protect: false,
|
||||||
}
|
}
|
||||||
|
@ -37,8 +18,49 @@ namespace :verify_email do
|
||||||
options = RakeOptionParserBoilerplate.process_args(options: options,
|
options = RakeOptionParserBoilerplate.process_args(options: options,
|
||||||
banner: banner,
|
banner: banner,
|
||||||
hash: opts_hash)
|
hash: opts_hash)
|
||||||
|
|
||||||
|
contacts = prepare_contacts(options)
|
||||||
|
logger.info 'No contacts to check email selected' and next if contacts.blank?
|
||||||
|
|
||||||
|
contacts.find_each do |contact|
|
||||||
|
VerifyEmailsJob.set(wait_until: spam_protect_timeout(options)).perform_later(
|
||||||
|
contact_id: contact.id,
|
||||||
|
check_level: check_level(options)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_level(options)
|
||||||
|
options[:check_level]
|
||||||
|
end
|
||||||
|
|
||||||
|
def spam_protect(options)
|
||||||
|
options[:spam_protect]
|
||||||
|
end
|
||||||
|
|
||||||
|
def spam_protect_timeout(options)
|
||||||
|
spam_protect(options) ? 0.seconds : SPAM_PROTECT_TIMEOUT
|
||||||
|
end
|
||||||
|
|
||||||
|
def logger
|
||||||
|
@logger ||= ActiveSupport::TaggedLogging.new(Syslog::Logger.new('registry'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_contacts(options)
|
||||||
|
if options[:domain_name].present?
|
||||||
|
contacts_by_domain(options[:domain_name])
|
||||||
|
else
|
||||||
|
Contact.recently_not_validated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def contacts_by_domain(domain_name)
|
||||||
|
domain = ::Domain.find_by(name: domain_name)
|
||||||
|
return unless domain
|
||||||
|
|
||||||
|
domain.contacts.recently_not_validated
|
||||||
|
end
|
||||||
|
|
||||||
def opts_hash
|
def opts_hash
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,9 +4,6 @@ class VerifyEmailsJobTest < ActiveJob::TestCase
|
||||||
def setup
|
def setup
|
||||||
@contact = contacts(:john)
|
@contact = contacts(:john)
|
||||||
@invalid_contact = contacts(:invalid_email)
|
@invalid_contact = contacts(:invalid_email)
|
||||||
@contact_verification = @contact.email_verification
|
|
||||||
@invalid_contact_verification = @invalid_contact.email_verification
|
|
||||||
|
|
||||||
@default_whitelist = Truemail.configure.whitelisted_domains
|
@default_whitelist = Truemail.configure.whitelisted_domains
|
||||||
@default_blacklist = Truemail.configure.blacklisted_domains
|
@default_blacklist = Truemail.configure.blacklisted_domains
|
||||||
Truemail.configure.whitelisted_domains = whitelisted_domains
|
Truemail.configure.whitelisted_domains = whitelisted_domains
|
||||||
|
@ -33,33 +30,21 @@ class VerifyEmailsJobTest < ActiveJob::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_job_checks_if_email_valid
|
def test_job_checks_if_email_valid
|
||||||
|
assert_difference 'ValidationEvent.successful.count', 1 do
|
||||||
perform_enqueued_jobs do
|
perform_enqueued_jobs do
|
||||||
VerifyEmailsJob.perform_now(@contact_verification.id)
|
VerifyEmailsJob.perform_now(contact_id: @contact.id, check_level: 'regex')
|
||||||
end
|
end
|
||||||
@contact_verification.reload
|
|
||||||
|
|
||||||
assert @contact_verification.success
|
|
||||||
end
|
end
|
||||||
|
assert ValidationEvent.validated_ids_by(Contact).include? @contact.id
|
||||||
def test_job_checks_does_not_run_if_recent
|
|
||||||
old_verified_at = Time.zone.now - 10.days
|
|
||||||
@contact_verification.update(success: true, verified_at: old_verified_at)
|
|
||||||
assert @contact_verification.recently_verified?
|
|
||||||
|
|
||||||
perform_enqueued_jobs do
|
|
||||||
VerifyEmailsJob.perform_now(@contact_verification.id)
|
|
||||||
end
|
|
||||||
@contact_verification.reload
|
|
||||||
|
|
||||||
assert_in_delta @contact_verification.verified_at.to_i, old_verified_at.to_i, 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_job_checks_if_email_invalid
|
def test_job_checks_if_email_invalid
|
||||||
perform_enqueued_jobs do
|
perform_enqueued_jobs do
|
||||||
VerifyEmailsJob.perform_now(@invalid_contact_verification.id)
|
VerifyEmailsJob.perform_now(contact_id: @invalid_contact.id, check_level: 'regex')
|
||||||
end
|
end
|
||||||
@contact_verification.reload
|
@invalid_contact.reload
|
||||||
|
|
||||||
refute @contact_verification.success
|
refute @invalid_contact.validation_events.last.success
|
||||||
|
refute ValidationEvent.validated_ids_by(Contact).include? @invalid_contact.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,6 @@ class VerifyEmailTaskTest < ActiveJob::TestCase
|
||||||
def setup
|
def setup
|
||||||
@contact = contacts(:john)
|
@contact = contacts(:john)
|
||||||
@invalid_contact = contacts(:invalid_email)
|
@invalid_contact = contacts(:invalid_email)
|
||||||
@contact_verification = @contact.email_verification
|
|
||||||
@invalid_contact_verification = @invalid_contact.email_verification
|
|
||||||
|
|
||||||
@default_whitelist = Truemail.configure.whitelisted_domains
|
@default_whitelist = Truemail.configure.whitelisted_domains
|
||||||
@default_blacklist = Truemail.configure.blacklisted_domains
|
@default_blacklist = Truemail.configure.blacklisted_domains
|
||||||
|
@ -36,32 +34,15 @@ class VerifyEmailTaskTest < ActiveJob::TestCase
|
||||||
def test_tasks_verifies_emails
|
def test_tasks_verifies_emails
|
||||||
capture_io { run_task }
|
capture_io { run_task }
|
||||||
|
|
||||||
@contact_verification.reload
|
assert ValidationEvent.validated_ids_by(Contact).include? @contact.id
|
||||||
@invalid_contact_verification.reload
|
assert @contact.validation_events.last.success
|
||||||
|
refute @invalid_contact.validation_events.last.success
|
||||||
assert @contact_verification.verified?
|
refute ValidationEvent.validated_ids_by(Contact).include? @invalid_contact.id
|
||||||
assert @invalid_contact_verification.failed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_domain_task_verifies_for_one_domain
|
|
||||||
capture_io { run_single_domain_task(@contact_verification.domain) }
|
|
||||||
|
|
||||||
@contact_verification.reload
|
|
||||||
@invalid_contact_verification.reload
|
|
||||||
|
|
||||||
assert @contact_verification.verified?
|
|
||||||
assert @invalid_contact_verification.not_verified?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_task
|
def run_task
|
||||||
perform_enqueued_jobs do
|
perform_enqueued_jobs do
|
||||||
Rake::Task['verify_email:all_domains'].execute
|
Rake::Task['verify_email:check_all'].execute
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_single_domain_task(domain)
|
|
||||||
perform_enqueued_jobs do
|
|
||||||
Rake::Task["verify_email:domain"].invoke(domain)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue