# frozen_string_literal: true require "spec_helper" require "fileutils" class TestUser < TestModel validates :email, 'valid_email_2/email': true end class TestUserDotted < TestModel validates :email, 'valid_email_2/email': { disallow_dotted: true } end class TestUserSubaddressing < TestModel validates :email, 'valid_email_2/email': { disallow_subaddressing: true } end class TestUserMX < TestModel validates :email, 'valid_email_2/email': { mx: true } end class TestUserStrictMX < TestModel validates :email, 'valid_email_2/email': { strict_mx: true } end class TestUserMXDnsTimeout < TestModel validates :email, 'valid_email_2/email': { mx: true, dns_timeout: 10 } end class TestUserMXDnsFailingTimeout < TestModel validates :email, 'valid_email_2/email': { mx: true, dns_timeout: Float::MIN } end class TestUserMXDnsNameserver < TestModel validates :email, 'valid_email_2/email': { mx: true, dns_nameserver: ['8.8.8.8', '8.8.4.4'] } end class TestUserMXDnsFailingNameserver < TestModel validates :email, 'valid_email_2/email': { mx: true, dns_timeout: 0.1, dns_nameserver: '1.0.0.0' } end class TestUserDisallowDisposable < TestModel validates :email, 'valid_email_2/email': { disposable: true } end class TestUserDisallowDisposableDomain < TestModel validates :email, 'valid_email_2/email': { disposable_domain: true } end class TestUserDisallowDisposableWithAllowList < TestModel validates :email, 'valid_email_2/email': { disposable_with_allow_list: true } end class TestUserDisallowDisposableDomainWithAllowList < TestModel validates :email, 'valid_email_2/email': { disposable_domain_with_allow_list: true } end class TestUserDisallowDenyListed < TestModel validates :email, 'valid_email_2/email': { deny_list: true } end class TestUserMessage < TestModel validates :email, 'valid_email_2/email': { message: "custom message" } end class TestUserMessageWithLambda < TestModel validates :email, 'valid_email_2/email': { message: -> { "custom message lambda" } } end class TestUserMessageWithProc < TestModel validates :email, 'valid_email_2/email': { message: Proc.new { "custom message proc" } } end class TestUserMultiple < TestModel validates :email, 'valid_email_2/email': { multiple: true } end describe ValidEmail2 do let(:disposable_domain) { ValidEmail2.disposable_emails.first } describe "basic validation" do subject(:user) { TestUser.new(email: "") } it "is valid" do user = TestUser.new(email: "foo@bar123.com") expect(user.valid?).to be_truthy end it "is invalid if email is empty" do expect(user.valid?).to be_truthy end it "is invalid if domain is missing" do user = TestUser.new(email: "foo@.com") expect(user.valid?).to be_falsey end %w[+ _ ! / \ ' `].each do |invalid_character| it "is invalid if email contains a \"#{invalid_character}\" character" do user = TestUser.new(email: "foo@google#{invalid_character}yahoo.com") expect(user.valid?).to be_falsey end end it "is invalid if address is malformed" do user = TestUser.new(email: "foo@bar") expect(user.valid?).to be_falsey end it "is invalid if Mail::AddressListsParser raises exception" do user = TestUser.new(email: "foo@gmail.com") expect(Mail::Address).to receive(:new).and_raise(Mail::Field::ParseError.new(nil, nil, nil)) expect(user.valid?).to be_falsey end it "is invalid if the domain contains consecutive dots" do user = TestUser.new(email: "foo@bar..com") expect(user.valid?).to be_falsey end it "is invalid if the address contains consecutive dots" do user = TestUser.new(email: "foo..bar@gmail.com") expect(user.valid?).to be_falsey end it "is invalid if the address starts with a dot" do user = TestUser.new(email: ".foo@bar.com") expect(user.valid?).to be_falsey end it "is invalid if the local part of the address ends with a dot" do user = TestUser.new(email: "foo.@bar.com") expect(user.valid?).to be_falsey end it "is invalid if the email contains emoticons" do user = TestUser.new(email: "foo🙈@gmail.com") expect(user.valid?).to be_falsy end it "is invalid if the domain contains spaces" do user = TestUser.new(email: "user@gmail .com") expect(user.valid?).to be_falsy end it "is invalid if the domain contains a forward slash" do user = TestUser.new(email: "user@gm/ail.com") expect(user.valid?).to be_falsy end it "is invalid if the domain begins with a dash" do user = TestUser.new(email: "foo@-gmail.com") expect(user.valid?).to be_falsy end it "is invalid if the domain name ends with a dash" do user = TestUser.new(email: "foo@gmail-.com") expect(user.valid?).to be_falsy end end describe "with disposable validation" do it "is valid if it's not a disposable email" do user = TestUserDisallowDisposable.new(email: "foo@gmail.com") expect(user.valid?).to be_truthy end it "is valid if it just ends with a disposable domain" do user = TestUserDisallowDisposable.new(email: "foo@nondisposable-0-mail.com") expect(user.valid?).to be_truthy end context "with disposable domain" do it "is invalid with disposable domain" do user = TestUserDisallowDisposable.new(email: "foo@#{disposable_domain}") expect(user.valid?).to be_falsey end it "is invalid with disposable domain even without mx server check" do user = TestUserDisallowDisposableDomain.new(email: "foo@#{disposable_domain}") expect(user.valid?).to be_falsey end it "is invalid if the domain is a subdomain of a disposable domain" do user = TestUserDisallowDisposable.new(email: "foo@bar.#{disposable_domain}") expect(user.valid?).to be_falsey end it "allows example.com" do user = TestUserDisallowDisposable.new(email: "foo@example.com") expect(user.valid?).to be_truthy end end context "with domain that is not disposable but it's mx server is disposable" do let(:domain) { "sogetthis.com" } around do |example| ValidEmail2.disposable_emails.delete(domain) example.run ValidEmail2.disposable_emails << domain end it "is invalid" do user = TestUserDisallowDisposable.new(email: "foo@sogetthis.com") expect(user.valid?).to be_falsey end end describe "with allow-listed emails" do let(:allow_list_domain) { disposable_domain } let(:allow_list_file_path) { "config/allow_listed_email_domains.yml" } # Some of the specs below need to explictly set the allow list var or it # may be cached to an empty set def set_allow_list ValidEmail2.instance_variable_set( :@allow_list, ValidEmail2.send(:load_if_exists, ValidEmail2::ALLOW_LIST_FILE) ) end after do FileUtils.rm(allow_list_file_path, force: true) set_allow_list end it "is invalid if the domain is disposable and not in the allow list" do user = TestUserDisallowDisposableWithAllowList.new(email: "foo@#{allow_list_domain}") expect(user.valid?).to be_falsey end it "is valid if the domain is disposable but in the allow list" do File.open(allow_list_file_path, "w") { |f| f.write [allow_list_domain].to_yaml } set_allow_list user = TestUserDisallowDisposableWithAllowList.new(email: "foo@#{allow_list_domain}") expect(user.valid?).to be_truthy end it "is invalid if the domain is a disposable_domain and not in the allow list" do user = TestUserDisallowDisposableDomainWithAllowList.new(email: "foo@#{allow_list_domain}") expect(user.valid?).to be_falsey end it "is valid if the domain is a disposable_domain but in the allow list" do File.open(allow_list_file_path, "w") { |f| f.write [allow_list_domain].to_yaml } set_allow_list user = TestUserDisallowDisposableDomainWithAllowList.new(email: "foo@#{allow_list_domain}") expect(user.valid?).to be_truthy end end end describe "with deny list validation" do it "is valid if the domain is not deny-listed" do user = TestUserDisallowDenyListed.new(email: "foo@gmail.com") expect(user.valid?).to be_truthy end it "is invalid if the domain is deny-listed" do user = TestUserDisallowDenyListed.new(email: "foo@deny-listed-test.com") expect(user.valid?).to be_falsey end end describe "with mx validation" do it "is valid if mx records are found" do user = TestUserMX.new(email: "foo@gmail.com") expect(user.valid?).to be_truthy end it "is valid if A records are found" do user = TestUserMX.new(email: "foo@ghs.google.com") expect(user.valid?).to be_truthy end it "is invalid if no mx records are found" do user = TestUserMX.new(email: "foo@subdomain.gmail.com") expect(user.valid?).to be_falsey end it "is invalid if a null mx is found" do user = TestUserMX.new(email: "foo@gmail.de") expect(user.valid?).to be_falsey end end describe "with strict mx validation" do it "is valid if mx records are found" do user = TestUserStrictMX.new(email: "foo@gmail.com") expect(user.valid?).to be_truthy end it "is invalid if A records are found but no mx records are found" do user = TestUserStrictMX.new(email: "foo@ghs.google.com") expect(user.valid?).to be_falsey end it "is invalid if no mx records are found" do user = TestUserStrictMX.new(email: "foo@subdomain.gmail.com") expect(user.valid?).to be_falsey end it "is invalid if a null mx is found" do user = TestUserStrictMX.new(email: "foo@gmail.de") expect(user.valid?).to be_falsey end end describe "with mx validation and dns not hitting timeout" do it "is valid if mx records are found" do user = TestUserMXDnsTimeout.new(email: "foo@gmail.com") expect(user.valid?).to be_truthy end it "is valid if A records are found" do user = TestUserMXDnsTimeout.new(email: "foo@ghs.google.com") expect(user.valid?).to be_truthy end it "is invalid if no mx records are found" do user = TestUserMXDnsTimeout.new(email: "foo@subdomain.gmail.com") expect(user.valid?).to be_falsey end it "is invalid if a null mx is found" do user = TestUserMXDnsTimeout.new(email: "foo@gmail.de") expect(user.valid?).to be_falsey end end describe "with mx validation and dns hitting timeout" do it "is never valid even if mx records exist" do user = TestUserMXDnsFailingTimeout.new(email: "foo@gmail.com") expect(user.valid?).to be_falsey end it "is never valid even A records exist" do user = TestUserMXDnsFailingTimeout.new(email: "foo@ghs.google.com") expect(user.valid?).to be_falsey end it "is invalid if no mx records exist" do user = TestUserMXDnsFailingTimeout.new(email: "foo@subdomain.gmail.com") expect(user.valid?).to be_falsey end it "is invalid if a null mx exists" do user = TestUserMXDnsFailingTimeout.new(email: "foo@gmail.de") expect(user.valid?).to be_falsey end end describe "with mx validation and dns nameserver" do it "is valid if mx records are found" do user = TestUserMXDnsNameserver.new(email: "foo@gmail.com") expect(user.valid?).to be_truthy end it "is valid if A records are found" do user = TestUserMXDnsNameserver.new(email: "foo@ghs.google.com") expect(user.valid?).to be_truthy end it "is invalid if no mx records are found" do user = TestUserMXDnsNameserver.new(email: "foo@subdomain.gmail.com") expect(user.valid?).to be_falsey end it "is invalid if a null mx is found" do user = TestUserMXDnsNameserver.new(email: "foo@gmail.de") expect(user.valid?).to be_falsey end end describe "with mx validation and failing dns nameserver" do it "is never valid even if mx records exist" do user = TestUserMXDnsFailingNameserver.new(email: "foo@gmail.com") expect(user.valid?).to be_falsey end it "is never valid even A records exist" do user = TestUserMXDnsFailingNameserver.new(email: "foo@ghs.google.com") expect(user.valid?).to be_falsey end it "is invalid if no mx records exist" do user = TestUserMXDnsFailingNameserver.new(email: "foo@subdomain.gmail.com") expect(user.valid?).to be_falsey end it "is invalid if a null mx exists" do user = TestUserMXDnsFailingNameserver.new(email: "foo@gmail.de") expect(user.valid?).to be_falsey end end describe "with dotted validation" do it "is valid when address does not contain dots" do user = TestUserDotted.new(email: "johndoe@gmail.com") expect(user.valid?).to be_truthy end it "is invalid when address cotains dots" do user = TestUserDotted.new(email: "john.doe@gmail.com") expect(user.valid?).to be_falsey end end describe "with subaddress validation" do it "is valid when address does not contain subaddress" do user = TestUserSubaddressing.new(email: "foo@gmail.com") expect(user.valid?).to be_truthy end it "is invalid when address cotains subaddress" do user = TestUserSubaddressing.new(email: "foo+1@gmail.com") expect(user.valid?).to be_falsey end end context "when message is present" do describe "with custom error message" do it "supports settings a custom error message" do user = TestUserMessage.new(email: "fakeemail") user.valid? expect(user.errors.full_messages).to include("Email custom message") end end describe "with custom error message lambda" do it "supports settings a custom error message lambda" do user = TestUserMessageWithLambda.new(email: "fakeemail") user.valid? expect(user.errors.full_messages).to include("Email custom message lambda") end end describe "with custom error message proc" do it "supports settings a custom error message proc" do user = TestUserMessageWithProc.new(email: "fakeemail") user.valid? expect(user.errors.full_messages).to include("Email custom message proc") end end end describe "with multiple addresses" do it "tests each address for it's own" do user = TestUserMultiple.new(email: "foo@gmail.com, bar@gmail.com") expect(user.valid?).to be_truthy end it "tests each address from an array" do user = TestUserMultiple.new(email: %w[foo@gmail.com bar@gmail.com]) expect(user.valid?).to be_truthy end context 'when one address is invalid' do it "fails for all" do user = TestUserMultiple.new(email: "foo@gmail.com, bar@123") expect(user.valid?).to be_falsey end end end describe "#dotted?" do it "is true when address local part contains a dot delimiter ('.')" do email = ValidEmail2::Address.new("john.doe@gmail.com") expect(email.dotted?).to be_truthy end it "is false when address local part contains a dot delimiter ('.')" do email = ValidEmail2::Address.new("johndoe@gmail.com") expect(email.dotted?).to be_falsey end end describe "#subaddressed?" do it "is true when address local part contains a recipient delimiter ('+')" do email = ValidEmail2::Address.new("foo+1@gmail.com") expect(email.subaddressed?).to be_truthy end it "is false when address local part contains a recipient delimiter ('+')" do email = ValidEmail2::Address.new("foo@gmail.com") expect(email.subaddressed?).to be_falsey end end end