diff --git a/.reek.yml b/.reek.yml index 279076f..e86dbd5 100644 --- a/.reek.yml +++ b/.reek.yml @@ -33,3 +33,4 @@ detectors: FeatureEnvy: exclude: - Truemail::Validate::Smtp#not_includes_user_not_found_errors + - Truemail::GenerateEmailHelper#prepare_user_name diff --git a/Gemfile.lock b/Gemfile.lock index caee75f..9cdda17 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - truemail (0.1.2) + truemail (0.1.3) GEM remote: https://rubygems.org/ diff --git a/lib/truemail/configuration.rb b/lib/truemail/configuration.rb index 0a038d7..8a54581 100644 --- a/lib/truemail/configuration.rb +++ b/lib/truemail/configuration.rb @@ -29,13 +29,13 @@ def email_pattern=(regex_pattern) def verifier_email=(email) validate_arguments(email, __method__) - @verifier_email = email + @verifier_email = email.downcase default_verifier_domain end def verifier_domain=(domain) validate_arguments(domain, __method__) - @verifier_domain = domain + @verifier_domain = domain.downcase end %i[connection_timeout response_timeout].each do |method| diff --git a/lib/truemail/core.rb b/lib/truemail/core.rb index 14c387d..07f3bb2 100644 --- a/lib/truemail/core.rb +++ b/lib/truemail/core.rb @@ -10,9 +10,10 @@ def initialize(current_param, class_name) end module RegexConstant - REGEX_DOMAIN = /[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,7}/ - REGEX_EMAIL_PATTERN = /(?=\A.{6,255}\z)(\A([\w|\-|\.]+)@(#{REGEX_DOMAIN})\z)/ + REGEX_DOMAIN = /[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,63}/i + REGEX_EMAIL_PATTERN = /(?=\A.{6,255}\z)(\A([a-zA-Z0-9]+[\w|\-|\.|\+]*)@(#{REGEX_DOMAIN})\z)/ REGEX_DOMAIN_PATTERN = /(?=\A.{4,255}\z)(\A#{REGEX_DOMAIN}\z)/ + REGEX_DOMAIN_FROM_EMAIL = /\A.+@(.+)\z/ end module Validate diff --git a/lib/truemail/validate/mx.rb b/lib/truemail/validate/mx.rb index ef7040b..712d936 100644 --- a/lib/truemail/validate/mx.rb +++ b/lib/truemail/validate/mx.rb @@ -9,7 +9,7 @@ class Mx < Truemail::Validate::Base def run return false unless Truemail::Validate::Regex.check(result) - result.domain = result.email[Truemail::RegexConstant::REGEX_EMAIL_PATTERN, 3] + result.domain = result.email[Truemail::RegexConstant::REGEX_DOMAIN_FROM_EMAIL, 1] return true if success(!result.mail_servers.push(*mx_records).empty?) add_error(Truemail::Validate::Mx::ERROR) false diff --git a/lib/truemail/validator.rb b/lib/truemail/validator.rb index 02398f3..bea0fa7 100644 --- a/lib/truemail/validator.rb +++ b/lib/truemail/validator.rb @@ -17,7 +17,7 @@ def initialize(errors: {}, mail_servers: [], **args) def initialize(email, with: :smtp) raise ArgumentError.new(with, :argument) unless VALIDATION_TYPES.include?(with) @validation_type = select_validation_type(email, with) - @result = Result.new(email: email) + @result = Truemail::Validator::Result.new(email: email) end def run diff --git a/lib/truemail/version.rb b/lib/truemail/version.rb index 363cddd..90f7614 100644 --- a/lib/truemail/version.rb +++ b/lib/truemail/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Truemail - VERSION = '0.1.2' + VERSION = '0.1.3' end diff --git a/spec/support/helpers/generate_email_helper.rb b/spec/support/helpers/generate_email_helper.rb index 62f2514..695f887 100644 --- a/spec/support/helpers/generate_email_helper.rb +++ b/spec/support/helpers/generate_email_helper.rb @@ -6,7 +6,7 @@ def self.call(**options) new(options).call end - def initialize(size: :auto, symbols: %w[- _ .], invalid_email_with: []) + def initialize(size: :auto, symbols: %w[- _ . +], invalid_email_with: []) @size = calculate_email_size(size) @symbols = symbols @invalid_symbols = invalid_email_with @@ -23,7 +23,7 @@ def call def calculate_email_size(size) case size - when :auto then rand(10..250) + when :auto then rand(15..250) when :min then 1 when :max then 250 when :out_of_range then rand(251..300) @@ -36,15 +36,34 @@ def sample_size size < (symbols_size + invalid_symbols_size + 1) ? 1 : size - symbols_size - invalid_symbols_size - 1 end + def invalid_symbols_empty? + invalid_symbols.empty? + end + + def size_one? + size == 1 + end + def user_name @user_name ||= - if size == 1 && !invalid_symbols.empty? + if size_one? && !invalid_symbols_empty? invalid_symbols.sample + elsif size_one? && invalid_symbols_empty? + ('a'..'z').to_a.sample else - ( - ('Aa'..'Zz').to_a.shuffle.join.chars.sample(sample_size).push(*symbols.shuffle) << rand(0..9) - ).shuffle.push(*invalid_symbols.sample(size)).shuffle[0...size].join + prepare_user_name(randomizer) end end + + def randomizer + ( + ('Aa'..'Zz').to_a.shuffle.join.chars.sample(sample_size).push(*symbols.shuffle) << rand(0..9) + ).shuffle.push(*invalid_symbols.sample(size)).shuffle[0...size] + end + + def prepare_user_name(sample) + sample.rotate!(1) while symbols.include?(sample.first) + sample.join + end end end diff --git a/spec/support/helpers/generate_email_helper_spec.rb b/spec/support/helpers/generate_email_helper_spec.rb index 30e11f6..06ab3fe 100644 --- a/spec/support/helpers/generate_email_helper_spec.rb +++ b/spec/support/helpers/generate_email_helper_spec.rb @@ -6,13 +6,17 @@ module Truemail context 'without params' do subject(:generate_email) { described_class.call } + let(:allowed_symbols) { %w[@ - _ . +] } + specify { expect(generate_email).to be_an_instance_of(String) } specify { 100.times { expect(generate_email.size).to be_between(15, 255) } } - specify { 100.times { expect(generate_email).to include(*%w[@ - _ .]) } } + specify { 100.times { expect(generate_email[0]).not_to include(*allowed_symbols) } } + specify { 100.times { expect(generate_email).to include(*allowed_symbols) } } end context 'with size: :min' do specify { expect(described_class.call(size: :min).size).to eq(6) } + specify { expect(described_class.call(size: :min)[0]).to match(/[a-z]/) } end context 'with size: :max' do diff --git a/spec/truemail/configuration_spec.rb b/spec/truemail/configuration_spec.rb index 1dd672a..9c443d0 100644 --- a/spec/truemail/configuration_spec.rb +++ b/spec/truemail/configuration_spec.rb @@ -27,6 +27,14 @@ let(:default_verifier_domain) { valid_email[/\A(.+)@(.+)\z/, 2] } context 'when auto configuration' do + let(:configuration_instance_expectaions) do + expect(configuration_instance.email_pattern).to eq(Truemail::RegexConstant::REGEX_EMAIL_PATTERN) + expect(configuration_instance.connection_timeout).to eq(2) + expect(configuration_instance.response_timeout).to eq(2) + expect(configuration_instance.validation_type_by_domain).to eq({}) + expect(configuration_instance.smtp_safe_check).to be(false) + end + it 'sets configuration instance with default configuration template' do expect { configuration_instance.verifier_email = valid_email } .to change(configuration_instance, :verifier_email) @@ -41,11 +49,41 @@ .and not_change(configuration_instance, :validation_type_by_domain) .and not_change(configuration_instance, :smtp_safe_check) - expect(configuration_instance.email_pattern).to eq(Truemail::RegexConstant::REGEX_EMAIL_PATTERN) - expect(configuration_instance.connection_timeout).to eq(2) - expect(configuration_instance.response_timeout).to eq(2) - expect(configuration_instance.validation_type_by_domain).to eq({}) - expect(configuration_instance.smtp_safe_check).to be(false) + configuration_instance_expectaions + end + + it 'sets configuration instance with default configuration template for upcase email' do + expect { configuration_instance.verifier_email = valid_email.upcase } + .to change(configuration_instance, :verifier_email) + .from(nil).to(valid_email) + .and change(configuration_instance, :verifier_domain) + .from(nil).to(default_verifier_domain) + .and change(configuration_instance, :complete?) + .from(false).to(true) + .and not_change(configuration_instance, :email_pattern) + .and not_change(configuration_instance, :connection_timeout) + .and not_change(configuration_instance, :response_timeout) + .and not_change(configuration_instance, :validation_type_by_domain) + .and not_change(configuration_instance, :smtp_safe_check) + + configuration_instance_expectaions + end + + it 'sets configuration instance with default configuration template for mixcase email' do + expect { configuration_instance.verifier_email = valid_email.capitalize } + .to change(configuration_instance, :verifier_email) + .from(nil).to(valid_email) + .and change(configuration_instance, :verifier_domain) + .from(nil).to(default_verifier_domain) + .and change(configuration_instance, :complete?) + .from(false).to(true) + .and not_change(configuration_instance, :email_pattern) + .and not_change(configuration_instance, :connection_timeout) + .and not_change(configuration_instance, :response_timeout) + .and not_change(configuration_instance, :validation_type_by_domain) + .and not_change(configuration_instance, :smtp_safe_check) + + configuration_instance_expectaions end end @@ -78,6 +116,18 @@ .to change(configuration_instance, :verifier_domain) .from(nil).to(valid_domain) end + + it 'sets custom verifier domain for upcase domain' do + expect { configuration_instance.verifier_domain = valid_domain.upcase } + .to change(configuration_instance, :verifier_domain) + .from(nil).to(valid_domain) + end + + it 'sets custom verifier domain for mixcase domain' do + expect { configuration_instance.verifier_domain = valid_domain.capitalize } + .to change(configuration_instance, :verifier_domain) + .from(nil).to(valid_domain) + end end context 'with invalid domain' do diff --git a/spec/truemail/core_spec.rb b/spec/truemail/core_spec.rb index 2c1f4b4..21b28b8 100644 --- a/spec/truemail/core_spec.rb +++ b/spec/truemail/core_spec.rb @@ -13,6 +13,7 @@ module Truemail specify { expect(described_class).to be_const_defined(:REGEX_DOMAIN) } specify { expect(described_class).to be_const_defined(:REGEX_EMAIL_PATTERN) } specify { expect(described_class).to be_const_defined(:REGEX_DOMAIN_PATTERN) } + specify { expect(described_class).to be_const_defined(:REGEX_DOMAIN_FROM_EMAIL) } end describe 'Truemail::RegexConstant::REGEX_EMAIL_PATTERN' do @@ -38,15 +39,34 @@ module Truemail ).to be(false) end - it "allows '-', '_', '.', numbers, letters case insensitive before @domain" do + it "allows '-', '_', '.', '+', numbers, letters case insensitive before @domain" do expect(regex_pattern.match?(GenerateEmailHelper.call)).to be(true) end + it 'allows tld size between 2 and 63 chars' do + expect(regex_pattern.match?('i@i.io')).to be(true) + expect(regex_pattern.match?('i@i.io' + 'z' * 61)).to be(true) + expect(regex_pattern.match?('i@i.io' + 'z' * 62)).to be(false) + expect(regex_pattern.match?('i@i.i')).to be(false) + end + + it 'case insensitive' do + %w[h@i.io H@i.io h@I.io h@i.Io H@i.Io Ho@iO.Io].each do |email| + expect(regex_pattern.match?(email)).to be(true) + end + end + it 'not allows special chars' do expect( regex_pattern.match?(GenerateEmailHelper.call(invalid_email_with: %w[! ~ , ' & %])) ).to be(false) end + + it "not allows '-', '_', '.', '+' for one char username" do + expect( + regex_pattern.match?(GenerateEmailHelper.call(size: :min, invalid_email_with: %w[- _ . +])) + ).to be(false) + end end describe 'Truemail::RegexConstant::REGEX_DOMAIN_PATTERN' do @@ -70,9 +90,11 @@ module Truemail expect(regex_pattern.match?('service.subdomain.company.domain')).to be(true) end - it 'allows tld size between 2 and 7 chars' do + it 'allows tld size between 2 and 63 chars' do expect(regex_pattern.match?('domain.io')).to be(true) - expect(regex_pattern.match?('domain.rentals')).to be(true) + expect(regex_pattern.match?('domain.iq' + 'z' * 61)).to be(true) + expect(regex_pattern.match?('domain.iq' + 'z' * 62)).to be(false) + expect(regex_pattern.match?('domain')).to be(false) end it 'not allows dash as last char' do @@ -83,6 +105,21 @@ module Truemail it 'not allows number in tld' do expect(regex_pattern.match?('domain.42')).to be(false) end + + it 'case insensitive' do + %w[domain.io DOMAIN.IO Domain.io DoMain.Io].each do |domain| + expect(regex_pattern.match?(domain)).to be(true) + end + end + end + + describe 'Truemail::RegexConstant::REGEX_DOMAIN_FROM_EMAIL' do + subject(:regex_pattern) { described_class::REGEX_DOMAIN_FROM_EMAIL } + + let(:email) { 'i@domain' } + + specify { expect(regex_pattern.match?(email)).to be(true) } + specify { expect(email[regex_pattern, 1]).to eq('domain') } end end