diff --git a/.reek.yml b/.reek.yml index 2f82087..e1b6414 100644 --- a/.reek.yml +++ b/.reek.yml @@ -17,6 +17,7 @@ detectors: TooManyInstanceVariables: exclude: - Truemail::Configuration + - Truemail::Validate::Smtp::Request Attribute: exclude: @@ -40,11 +41,13 @@ detectors: - Truemail::Worker#success - Truemail#raise_unless - Truemail::Configuration#raise_unless + - Truemail#determine_configuration FeatureEnvy: exclude: - Truemail::Validate::Smtp#not_includes_user_not_found_errors - Truemail::GenerateEmailHelper#prepare_user_name + - Truemail::ConfigurationHelper#create_configuration NilCheck: exclude: diff --git a/Gemfile.lock b/Gemfile.lock index b094e27..aa9b127 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - truemail (1.2.1) + truemail (1.3.0) GEM remote: https://rubygems.org/ diff --git a/README.md b/README.md index d244e45..d1422a9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Maintainability](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/maintainability)](https://codeclimate.com/github/rubygarage/truemail/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/test_coverage)](https://codeclimate.com/github/rubygarage/truemail/test_coverage) [![CircleCI](https://circleci.com/gh/rubygarage/truemail/tree/master.svg?style=svg)](https://circleci.com/gh/rubygarage/truemail/tree/master) [![Gem Version](https://badge.fury.io/rb/truemail.svg)](https://badge.fury.io/rb/truemail) [![Downloads](https://img.shields.io/gem/dt/truemail.svg?colorA=004d99&colorB=0073e6)](https://rubygems.org/gems/truemail) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) -The Truemail gem helps you validate emails by regex pattern, presence of domain mx-records, and real existence of email account on a current email server. Also Truemail gem allows performing an audit of the host in which runs. +The Truemail gem helps you validate emails via regex pattern, presence of DNS records, and real existence of email account on a current email server. Also Truemail gem allows performing an audit of the host in which runs. ## Features @@ -34,17 +34,19 @@ Email validation is a tricky thing. There are a number of different ways to vali **Syntax Checking**: Checks the email addresses via regex pattern. -**Mail Server Existence Check**: Checks the availability of the email address domain using DNS MX records. +**Mail Server Existence Check**: Checks the availability of the email address domain using DNS records. **Mail Existence Check**: Checks if the email address really exists and can receive email via SMTP connections and email-sending emulation techniques. ## Usage -### Configuration features +### Configuration features -#### Set configuration +You can use global gem configuration or custom independent configuration. -To have an access for ```Truemail.configuration``` and gem features, you must configure it first as in the example below: +#### Setting global configuration + +To have an access for ```Truemail.configuration``` and gem configuration features, you must configure it first as in the example below: ```ruby require 'truemail' @@ -110,7 +112,7 @@ Truemail.configure do |config| end ``` -#### Read configuration +##### Read global configuration After successful configuration, you can read current Truemail configuration instance anywhere in your application. @@ -132,7 +134,7 @@ Truemail.configuration @smtp_safe_check=true> ``` -#### Update configuration +##### Update global configuration ```ruby Truemail.configuration.connection_timeout = 3 @@ -158,7 +160,7 @@ Truemail.configuration @smtp_safe_check=true> ``` -#### Reset configuration +##### Reset global configuration Also you can reset Truemail configuration. @@ -169,6 +171,23 @@ Truemail.configuration => nil ``` +#### Using custom independent configuration + +Allows to use independent configuration for each validation/audition instance. When using this feature you do not need to have Truemail global configuration. + +```ruby +custom_configuration = Truemail::Configuration.new do |config| + config.verifier_email = 'verifier@example.com' +end + +Truemail.validate('email@example.com', custom_configuration: custom_configuration) +Truemail.valid?('email@example.com', custom_configuration: custom_configuration) +Truemail.host_audit('email@example.com', custom_configuration: custom_configuration) +``` + +Please note, you should have global or custom configuration for use Truemail gem. + + ### Validation features #### Whitelist/Blacklist check @@ -210,6 +229,20 @@ Truemail.validate('email@white-domain.com') mail_servers=[], errors={}, smtp_debug=nil>, + configuration=#:mx}, + @verifier_domain="example.com", + @verifier_email="verifier@example.com", + @whitelist_validation=false, + @whitelisted_domains=["white-domain.com", "somedomain.com"]>, @validation_type=:whitelist> ``` @@ -241,6 +274,21 @@ Truemail.validate('email@white-domain.com', with: :regex) mail_servers=[], errors={}, smtp_debug=nil>, + configuration= + #, @validation_type=:regex> ``` @@ -257,6 +305,21 @@ Truemail.validate('email@domain.com', with: :regex) mail_servers=[], errors={}, smtp_debug=nil>, + configuration= + #, @validation_type=:blacklist> ``` @@ -275,6 +338,21 @@ Truemail.validate('email@black-domain.com') mail_servers=[], errors={}, smtp_debug=nil>, + configuration= + #, @validation_type=:blacklist> ``` @@ -293,6 +371,21 @@ Truemail.validate('email@somedomain.com') mail_servers=[], errors={}, smtp_debug=nil>, + configuration= + #, @validation_type=:whitelist> ``` @@ -327,6 +420,21 @@ Truemail.validate('email@example.com', with: :regex) mail_servers=[], errors={}, smtp_debug=nil>, + configuration= + #, @validation_type=:regex> ``` @@ -351,6 +459,21 @@ Truemail.validate('email@example.com', with: :regex) mail_servers=[], errors={}, smtp_debug=nil>, + configuration= + #, @validation_type=:regex> ``` @@ -384,6 +507,21 @@ Truemail.validate('email@example.com', with: :mx) mail_servers=["127.0.1.1", "127.0.1.2"], errors={}, smtp_debug=nil>, + configuration= + #, @validation_type=:mx> ``` @@ -420,6 +558,21 @@ Truemail.validate('email@example.com') mail_servers=["127.0.1.1", "127.0.1.2"], errors={}, smtp_debug=nil>, + configuration= + #, @validation_type=:smtp> # SMTP validation failed @@ -434,17 +587,9 @@ Truemail.validate('email@example.com') smtp_debug= [#, @email="email@example.com", @@ -464,6 +609,21 @@ Truemail.validate('email@example.com') @string="250 OK\n">, rcptto=false, errors={:rcptto=>"550 User not found\n"}>>]>, + configuration= + #, @validation_type=:smtp> ``` @@ -491,33 +651,40 @@ Truemail.validate('email@example.com') smtp_debug= [#, - @email="email@example.com", - @host="127.0.1.1", - @attempts=nil, - @response= - #, - mailfrom=false, - rcptto=nil, - errors={:mailfrom=>"554 5.7.1 Client host blocked\n", :connection=>"server dropped connection after response"}>>,]>, + #, + @email="email@example.com", + @host="127.0.1.1", + @attempts=nil, + @response= + #, + mailfrom=false, + rcptto=nil, + errors={:mailfrom=>"554 5.7.1 Client host blocked\n", :connection=>"server dropped connection after response"}>>,]>, + configuration= + #, @validation_type=:smtp> # SMTP validation failed @@ -532,17 +699,9 @@ Truemail.validate('email@example.com') smtp_debug= [#, @email="email@example.com", @@ -559,6 +718,21 @@ Truemail.validate('email@example.com') mailfrom=#, rcptto=false, errors={:rcptto=>"550 User not found\n"}>>]>, + configuration= + #, @validation_type=:smtp> ``` @@ -576,14 +750,44 @@ Truemail.host_audit => #> + warnings={}>, + configuration= + # # Has PTR warning => #"ptr record does not reference to current verifier domain"}>> + {:ptr=>"ptr record does not reference to current verifier domain"}>, + configuration= + # ``` ### Truemail helpers diff --git a/lib/truemail.rb b/lib/truemail.rb index 1f95699..518b276 100644 --- a/lib/truemail.rb +++ b/lib/truemail.rb @@ -4,7 +4,7 @@ module Truemail INCOMPLETE_CONFIG = 'verifier_email is required parameter' - NOT_CONFIGURED = 'use Truemail.configure before' + NOT_CONFIGURED = 'use Truemail.configure before or pass custom configuration' class << self def configuration @@ -12,7 +12,7 @@ def configuration return unless block_given? configuration = Truemail::Configuration.new yield(configuration) - raise_unless(configuration.complete?, INCOMPLETE_CONFIG) + raise_unless(configuration.complete?, Truemail::INCOMPLETE_CONFIG) configuration end end @@ -25,24 +25,29 @@ def reset_configuration! @configuration = nil end - def validate(email, **options) - raise_unless(configuration, NOT_CONFIGURED) - Truemail::Validator.new(email, **options).run + def validate(email, custom_configuration: nil, **options) + Truemail::Validator.new(email, configuration: determine_configuration(custom_configuration), **options).run end def valid?(email, **options) validate(email, **options).result.valid? end - def host_audit - raise_unless(configuration, NOT_CONFIGURED) - Truemail::Auditor.run + def host_audit(custom_configuration: nil) + Truemail::Auditor.new(configuration: determine_configuration(custom_configuration)).run end private def raise_unless(condition, message) - raise ConfigurationError, message unless condition + raise Truemail::ConfigurationError, message unless condition + end + + def determine_configuration(custom_configuration) + current_configuration = custom_configuration || configuration + raise_unless(current_configuration, Truemail::NOT_CONFIGURED) + raise_unless(current_configuration.complete?, Truemail::INCOMPLETE_CONFIG) + current_configuration.dup.freeze end end end diff --git a/lib/truemail/audit/base.rb b/lib/truemail/audit/base.rb index 023c7be..1845792 100644 --- a/lib/truemail/audit/base.rb +++ b/lib/truemail/audit/base.rb @@ -9,8 +9,12 @@ def add_warning(message) result.warnings[self.class.name.split('::').last.downcase.to_sym] = message end + def configuration + result.configuration + end + def verifier_domain - Truemail.configuration.verifier_domain + configuration.verifier_domain end end end diff --git a/lib/truemail/audit/ptr.rb b/lib/truemail/audit/ptr.rb index f7fef18..1a46dc0 100644 --- a/lib/truemail/audit/ptr.rb +++ b/lib/truemail/audit/ptr.rb @@ -27,7 +27,9 @@ def detect_ip_via_ipify end def current_host_address - @current_host_address ||= Truemail::Wrapper.call { IPAddr.new(detect_ip_via_ipify) } + @current_host_address ||= Truemail::Wrapper.call(configuration: configuration) do + IPAddr.new(detect_ip_via_ipify) + end end def current_host_reverse_lookup @@ -35,7 +37,7 @@ def current_host_reverse_lookup end def ptr_records - @ptr_records ||= Truemail::Wrapper.call do + @ptr_records ||= Truemail::Wrapper.call(configuration: configuration) do Resolv::DNS.new.getresources( current_host_reverse_lookup, Resolv::DNS::Resource::IN::PTR ).map { |ptr_record| ptr_record.name.to_s } @@ -47,11 +49,13 @@ def ptr_not_refer_to_verifier_domain? end def a_record - Truemail::Wrapper.call { Resolv::DNS.new.getaddress(verifier_domain).to_s } + Truemail::Wrapper.call(configuration: configuration) do + Resolv::DNS.new.getaddress(verifier_domain).to_s + end end def verifier_domain_refer_to_current_host_address? - a_record == current_host_address.to_s + a_record.eql?(current_host_address.to_s) end end end diff --git a/lib/truemail/auditor.rb b/lib/truemail/auditor.rb index 6f9679d..12f6691 100644 --- a/lib/truemail/auditor.rb +++ b/lib/truemail/auditor.rb @@ -2,18 +2,16 @@ module Truemail class Auditor - Result = Struct.new(:warnings, keyword_init: true) do + Result = Struct.new(:warnings, :configuration, keyword_init: true) do def initialize(warnings: {}, **args) super end end - def self.run - new.run - end + attr_reader :result - def result - @result ||= Truemail::Auditor::Result.new + def initialize(configuration:) + @result = Truemail::Auditor::Result.new(configuration: configuration) end def run diff --git a/lib/truemail/configuration.rb b/lib/truemail/configuration.rb index 0e3a839..88c6883 100644 --- a/lib/truemail/configuration.rb +++ b/lib/truemail/configuration.rb @@ -23,10 +23,11 @@ class Configuration alias retry_count connection_attempts - def initialize + def initialize(&block) instance_initializer.each do |instace_variable, value| instance_variable_set(:"@#{instace_variable}", value) end + tap(&block) if block_given? end %i[email_pattern smtp_error_body_pattern].each do |method| diff --git a/lib/truemail/validate/base.rb b/lib/truemail/validate/base.rb index 2ed7545..74c0d7a 100644 --- a/lib/truemail/validate/base.rb +++ b/lib/truemail/validate/base.rb @@ -14,7 +14,7 @@ def mail_servers end def configuration - Truemail.configuration + result.configuration end end end diff --git a/lib/truemail/validate/mx.rb b/lib/truemail/validate/mx.rb index 77d2d78..cd53872 100644 --- a/lib/truemail/validate/mx.rb +++ b/lib/truemail/validate/mx.rb @@ -24,7 +24,7 @@ def host_extractor_methods def mx_lookup host_extractor_methods.any? do |method| - Truemail::Wrapper.call { send(method) } + Truemail::Wrapper.call(configuration: configuration) { send(method) } end end diff --git a/lib/truemail/validate/smtp.rb b/lib/truemail/validate/smtp.rb index c39430c..46d870e 100644 --- a/lib/truemail/validate/smtp.rb +++ b/lib/truemail/validate/smtp.rb @@ -40,7 +40,7 @@ def rcptto_error def establish_smtp_connection mail_servers.each do |mail_server| smtp_results << Truemail::Validate::Smtp::Request.new( - host: mail_server, email: result.email, **attempts + configuration: configuration, host: mail_server, email: result.email, **attempts ) next unless request.check_port request.run || rcptto_error ? break : next diff --git a/lib/truemail/validate/smtp/request.rb b/lib/truemail/validate/smtp/request.rb index 54c13e6..e49f759 100644 --- a/lib/truemail/validate/smtp/request.rb +++ b/lib/truemail/validate/smtp/request.rb @@ -11,12 +11,13 @@ class Request RESPONSE_TIMEOUT_ERROR = 'server response timeout' CONNECTION_DROPPED = 'server dropped connection after response' - attr_reader :host, :email, :response + attr_reader :configuration, :host, :email, :response - def initialize(host:, email:, attempts: nil) + def initialize(configuration:, host:, email:, attempts: nil) + @configuration = Truemail::Validate::Smtp::Request::Configuration.new(configuration) + @response = Truemail::Validate::Smtp::Response.new @host = host @email = email - @response = Truemail::Validate::Smtp::Response.new @attempts = attempts end @@ -41,6 +42,17 @@ def run private + class Configuration + REQUEST_PARAMS = %i[connection_timeout response_timeout verifier_domain verifier_email].freeze + + def initialize(configuration) + REQUEST_PARAMS.each do |attribute| + self.class.class_eval { attr_reader attribute } + instance_variable_set(:"@#{attribute}", configuration.public_send(attribute)) + end + end + end + attr_reader :attempts def attempts_exist? @@ -48,10 +60,6 @@ def attempts_exist? (@attempts -= 1).positive? end - def configuration - @configuration ||= Truemail.configuration.dup.freeze - end - def session Net::SMTP.new(host, Truemail::Validate::Smtp::Request::SMTP_PORT).tap do |settings| settings.open_timeout = configuration.connection_timeout diff --git a/lib/truemail/validator.rb b/lib/truemail/validator.rb index 8361a28..957d16b 100644 --- a/lib/truemail/validator.rb +++ b/lib/truemail/validator.rb @@ -2,7 +2,7 @@ module Truemail class Validator - RESULT_ATTRS = %i[success email domain mail_servers errors smtp_debug].freeze + RESULT_ATTRS = %i[success email domain mail_servers errors smtp_debug configuration].freeze VALIDATION_TYPES = %i[regex mx smtp].freeze Result = Struct.new(*RESULT_ATTRS, keyword_init: true) do @@ -14,10 +14,11 @@ def initialize(errors: {}, mail_servers: [], **args) attr_reader :validation_type, :result - def initialize(email, with: Truemail.configuration.default_validation_type) + def initialize(email, with: nil, configuration:) + with ||= configuration.default_validation_type raise Truemail::ArgumentError.new(with, :argument) unless Truemail::Validator::VALIDATION_TYPES.include?(with) + @result = Truemail::Validator::Result.new(email: email, configuration: configuration) @validation_type = select_validation_type(email, with) - @result = Truemail::Validator::Result.new(email: email) end def run @@ -38,7 +39,7 @@ def update_validation_type def select_validation_type(email, current_validation_type) domain = email[Truemail::RegexConstant::REGEX_EMAIL_PATTERN, 3] - Truemail.configuration.validation_type_by_domain[domain] || current_validation_type + result.configuration.validation_type_by_domain[domain] || current_validation_type end end end diff --git a/lib/truemail/version.rb b/lib/truemail/version.rb index cf1385f..5c1fea7 100644 --- a/lib/truemail/version.rb +++ b/lib/truemail/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Truemail - VERSION = '1.2.1' + VERSION = '1.3.0' end diff --git a/lib/truemail/wrapper.rb b/lib/truemail/wrapper.rb index 72f164a..56ea376 100644 --- a/lib/truemail/wrapper.rb +++ b/lib/truemail/wrapper.rb @@ -2,18 +2,20 @@ module Truemail class Wrapper + attr_reader :timeout attr_accessor :attempts - def self.call(&block) - new.call(&block) + def self.call(configuration:, &block) + new(configuration).call(&block) end - def initialize - @attempts = Truemail.configuration.connection_attempts + def initialize(configuration) + @attempts = configuration.connection_attempts + @timeout = configuration.connection_timeout end def call(&block) - Timeout.timeout(Truemail.configuration.connection_timeout, &block) + Timeout.timeout(timeout, &block) rescue Resolv::ResolvError, IPAddr::InvalidAddressError false rescue Timeout::Error diff --git a/spec/support/helpers/configuration_helper.rb b/spec/support/helpers/configuration_helper.rb index c3d7550..b129551 100644 --- a/spec/support/helpers/configuration_helper.rb +++ b/spec/support/helpers/configuration_helper.rb @@ -9,5 +9,10 @@ def configuration_block(**configuration_settings) end end end + + def create_configuration(**configuration_settings) + configuration_settings[:verifier_email] = FFaker::Internet.email unless configuration_settings[:verifier_email] + Truemail::Configuration.new(&configuration_block(configuration_settings)) + end end end diff --git a/spec/support/helpers/configuration_helper_spec.rb b/spec/support/helpers/configuration_helper_spec.rb index c5428cc..7e3de59 100644 --- a/spec/support/helpers/configuration_helper_spec.rb +++ b/spec/support/helpers/configuration_helper_spec.rb @@ -2,7 +2,7 @@ module Truemail RSpec.describe ConfigurationHelper, type: :helper do - describe '.configuration_block' do + describe '#configuration_block' do let(:configuration_params) { { param_1: 1, param_2: 2 } } let(:configuration_instance) { Struct.new(*configuration_params.keys).new } @@ -16,5 +16,28 @@ module Truemail end end end + + describe '#create_configuration' do + subject(:configuration_builder) { create_configuration(params) } + + let(:params) { {} } + + context 'with default params' do + it 'returns configuration instance with random verifier email' do + expect(configuration_builder).to be_an_instance_of(Truemail::Configuration) + expect(configuration_builder.verifier_email).not_to be_nil + end + end + + context 'with custom params' do + let(:verifier_email) { FFaker::Internet.email } + let(:params) { { verifier_email: verifier_email } } + + it 'returns configuration instance with custom verifier email' do + expect(configuration_builder).to be_an_instance_of(Truemail::Configuration) + expect(configuration_builder.verifier_email).to eq(verifier_email) + end + end + end end end diff --git a/spec/truemail/audit/ptr_spec.rb b/spec/truemail/audit/ptr_spec.rb index e3dd747..75b21e5 100644 --- a/spec/truemail/audit/ptr_spec.rb +++ b/spec/truemail/audit/ptr_spec.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true RSpec.describe Truemail::Audit::Ptr do - let(:email) { FFaker::Internet.email } - let(:result_instance) { Truemail::Auditor::Result.new } - - before { Truemail.configure { |config| config.verifier_email = email } } + let(:configuration_instance) { create_configuration } + let(:result_instance) { Truemail::Auditor::Result.new(configuration: configuration_instance) } describe 'defined constants' do specify { expect(described_class).to be_const_defined(:GET_MY_IP_URL) } @@ -30,7 +28,7 @@ subject(:ptr_auditor) { ptr_auditor_instance.run } let(:ptr_auditor_instance) { described_class.new(result_instance) } - let(:host_name) { Truemail.configuration.verifier_domain } + let(:host_name) { configuration_instance.verifier_domain } let(:host_address) { FFaker::Internet.ip_v4_address } let(:other_host_address) { '127.0.0.1' } diff --git a/spec/truemail/auditor_spec.rb b/spec/truemail/auditor_spec.rb index 7703e04..f9efdf2 100644 --- a/spec/truemail/auditor_spec.rb +++ b/spec/truemail/auditor_spec.rb @@ -2,23 +2,24 @@ module Truemail RSpec.describe Truemail::Auditor do - subject(:auditor_instance) { described_class.run } + subject(:auditor_instance) { described_class.new(configuration: configuration_instance) } - let(:email) { FFaker::Internet.email } - let(:auditor_instance_result) { auditor_instance.result } - - before { Truemail.configure { |config| config.verifier_email = email } } + let(:configuration_instance) { create_configuration } describe 'defined constants' do specify { expect(described_class).to be_const_defined(:Result) } end - describe '.run' do - it 'creates and updates default auditor result' do - allow(Truemail::Audit::Ptr).to receive(:check) - expect(auditor_instance).to be_an_instance_of(Truemail::Auditor) - expect(auditor_instance_result).to be_an_instance_of(Truemail::Auditor::Result) - expect(auditor_instance_result.warnings).to eq({}) + describe '.new' do + it 'creates auditor with result getter' do + expect(auditor_instance.result).to be_an_instance_of(Truemail::Auditor::Result) + end + end + + describe '#run' do + it 'runs audition methods' do + expect(Truemail::Audit::Ptr).to receive(:check) + expect(auditor_instance.run).to be_an_instance_of(described_class) end end end @@ -26,18 +27,16 @@ module Truemail RSpec.describe Truemail::Auditor::Result do subject(:result_instance) { described_class.new } - let(:hash_object) { {} } - - specify do - expect(result_instance.members).to include(:warnings) + it 'has attribute accessors warnings, configuration' do + expect(result_instance.members).to include(:warnings, :configuration) end it 'has default values for attributes' do - expect(result_instance.warnings).to eq(hash_object) + expect(result_instance.warnings).to eq({}) end it 'accepts parametrized arguments' do - expect(described_class.new(warnings: hash_object).warnings).to eq(hash_object) + expect(described_class.new(warnings: :data).warnings).to eq(:data) end end end diff --git a/spec/truemail/configuration_spec.rb b/spec/truemail/configuration_spec.rb index 6a47cc1..001a02f 100644 --- a/spec/truemail/configuration_spec.rb +++ b/spec/truemail/configuration_spec.rb @@ -3,6 +3,8 @@ RSpec.describe Truemail::Configuration do subject(:configuration_instance) { described_class.new } + let(:valid_email) { FFaker::Internet.email } + describe 'defined constants' do specify { expect(described_class).to be_const_defined(:DEFAULT_CONNECTION_TIMEOUT) } specify { expect(described_class).to be_const_defined(:DEFAULT_RESPONSE_TIMEOUT) } @@ -21,11 +23,16 @@ expect(configuration_instance.respond_to?(:validation_type_for=)).to be(true) end + it 'accepts block' do + expect( + described_class.new(&configuration_block(verifier_email: valid_email) + ).verifier_email).to eq(valid_email) + end + include_examples 'sets default configuration' end describe 'configuration cases' do - let(:valid_email) { FFaker::Internet.email } let(:default_verifier_domain) { valid_email[/\A(.+)@(.+)\z/, 2] } context 'when auto configuration' do diff --git a/spec/truemail/validate/domain_list_match_spec.rb b/spec/truemail/validate/domain_list_match_spec.rb index b029112..f439e81 100644 --- a/spec/truemail/validate/domain_list_match_spec.rb +++ b/spec/truemail/validate/domain_list_match_spec.rb @@ -6,12 +6,11 @@ let(:email) { FFaker::Internet.email } let(:domain) { email[Truemail::RegexConstant::REGEX_DOMAIN_FROM_EMAIL, 1] } - let(:result_instance) { Truemail::Validator::Result.new(email: email) } + let(:configuration_instance) { create_configuration } + let(:result_instance) { Truemail::Validator::Result.new(email: email, configuration: configuration_instance) } before do - allow(Truemail) - .to receive_message_chain(:configuration, :whitelist_validation) - .and_return(whitelist_validation_condition) + allow(configuration_instance).to receive(:whitelist_validation).and_return(whitelist_validation_condition) end context 'when whitelist validation not configured' do @@ -19,16 +18,16 @@ context 'when email domain in white list' do specify do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([domain]) - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + allow(configuration_instance).to receive(:whitelisted_domains).and_return([domain]) + allow(configuration_instance).to receive(:blacklisted_domains).and_return([]) expect { list_match_validator }.to change(result_instance, :success).from(nil).to(true) end end context 'when email domain in black list' do specify do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([]) - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([domain]) + allow(configuration_instance).to receive(:whitelisted_domains).and_return([]) + allow(configuration_instance).to receive(:blacklisted_domains).and_return([domain]) expect { list_match_validator } .to change(result_instance, :success).from(nil).to(false) .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) @@ -37,16 +36,16 @@ context 'when email domain exists on both lists' do specify do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([domain]) - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([domain]) + allow(configuration_instance).to receive(:whitelisted_domains).and_return([domain]) + allow(configuration_instance).to receive(:blacklisted_domains).and_return([domain]) expect { list_match_validator }.to change(result_instance, :success).from(nil).to(true) end end context 'when email domain exists not on both lists' do specify do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([]) - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + allow(configuration_instance).to receive(:whitelisted_domains).and_return([]) + allow(configuration_instance).to receive(:blacklisted_domains).and_return([]) expect { list_match_validator }.not_to change(result_instance, :success) end end @@ -56,20 +55,18 @@ let(:whitelist_validation_condition) { true } context 'when email domain whitelisted in configuration' do - before do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([domain]) - end + before { allow(configuration_instance).to receive(:whitelisted_domains).and_return([domain]) } context 'when email domain in white list' do specify do - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + allow(configuration_instance).to receive(:blacklisted_domains).and_return([]) expect { list_match_validator }.not_to change(result_instance, :success) end end context 'when email domain exists on both lists' do specify do - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([domain]) + allow(configuration_instance).to receive(:blacklisted_domains).and_return([domain]) expect { list_match_validator } .to change(result_instance, :success).from(nil).to(false) .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) @@ -79,12 +76,12 @@ context 'when email domain not whitelisted in configuration' do before do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([]) + allow(configuration_instance).to receive(:whitelisted_domains).and_return([]) end context 'when email domain in black list' do specify do - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + allow(configuration_instance).to receive(:blacklisted_domains).and_return([]) expect { list_match_validator } .to change(result_instance, :success).from(nil).to(false) .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) @@ -93,7 +90,7 @@ context 'when email domain not exists on both lists' do specify do - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + allow(configuration_instance).to receive(:blacklisted_domains).and_return([]) expect { list_match_validator } .to change(result_instance, :success).from(nil).to(false) .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) diff --git a/spec/truemail/validate/mx_spec.rb b/spec/truemail/validate/mx_spec.rb index 1403d8c..57d8bf9 100644 --- a/spec/truemail/validate/mx_spec.rb +++ b/spec/truemail/validate/mx_spec.rb @@ -2,9 +2,7 @@ RSpec.describe Truemail::Validate::Mx do let(:email) { FFaker::Internet.email } - let(:result_instance) { Truemail::Validator::Result.new(email: email) } - - before { Truemail.configure { |config| config.verifier_email = email } } + let(:result_instance) { Truemail::Validator::Result.new(email: email, configuration: create_configuration) } describe 'defined constants' do specify { expect(described_class).to be_const_defined(:ERROR) } diff --git a/spec/truemail/validate/regex_spec.rb b/spec/truemail/validate/regex_spec.rb index c0ca3ff..60e3160 100644 --- a/spec/truemail/validate/regex_spec.rb +++ b/spec/truemail/validate/regex_spec.rb @@ -8,11 +8,14 @@ describe '.check' do subject(:regex_validator) { described_class.check(result_instance) } - let(:result_instance) { Truemail::Validator::Result.new(email: FFaker::Internet.email) } + let(:configuration_instance) { create_configuration } + let(:result_instance) do + Truemail::Validator::Result.new(email: FFaker::Internet.email, configuration: configuration_instance) + end context 'when validation pass' do before do - allow(Truemail).to receive_message_chain(:configuration, :email_pattern, :match?).and_return(true) + allow(configuration_instance).to receive_message_chain(:email_pattern, :match?).and_return(true) end specify do @@ -26,7 +29,7 @@ context 'when validation fails' do before do - allow(Truemail).to receive_message_chain(:configuration, :email_pattern, :match?).and_return(false) + allow(configuration_instance).to receive_message_chain(:email_pattern, :match?).and_return(false) end specify do diff --git a/spec/truemail/validate/smtp/request_spec.rb b/spec/truemail/validate/smtp/request_spec.rb index 96feaa4..5a26bb9 100644 --- a/spec/truemail/validate/smtp/request_spec.rb +++ b/spec/truemail/validate/smtp/request_spec.rb @@ -1,259 +1,288 @@ # frozen_string_literal: true -RSpec.describe Truemail::Validate::Smtp::Request do - subject(:request_instance) do - described_class.new(host: mail_server, email: target_email, **attempts) - end - - let(:mail_server) { FFaker::Internet.domain_name } - let(:target_email) { FFaker::Internet.email } - let(:response_instance) { request_instance.response } - let(:request_instance_host) { request_instance.host } - let(:configuration) { request_instance.send(:configuration) } - let(:connection_timout) { configuration.connection_timeout } - let(:response_timeout) { configuration.response_timeout } - let(:attempts) { {} } - - before { Truemail.configure { |config| config.verifier_email = FFaker::Internet.email } } - - describe 'defined constants' do - specify { expect(described_class).to be_const_defined(:SMTP_PORT) } - specify { expect(described_class).to be_const_defined(:CONNECTION_TIMEOUT_ERROR) } - specify { expect(described_class).to be_const_defined(:RESPONSE_TIMEOUT_ERROR) } - specify { expect(described_class).to be_const_defined(:CONNECTION_DROPPED) } - end - - describe 'attribute readers' do - specify { expect(request_instance.public_methods).to include(:host, :email, :response) } - end - - describe '.new' do - specify { expect(request_instance.host).to eq(mail_server) } - specify { expect(request_instance.email).to eq(target_email) } - specify { expect(response_instance).to be_an_instance_of(Truemail::Validate::Smtp::Response) } - end - - describe '#check_port' do - let(:connection_timeout) { configuration.connection_timeout } - let(:response_instance_target_method) { request_instance.check_port } - - context 'when port opened' do - specify do - allow(Timeout).to receive(:timeout).with(connection_timeout).and_call_original - allow(TCPSocket).to receive_message_chain(:new, :close) - expect { request_instance.check_port } - .to change(response_instance, :port_opened).from(nil).to(true) - end +module Truemail + RSpec.describe Truemail::Validate::Smtp::Request do + subject(:request_instance) do + described_class.new( + configuration: configuration_instance, + host: mail_server, + email: target_email, + **attempts + ) end - context 'when port closed' do - let(:error_stubs) do - allow(Timeout).to receive(:timeout).with(connection_timeout).and_raise(Timeout::Error) - end - - specify do - error_stubs - expect { response_instance_target_method }.to change(response_instance, :port_opened).from(nil).to(false) - end + let(:mail_server) { FFaker::Internet.domain_name } + let(:target_email) { FFaker::Internet.email } + let(:response_instance) { request_instance.response } + let(:request_instance_host) { request_instance.host } + let(:configuration_instance) { create_configuration } + let(:connection_timeout) { configuration_instance.connection_timeout } + let(:response_timeout) { configuration_instance.response_timeout } + let(:attempts) { {} } + + describe 'defined constants' do + specify { expect(described_class).to be_const_defined(:SMTP_PORT) } + specify { expect(described_class).to be_const_defined(:CONNECTION_TIMEOUT_ERROR) } + specify { expect(described_class).to be_const_defined(:RESPONSE_TIMEOUT_ERROR) } + specify { expect(described_class).to be_const_defined(:CONNECTION_DROPPED) } + end - specify do - allow(TCPSocket).to receive(:new).and_raise(SocketError) - expect { response_instance_target_method }.to change(response_instance, :port_opened).from(nil).to(false) - end + describe 'attribute readers' do + specify { expect(request_instance.public_methods).to include(:configuration, :host, :email, :response) } + end - include_examples 'request retry behavior' + describe '.new' do + specify { expect(request_instance.configuration).to be_an_instance_of(Truemail::Validate::Smtp::Request::Configuration) } + specify { expect(request_instance.host).to eq(mail_server) } + specify { expect(request_instance.email).to eq(target_email) } + specify { expect(request_instance.response).to be_an_instance_of(Truemail::Validate::Smtp::Response) } end - end - describe '#session' do - context 'when session creates' do - let(:session) { request_instance.send(:session) } + describe '#check_port' do + let(:response_instance_target_method) { request_instance.check_port } - before do - allow(Net::SMTP) - .to receive(:new) - .with(request_instance_host, Truemail::Validate::Smtp::Request::SMTP_PORT) - .and_call_original + context 'when port opened' do + specify do + allow(Timeout).to receive(:timeout).with(connection_timeout).and_call_original + allow(TCPSocket).to receive_message_chain(:new, :close) + expect { request_instance.check_port } + .to change(response_instance, :port_opened).from(nil).to(true) + end end - it 'sets connection timeout with value from global configuration' do - expect(session.open_timeout).to eq(connection_timout) - end + context 'when port closed' do + let(:error_stubs) do + allow(Timeout).to receive(:timeout).with(connection_timeout).and_raise(Timeout::Error) + end - it 'sets response timeout with value from global configuration' do - expect(session.read_timeout).to eq(response_timeout) - end - end - end + specify do + error_stubs + expect { response_instance_target_method }.to change(response_instance, :port_opened).from(nil).to(false) + end - describe '#run' do - let(:response_instance_target_method) { request_instance.run } + specify do + allow(TCPSocket).to receive(:new).and_raise(SocketError) + expect { response_instance_target_method }.to change(response_instance, :port_opened).from(nil).to(false) + end - before do - allow(session).to receive(:open_timeout=).with(connection_timout) - allow(session).to receive(:read_timeout=).with(response_timeout) - allow(Net::SMTP) - .to receive(:new) - .with(request_instance_host, Truemail::Validate::Smtp::Request::SMTP_PORT) - .and_return(session) + include_examples 'request retry behavior' + end end - context 'when smtp communication complete successfully' do - let(:session) do - instance_double( - 'Net::SMTP', - open_timeout: connection_timout, - read_timeout: response_timeout, - helo: true, - mailfrom: true, - rcptto: true - ) - end + describe '#session' do + context 'when session creates' do + let(:session) { request_instance.send(:session) } - specify do - allow(session).to receive(:start).and_yield(session) + before do + allow(Net::SMTP) + .to receive(:new) + .with(request_instance_host, Truemail::Validate::Smtp::Request::SMTP_PORT) + .and_call_original + end - expect { response_instance_target_method } - .to change(response_instance, :connection).from(nil).to(true) - .and change(response_instance, :helo).from(nil).to(true) - .and change(response_instance, :mailfrom).from(nil).to(true) - .and change(response_instance, :rcptto).from(nil).to(true) - .and not_change(response_instance, :errors) + it 'sets connection timeout with value from global configuration' do + expect(session.open_timeout).to eq(connection_timeout) + end - expect(response_instance_target_method).to be(true) + it 'sets response timeout with value from global configuration' do + expect(session.read_timeout).to eq(response_timeout) + end end end - context 'when smtp communication fails' do - let(:error_message) { 'error message' } - let(:session) do - instance_double( - 'Net::SMTP', - open_timeout: connection_timout, - read_timeout: response_timeout - ) + describe '#run' do + let(:response_instance_target_method) { request_instance.run } + + before do + allow(session).to receive(:open_timeout=).with(connection_timeout) + allow(session).to receive(:read_timeout=).with(response_timeout) + allow(Net::SMTP) + .to receive(:new) + .with(request_instance_host, Truemail::Validate::Smtp::Request::SMTP_PORT) + .and_return(session) end - context 'when connection timeout error' do - let(:error_stubs) do - allow(session).to receive(:start).and_raise(Net::OpenTimeout) + context 'when smtp communication complete successfully' do + let(:session) do + instance_double( + 'Net::SMTP', + open_timeout: connection_timeout, + read_timeout: response_timeout, + helo: true, + mailfrom: true, + rcptto: true + ) end specify do - error_stubs + allow(session).to receive(:start).and_yield(session) expect { response_instance_target_method } - .to change(response_instance, :connection).from(nil).to(false) - .and change(response_instance, :errors) - .from({}).to({ connection: Truemail::Validate::Smtp::Request::CONNECTION_TIMEOUT_ERROR }) - .and not_change(response_instance, :helo) - .and not_change(response_instance, :mailfrom) - .and not_change(response_instance, :rcptto) - - expect(response_instance_target_method).to be(false) - end + .to change(response_instance, :connection).from(nil).to(true) + .and change(response_instance, :helo).from(nil).to(true) + .and change(response_instance, :mailfrom).from(nil).to(true) + .and change(response_instance, :rcptto).from(nil).to(true) + .and not_change(response_instance, :errors) - include_examples 'request retry behavior' + expect(response_instance_target_method).to be(true) + end end - context 'when remote server has dropped connection during session' do - let(:error_stubs) do - allow(session).to receive(:start).and_yield(session).and_raise(EOFError) - allow(session).to receive(:helo).and_raise(StandardError) + context 'when smtp communication fails' do + let(:error_message) { 'error message' } + let(:session) do + instance_double( + 'Net::SMTP', + open_timeout: connection_timeout, + read_timeout: response_timeout + ) end - specify do - error_stubs + context 'when connection timeout error' do + let(:error_stubs) do + allow(session).to receive(:start).and_raise(Net::OpenTimeout) + end - expect { response_instance_target_method } - .to change(response_instance, :connection).from(nil).to(false) - .and change(response_instance, :errors) - .from({}).to({ connection: Truemail::Validate::Smtp::Request::CONNECTION_DROPPED, helo: 'StandardError' }) - .and change(response_instance, :helo).from(nil).to(false) - .and not_change(response_instance, :mailfrom) - .and not_change(response_instance, :rcptto) - - expect(response_instance_target_method).to be(false) - end + specify do + error_stubs - include_examples 'request retry behavior' - end + expect { response_instance_target_method } + .to change(response_instance, :connection).from(nil).to(false) + .and change(response_instance, :errors) + .from({}).to({ connection: Truemail::Validate::Smtp::Request::CONNECTION_TIMEOUT_ERROR }) + .and not_change(response_instance, :helo) + .and not_change(response_instance, :mailfrom) + .and not_change(response_instance, :rcptto) - context 'when connection other errors' do - let(:error_stubs) do - allow(session).to receive(:start).and_raise(StandardError, error_message) + expect(response_instance_target_method).to be(false) + end + + include_examples 'request retry behavior' end - specify do - error_stubs + context 'when remote server has dropped connection during session' do + let(:error_stubs) do + allow(session).to receive(:start).and_yield(session).and_raise(EOFError) + allow(session).to receive(:helo).and_raise(StandardError) + end - expect { response_instance_target_method } - .to change(response_instance, :connection).from(nil).to(false) - .and change(response_instance, :errors) - .from({}).to({ connection: 'error message' }) - .and not_change(response_instance, :helo) - .and not_change(response_instance, :mailfrom) - .and not_change(response_instance, :rcptto) - - expect(response_instance_target_method).to be(false) + specify do + error_stubs + + expect { response_instance_target_method } + .to change(response_instance, :connection).from(nil).to(false) + .and change(response_instance, :errors) + .from({}).to({ connection: Truemail::Validate::Smtp::Request::CONNECTION_DROPPED, helo: 'StandardError' }) + .and change(response_instance, :helo).from(nil).to(false) + .and not_change(response_instance, :mailfrom) + .and not_change(response_instance, :rcptto) + + expect(response_instance_target_method).to be(false) + end + + include_examples 'request retry behavior' end - include_examples 'request retry behavior' - end + context 'when connection other errors' do + let(:error_stubs) do + allow(session).to receive(:start).and_raise(StandardError, error_message) + end - context 'when smtp response errors' do - it 'helo smtp server response timeout' do - allow(session).to receive(:start).and_yield(session) - allow(session).to receive(:helo).and_raise(Net::ReadTimeout) - allow(session).to receive(:mailfrom) - allow(session).to receive(:rcptto) + specify do + error_stubs - expect { response_instance_target_method } - .to change(response_instance, :connection).from(nil).to(true) - .and change(response_instance, :helo).from(nil).to(false) - .and change(response_instance, :errors) - .from({}).to({ helo: Truemail::Validate::Smtp::Request::RESPONSE_TIMEOUT_ERROR }) - .and not_change(response_instance, :mailfrom) - .and not_change(response_instance, :rcptto) + expect { response_instance_target_method } + .to change(response_instance, :connection).from(nil).to(false) + .and change(response_instance, :errors) + .from({}).to({ connection: 'error message' }) + .and not_change(response_instance, :helo) + .and not_change(response_instance, :mailfrom) + .and not_change(response_instance, :rcptto) - expect(session).not_to have_received(:mailfrom) - expect(session).not_to have_received(:rcptto) + expect(response_instance_target_method).to be(false) + end - expect(response_instance_target_method).to be(false) + include_examples 'request retry behavior' end - it 'mailfrom smtp server error' do - allow(session).to receive(:start).and_yield(session) - allow(session).to receive(:helo).and_return(true) - allow(session).to receive(:mailfrom).and_raise(StandardError, error_message) - allow(session).to receive(:rcptto) + context 'when smtp response errors' do + it 'helo smtp server response timeout' do + allow(session).to receive(:start).and_yield(session) + allow(session).to receive(:helo).and_raise(Net::ReadTimeout) + allow(session).to receive(:mailfrom) + allow(session).to receive(:rcptto) + + expect { response_instance_target_method } + .to change(response_instance, :connection).from(nil).to(true) + .and change(response_instance, :helo).from(nil).to(false) + .and change(response_instance, :errors) + .from({}).to({ helo: Truemail::Validate::Smtp::Request::RESPONSE_TIMEOUT_ERROR }) + .and not_change(response_instance, :mailfrom) + .and not_change(response_instance, :rcptto) + + expect(session).not_to have_received(:mailfrom) + expect(session).not_to have_received(:rcptto) + + expect(response_instance_target_method).to be(false) + end + + it 'mailfrom smtp server error' do + allow(session).to receive(:start).and_yield(session) + allow(session).to receive(:helo).and_return(true) + allow(session).to receive(:mailfrom).and_raise(StandardError, error_message) + allow(session).to receive(:rcptto) + + expect { response_instance_target_method } + .to change(response_instance, :connection).from(nil).to(true) + .and change(response_instance, :helo).from(nil).to(true) + .and change(response_instance, :mailfrom).from(nil).to(false) + .and change(response_instance, :errors).from({}).to({ mailfrom: error_message }) + .and not_change(response_instance, :rcptto) + + expect(session).not_to have_received(:rcptto) + + expect(response_instance_target_method).to be(false) + end + + it 'rcptto smtp server error' do + allow(session).to receive(:start).and_yield(session) + allow(session).to receive(:helo).and_return(true) + allow(session).to receive(:mailfrom).and_return(true) + allow(session).to receive(:rcptto).and_raise(StandardError, error_message) + + expect { response_instance_target_method } + .to change(response_instance, :connection).from(nil).to(true) + .and change(response_instance, :helo).from(nil).to(true) + .and change(response_instance, :mailfrom).from(nil).to(true) + .and change(response_instance, :rcptto).from(nil).to(false) + .and change(response_instance, :errors).from({}).to({ rcptto: error_message }) + + expect(response_instance_target_method).to be(false) + end + end + end + end + end - expect { response_instance_target_method } - .to change(response_instance, :connection).from(nil).to(true) - .and change(response_instance, :helo).from(nil).to(true) - .and change(response_instance, :mailfrom).from(nil).to(false) - .and change(response_instance, :errors).from({}).to({ mailfrom: error_message }) - .and not_change(response_instance, :rcptto) + RSpec.describe Truemail::Validate::Smtp::Request::Configuration do + subject(:request_configuration_instance) { described_class.new(configuration_instance) } - expect(session).not_to have_received(:rcptto) + let(:configuration_instance) { create_configuration } - expect(response_instance_target_method).to be(false) - end + describe 'defined constants' do + specify { expect(described_class).to be_const_defined(:REQUEST_PARAMS) } + end - it 'rcptto smtp server error' do - allow(session).to receive(:start).and_yield(session) - allow(session).to receive(:helo).and_return(true) - allow(session).to receive(:mailfrom).and_return(true) - allow(session).to receive(:rcptto).and_raise(StandardError, error_message) + describe 'attribute readers' do + let(:attribute_readers) { %i[connection_timeout response_timeout verifier_domain verifier_email] } - expect { response_instance_target_method } - .to change(response_instance, :connection).from(nil).to(true) - .and change(response_instance, :helo).from(nil).to(true) - .and change(response_instance, :mailfrom).from(nil).to(true) - .and change(response_instance, :rcptto).from(nil).to(false) - .and change(response_instance, :errors).from({}).to({ rcptto: error_message }) + specify { expect(request_configuration_instance.public_methods).to include(*attribute_readers) } + end - expect(response_instance_target_method).to be(false) + describe '.new' do + Truemail::Validate::Smtp::Request::Configuration::REQUEST_PARAMS.each do |method| + specify do + expect(request_configuration_instance.public_send(method)).to eq(configuration_instance.public_send(method)) end end end diff --git a/spec/truemail/validate/smtp_spec.rb b/spec/truemail/validate/smtp_spec.rb index 39fa1a7..8c6bbae 100644 --- a/spec/truemail/validate/smtp_spec.rb +++ b/spec/truemail/validate/smtp_spec.rb @@ -2,8 +2,7 @@ RSpec.describe Truemail::Validate::Smtp do let(:email) { FFaker::Internet.email } - - before { Truemail.configure { |config| config.verifier_email = email } } + let(:configuration_instance) { create_configuration } describe 'defined constants' do specify { expect(described_class).to be_const_defined(:ERROR) } @@ -17,7 +16,8 @@ described_class.new( Truemail::Validator::Result.new( email: email, - mail_servers: Array.new(3) { FFaker::Internet.ip_v4_address } + mail_servers: Array.new(3) { FFaker::Internet.ip_v4_address }, + configuration: configuration_instance ) ) end @@ -50,7 +50,7 @@ before { allow(result_instance.mail_servers).to receive(:one?).and_return(true) } it 'returns hash with attempts from configuration' do - expect(smtp_validator_instance.send(:attempts)).to eq({ attempts: Truemail.configuration.connection_attempts }) + expect(smtp_validator_instance.send(:attempts)).to eq({ attempts: configuration_instance.connection_attempts }) end end end @@ -100,7 +100,7 @@ expect { smtp_validator_instance.send(:establish_smtp_connection) } .to change(smtp_results, :size).from(0).to(1) - expect(smtp_results.last.host).to eq(result_instance.mail_servers.first) + expect(smtp_results.last.send(:host)).to eq(result_instance.mail_servers.first) end end end @@ -115,7 +115,9 @@ context 'when smtp validation has success response' do before do request = Truemail::Validate::Smtp::Request.new( - host: result_instance.mail_servers.first, email: result_instance.email + host: result_instance.mail_servers.first, + email: result_instance.email, + configuration: configuration_instance ) request.response.rcptto = true @@ -141,12 +143,16 @@ before do request_1 = Truemail::Validate::Smtp::Request.new( - host: result_instance.mail_servers[0], email: result_instance.email + host: result_instance.mail_servers[0], + email: result_instance.email, + configuration: configuration_instance ) request_1.response.port_opened = false request_2 = Truemail::Validate::Smtp::Request.new( - host: result_instance.mail_servers[1], email: result_instance.email + host: result_instance.mail_servers[1], + email: result_instance.email, + configuration: configuration_instance ) request_2.response.tap do |response| response.port_opened = true @@ -155,7 +161,9 @@ end request_3 = Truemail::Validate::Smtp::Request.new( - host: result_instance.mail_servers[2], email: result_instance.email + host: result_instance.mail_servers[2], + email: result_instance.email, + configuration: configuration_instance ) request_3.response.tap do |response| @@ -187,7 +195,7 @@ end context 'with smtp safe check' do - before { Truemail.configuration.smtp_safe_check = true } + before { configuration_instance.smtp_safe_check = true } context 'when smtp user error has been not detected' do specify do diff --git a/spec/truemail/validator_spec.rb b/spec/truemail/validator_spec.rb index b553a3e..74ea168 100644 --- a/spec/truemail/validator_spec.rb +++ b/spec/truemail/validator_spec.rb @@ -5,11 +5,11 @@ module Truemail subject(:validator_instance) { described_class.new(email, options) } let(:email) { FFaker::Internet.email } - let(:options) { {} } + let(:configuration_instance) { create_configuration } + let(:configuration) { { configuration: configuration_instance } } + let(:options) { { **configuration } } let(:validator_instance_result) { validator_instance.result } - before { Truemail.configure { |config| config.verifier_email = email } } - describe 'defined constants' do specify { expect(described_class).to be_const_defined(:RESULT_ATTRS) } specify { expect(described_class).to be_const_defined(:VALIDATION_TYPES) } @@ -19,7 +19,7 @@ module Truemail describe '.new' do Truemail::Validator::VALIDATION_TYPES.each do |validation_type| context "with: #{validation_type}" do - let(:options) { { with: validation_type } } + let(:options) { { with: validation_type, **configuration } } it "creates validator instance with #{validation_type} validation type" do expect(validator_instance.validation_type).to eq(validation_type) @@ -27,6 +27,7 @@ module Truemail it 'creates default validator result' do expect(validator_instance_result).to be_an_instance_of(Truemail::Validator::Result) + expect(validator_instance_result.configuration).to eq(configuration_instance) expect(validator_instance_result.success).to be_nil expect(validator_instance_result.email).to eq(email) expect(validator_instance_result.domain).to be_nil @@ -37,7 +38,7 @@ module Truemail end context 'with invalid validation type' do - let(:options) { { with: :invalid_validation_type } } + let(:options) { { with: :invalid_validation_type, **configuration } } specify do expect { validator_instance } @@ -49,7 +50,7 @@ module Truemail describe '#run' do subject(:validator_instance_run) { validator_instance.run } - let(:validator_instance) { described_class.new(email, with: validation_type) } + let(:validator_instance) { described_class.new(email, with: validation_type, **configuration) } let(:validation_type) { :regex } before do @@ -96,16 +97,14 @@ module Truemail describe '#select_validation_type' do subject(:select_validation_type) do - described_class.new(email, with: current_validation_type).validation_type + described_class.new(email, with: current_validation_type, **configuration).validation_type end let(:current_validation_type) { :regex } let(:new_validation_type) { :mx } let(:domain) { FFaker::Internet.domain_name } - before do - Truemail.configuration.validation_type_for = { domain => new_validation_type } - end + before { configuration_instance.validation_type_for = { domain => new_validation_type } } context 'when domain of current email exists in configuration' do let(:email) { "email@#{domain}" } diff --git a/spec/truemail/wrapper_spec.rb b/spec/truemail/wrapper_spec.rb index 13c9417..bbfd022 100644 --- a/spec/truemail/wrapper_spec.rb +++ b/spec/truemail/wrapper_spec.rb @@ -1,17 +1,25 @@ # frozen_string_literal: true RSpec.describe Truemail::Wrapper do - let(:email) { FFaker::Internet.email } - let(:method) { :hosts_from_mx_records? } - let(:mx_instance) { instance_double(Truemail::Validate::Mx, method => true) } + let(:configuration_instance) { create_configuration } + let(:method) { :method } + let(:mx_instance) { instance_double('SomeObject', method => true) } let(:block) { ->(_) { mx_instance.send(method) } } - before { Truemail.configure { |config| config.verifier_email = email } } - describe '.call' do + subject(:resolver_execution_wrapper) do + described_class.call(configuration: configuration_instance, &block) + end + + it 'returns wrapped block context' do + expect(resolver_execution_wrapper).to be(true) + end + end + + describe '#call' do subject(:resolver_execution_wrapper) { resolver_execution_wrapper_instance.call(&block) } - let(:resolver_execution_wrapper_instance) { described_class.new } + let(:resolver_execution_wrapper_instance) { described_class.new(configuration_instance) } before { allow(resolver_execution_wrapper_instance).to receive(:call).and_call_original } diff --git a/spec/truemail_spec.rb b/spec/truemail_spec.rb index e551ad1..d9a0e41 100644 --- a/spec/truemail_spec.rb +++ b/spec/truemail_spec.rb @@ -2,6 +2,15 @@ RSpec.describe Truemail do let(:email) { FFaker::Internet.email } + let(:custom_configuration) { nil } + + shared_examples 'configuration error' do + context 'when global configuration not set or custom configuration not passed' do + specify do + expect { subject }.to raise_error(Truemail::ConfigurationError, Truemail::NOT_CONFIGURED) + end + end + end describe 'defined constants' do specify { expect(described_class).to be_const_defined(:INCOMPLETE_CONFIG) } @@ -19,100 +28,106 @@ specify { expect(described_class).to be_const_defined(:Validate) } end - describe '.configure' do - subject(:configure) { described_class.configure(&config_block) } + describe 'global configuration methods' do + describe '.configure' do + subject(:configure) { described_class.configure(&config_block) } - let(:config_block) {} + let(:config_block) {} - context 'without block' do - specify { expect(configure).to be_nil } - specify { expect { configure }.not_to change(described_class, :configuration) } - end + context 'without block' do + specify { expect(configure).to be_nil } + specify { expect { configure }.not_to change(described_class, :configuration) } + end - context 'with block' do - context 'without required parameter' do - let(:config_block) { configuration_block } + context 'with block' do + context 'without required parameter' do + let(:config_block) { configuration_block } - specify do - expect { configure } - .to raise_error(Truemail::ConfigurationError, Truemail::INCOMPLETE_CONFIG) + specify do + expect { configure } + .to raise_error(Truemail::ConfigurationError, Truemail::INCOMPLETE_CONFIG) + end end - end - context 'with valid required parameter' do - let(:config_block) { configuration_block(verifier_email: email) } + context 'with valid required parameter' do + let(:config_block) { configuration_block(verifier_email: email) } - specify do - expect { configure } - .to change(described_class, :configuration) - .from(nil).to(be_instance_of(Truemail::Configuration)) - end + specify do + expect { configure } + .to change(described_class, :configuration) + .from(nil).to(be_instance_of(Truemail::Configuration)) + end - it 'sets attributes into configuration instance' do - expect(configure).to be_an_instance_of(Truemail::Configuration) - expect(described_class.configuration.verifier_email).to eq(email) + it 'sets attributes into configuration instance' do + expect(configure).to be_an_instance_of(Truemail::Configuration) + expect(described_class.configuration.verifier_email).to eq(email) + end end end end - end - describe '.reset_configuration!' do - before { described_class.configure(&configuration_block(verifier_email: email)) } + describe '.reset_configuration!' do + before { described_class.configure(&configuration_block(verifier_email: email)) } - specify do - expect { described_class.reset_configuration! } - .to change(described_class, :configuration) - .from(be_instance_of(Truemail::Configuration)).to(nil) + specify do + expect { described_class.reset_configuration! } + .to change(described_class, :configuration) + .from(be_instance_of(Truemail::Configuration)).to(nil) + end end - end - describe '.configuration' do - subject(:configuration) { described_class.configuration } + describe '.configuration' do + subject(:configuration) { described_class.configuration } - before do - described_class.configure(&configuration_block( - verifier_email: email, - verifier_domain: domain + before do + described_class.configure(&configuration_block( + verifier_email: email, + verifier_domain: domain + ) ) - ) - end + end - let(:domain) { FFaker::Internet.domain_name } - let(:new_email) { FFaker::Internet.email } - let(:new_domain) { FFaker::Internet.domain_name } - let(:new_regex_pattern) { /\A+.\z/ } - let(:new_smtp_error_body_pattern) { /\A\d+\z/ } - - specify { expect(configuration).to be_instance_of(Truemail::Configuration) } - - it 'accepts to rewrite current configuration settings' do - expect do - configuration.tap(&configuration_block( - verifier_email: new_email, - verifier_domain: new_domain, - email_pattern: new_regex_pattern, - smtp_error_body_pattern: new_smtp_error_body_pattern + let(:domain) { FFaker::Internet.domain_name } + let(:new_email) { FFaker::Internet.email } + let(:new_domain) { FFaker::Internet.domain_name } + let(:new_regex_pattern) { /\A+.\z/ } + let(:new_smtp_error_body_pattern) { /\A\d+\z/ } + + specify { expect(configuration).to be_instance_of(Truemail::Configuration) } + + it 'accepts to rewrite current configuration settings' do + expect do + configuration.tap(&configuration_block( + verifier_email: new_email, + verifier_domain: new_domain, + email_pattern: new_regex_pattern, + smtp_error_body_pattern: new_smtp_error_body_pattern + ) ) - ) + end + .to change(configuration, :verifier_email).from(email).to(new_email) + .and change(configuration, :verifier_domain).from(domain).to(new_domain) + .and change(configuration, :email_pattern) + .from(Truemail::RegexConstant::REGEX_EMAIL_PATTERN).to(new_regex_pattern) + .and change(configuration, :smtp_error_body_pattern) + .from(Truemail::RegexConstant::REGEX_SMTP_ERROR_BODY_PATTERN).to(new_smtp_error_body_pattern) end - .to change(configuration, :verifier_email).from(email).to(new_email) - .and change(configuration, :verifier_domain).from(domain).to(new_domain) - .and change(configuration, :email_pattern) - .from(Truemail::RegexConstant::REGEX_EMAIL_PATTERN).to(new_regex_pattern) - .and change(configuration, :smtp_error_body_pattern) - .from(Truemail::RegexConstant::REGEX_SMTP_ERROR_BODY_PATTERN).to(new_smtp_error_body_pattern) end end describe '.validate' do - context 'when configuration not set' do + subject(:validate) { described_class.validate(email, custom_configuration: custom_configuration) } + + shared_examples 'returns validator instance' do specify do - expect { described_class.validate(email) } - .to raise_error(Truemail::ConfigurationError, Truemail::NOT_CONFIGURED) + allow(Truemail::Validate::Smtp).to receive(:check).and_return(true) + expect(validate).to be_an_instance_of(Truemail::Validator) end end - context 'when configuration successfully set' do + include_examples 'configuration error' + + context 'when global configuration successfully set' do before do described_class.configure do |config| config.verifier_email = 'valdyslav.trotsenko@rubygarage.org' @@ -121,11 +136,7 @@ end end - specify do - allow(Truemail::Validate::Smtp).to receive(:check).and_return(true) - expect(described_class.validate('nonexistent_email@rubygarage.org')) - .to be_an_instance_of(Truemail::Validator) - end + include_examples 'returns validator instance' describe 'integration tests' do context 'when checks real email' do @@ -141,35 +152,64 @@ end end end + + context 'when custom configuration passed' do + let(:custom_configuration) { create_configuration } + + include_examples 'returns validator instance' + end end describe '.valid?' do - subject(:valid_helper) { described_class.valid?(email) } + subject(:valid_helper) { described_class.valid?(email, custom_configuration: custom_configuration) } - before { described_class.configure { |config| config.verifier_email = email } } + shared_examples 'returns boolean' do + it 'returns boolean from validator result instance' do + allow(Truemail::Validate::Smtp).to receive(:check).and_return(true) + allow_any_instance_of(Truemail::Validator::Result).to receive(:valid?).and_return(true) + expect(valid_helper).to be(true) + end + end + + include_examples 'configuration error' + + context 'when global configuration successfully set' do + before { described_class.configure { |config| config.verifier_email = email } } + + include_examples 'returns boolean' + end + + context 'when custom configuration passed' do + let(:custom_configuration) { create_configuration } - it 'returns boolean from validator result instance' do - allow(Truemail::Validate::Smtp).to receive(:check).and_return(true) - allow_any_instance_of(Truemail::Validator::Result).to receive(:valid?).and_return(true) - expect(valid_helper).to be(true) + include_examples 'returns boolean' end end describe '.host_audit' do - subject(:host_audit) { described_class.host_audit } - - before do - described_class.configure { |config| config.verifier_email = email } - allow(Truemail::Auditor).to receive(:run).and_call_original + subject(:host_audit) { described_class.host_audit(custom_configuration: custom_configuration) } + + shared_examples 'returns auditor instance' do + it 'returns auditor instance' do + expect(Truemail::Auditor).to receive(:new).and_call_original + expect_any_instance_of(Truemail::Auditor).to receive(:run).and_call_original + expect(Truemail::Audit::Ptr).to receive(:check).and_return(true) + expect(host_audit).to be_an_instance_of(Truemail::Auditor) + end end - it 'returns auditor instance' do - expect(host_audit).to be_an_instance_of(Truemail::Auditor) + include_examples 'configuration error' + + context 'when global configuration successfully set' do + before { described_class.configure { |config| config.verifier_email = email } } + + include_examples 'returns auditor instance' end - it 'runs checks for auditor result instance' do - expect(Truemail::Audit::Ptr).to receive(:check) - host_audit + context 'when custom configuration passed' do + let(:custom_configuration) { create_configuration } + + include_examples 'returns auditor instance' end end end diff --git a/truemail.gemspec b/truemail.gemspec index 1f9707d..a94ddcd 100644 --- a/truemail.gemspec +++ b/truemail.gemspec @@ -11,7 +11,8 @@ Gem::Specification.new do |spec| spec.email = ['admin@bestweb.com.ua'] spec.summary = %(truemail) - spec.description = %(Configurable plain ruby email validator. Validate email by regexp, mx records and real email existence) + spec.description = %(Configurable plain Ruby email validator. Verify email via Regex, DNS and SMTP. Be sure that email address exists) + spec.homepage = 'https://github.com/rubygarage/truemail' spec.license = 'MIT'