valid_email2 7.0.0 → 7.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yaml +5 -2
- data/.github/workflows/release.yaml +2 -0
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +17 -0
- data/.rubocop_todo.yml +263 -0
- data/CHANGELOG.md +78 -0
- data/README.md +11 -0
- data/config/disposable_email_domains.txt +5133 -5677
- data/gemfiles/activemodel8.gemfile +5 -0
- data/lib/valid_email2/address.rb +26 -63
- data/lib/valid_email2/dns.rb +71 -0
- data/lib/valid_email2/email_validator.rb +3 -1
- data/lib/valid_email2/version.rb +1 -1
- data/pull_mailchecker_emails.rb +13 -3
- data/spec/address_spec.rb +128 -42
- data/spec/benchmark_spec.rb +13 -2
- data/spec/dns_spec.rb +21 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/valid_email2_spec.rb +3 -0
- data/valid_email2.gemspec +6 -5
- metadata +38 -8
- data/lib/valid_email2/dns_records_cache.rb +0 -37
data/lib/valid_email2/address.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal:true
|
2
2
|
|
3
3
|
require "valid_email2"
|
4
|
-
require "resolv"
|
5
4
|
require "mail"
|
6
|
-
require "valid_email2/
|
5
|
+
require "valid_email2/dns"
|
7
6
|
|
8
7
|
module ValidEmail2
|
9
8
|
class Address
|
@@ -29,11 +28,10 @@ module ValidEmail2
|
|
29
28
|
@permitted_multibyte_characters_regex = val
|
30
29
|
end
|
31
30
|
|
32
|
-
def initialize(address,
|
31
|
+
def initialize(address, dns = Dns.new)
|
33
32
|
@parse_error = false
|
34
33
|
@raw_address = address
|
35
|
-
@
|
36
|
-
@dns_nameserver = dns_nameserver
|
34
|
+
@dns = dns
|
37
35
|
|
38
36
|
begin
|
39
37
|
@address = Mail::Address.new(address)
|
@@ -84,57 +82,54 @@ module ValidEmail2
|
|
84
82
|
end
|
85
83
|
|
86
84
|
def disposable_domain?
|
87
|
-
domain_is_in?(ValidEmail2.disposable_emails)
|
88
|
-
end
|
89
|
-
|
90
|
-
def disposable_mx_server?
|
91
|
-
valid? && mx_server_is_in?(ValidEmail2.disposable_emails)
|
85
|
+
domain_is_in?(address.domain, ValidEmail2.disposable_emails)
|
92
86
|
end
|
93
87
|
|
94
88
|
def allow_listed?
|
95
|
-
domain_is_in?(ValidEmail2.allow_list)
|
89
|
+
domain_is_in?(address.domain, ValidEmail2.allow_list)
|
96
90
|
end
|
97
91
|
|
98
92
|
def deny_listed?
|
99
|
-
valid? && domain_is_in?(ValidEmail2.deny_list)
|
93
|
+
valid? && domain_is_in?(address.domain, ValidEmail2.deny_list)
|
100
94
|
end
|
101
95
|
|
102
96
|
def valid_mx?
|
103
97
|
return false unless valid?
|
104
98
|
return false if null_mx?
|
105
99
|
|
106
|
-
|
100
|
+
@dns.mx_servers(address.domain).any? || @dns.a_servers(address.domain).any?
|
107
101
|
end
|
108
102
|
|
109
103
|
def valid_strict_mx?
|
110
104
|
return false unless valid?
|
111
105
|
return false if null_mx?
|
112
106
|
|
113
|
-
mx_servers.any?
|
107
|
+
@dns.mx_servers(address.domain).any?
|
114
108
|
end
|
115
109
|
|
116
110
|
private
|
117
111
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
i = address_domain.index('.')
|
123
|
-
return false unless i
|
124
|
-
|
125
|
-
domain_list.include?(address_domain[(i + 1)..-1])
|
112
|
+
def disposable_mx_server?
|
113
|
+
address_domains = @dns.mx_servers(address.domain).map(&:exchange).map(&:to_s)
|
114
|
+
domain_is_in?(address_domains, ValidEmail2.disposable_emails)
|
126
115
|
end
|
127
116
|
|
128
|
-
def
|
129
|
-
|
130
|
-
|
117
|
+
def domain_is_in?(address_domains, domain_list)
|
118
|
+
Array(address_domains).any? do |address_domain|
|
119
|
+
address_domain = address_domain.downcase
|
120
|
+
return true if domain_list.include?(address_domain)
|
131
121
|
|
132
|
-
|
122
|
+
tokens = address_domain.split('.')
|
123
|
+
return false if tokens.length < 3
|
133
124
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
125
|
+
# check only 6 elements deep
|
126
|
+
2.upto(6).each do |depth|
|
127
|
+
limited_sub_domain_part = tokens.reverse.first(depth).reverse.join('.')
|
128
|
+
return true if domain_list.include?(limited_sub_domain_part)
|
129
|
+
end
|
130
|
+
|
131
|
+
false
|
132
|
+
end
|
138
133
|
end
|
139
134
|
|
140
135
|
def address_contain_multibyte_characters?
|
@@ -145,41 +140,9 @@ module ValidEmail2
|
|
145
140
|
@raw_address.each_char.any? { |char| char.bytesize > 1 && char !~ self.class.permitted_multibyte_characters_regex }
|
146
141
|
end
|
147
142
|
|
148
|
-
def resolv_config
|
149
|
-
@resolv_config ||= begin
|
150
|
-
config = Resolv::DNS::Config.default_config_hash
|
151
|
-
config[:nameserver] = @dns_nameserver if @dns_nameserver
|
152
|
-
config
|
153
|
-
end
|
154
|
-
|
155
|
-
@resolv_config
|
156
|
-
end
|
157
|
-
|
158
|
-
def mx_servers
|
159
|
-
@mx_servers_cache ||= ValidEmail2::DnsRecordsCache.new
|
160
|
-
|
161
|
-
@mx_servers_cache.fetch(address.domain.downcase) do
|
162
|
-
Resolv::DNS.open(resolv_config) do |dns|
|
163
|
-
dns.timeouts = @dns_timeout
|
164
|
-
dns.getresources(address.domain, Resolv::DNS::Resource::IN::MX)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
143
|
def null_mx?
|
144
|
+
mx_servers = @dns.mx_servers(address.domain)
|
170
145
|
mx_servers.length == 1 && mx_servers.first.preference == 0 && mx_servers.first.exchange.length == 0
|
171
146
|
end
|
172
|
-
|
173
|
-
def mx_or_a_servers
|
174
|
-
@mx_or_a_servers_cache ||= ValidEmail2::DnsRecordsCache.new
|
175
|
-
|
176
|
-
@mx_or_a_servers_cache.fetch(address.domain.downcase) do
|
177
|
-
Resolv::DNS.open(resolv_config) do |dns|
|
178
|
-
dns.timeouts = @dns_timeout
|
179
|
-
(mx_servers.any? && mx_servers) ||
|
180
|
-
dns.getresources(address.domain, Resolv::DNS::Resource::IN::A)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
147
|
end
|
185
148
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "resolv"
|
2
|
+
|
3
|
+
module ValidEmail2
|
4
|
+
class Dns
|
5
|
+
MAX_CACHE_SIZE = 1_000
|
6
|
+
CACHE = {}
|
7
|
+
|
8
|
+
CacheEntry = Struct.new(:records, :cached_at, :ttl)
|
9
|
+
|
10
|
+
def self.prune_cache
|
11
|
+
entries_sorted_by_cached_at_asc = CACHE.sort_by { |key, data| data.cached_at }
|
12
|
+
entries_to_remove = entries_sorted_by_cached_at_asc.first(CACHE.size - MAX_CACHE_SIZE)
|
13
|
+
entries_to_remove.each { |key, _value| CACHE.delete(key) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.clear_cache
|
17
|
+
CACHE.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(dns_timeout = 5, dns_nameserver = nil)
|
21
|
+
@dns_timeout = dns_timeout
|
22
|
+
@dns_nameserver = dns_nameserver
|
23
|
+
end
|
24
|
+
|
25
|
+
def mx_servers(domain)
|
26
|
+
fetch(domain, Resolv::DNS::Resource::IN::MX)
|
27
|
+
end
|
28
|
+
|
29
|
+
def a_servers(domain)
|
30
|
+
fetch(domain, Resolv::DNS::Resource::IN::A)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def prune_cache
|
36
|
+
self.class.prune_cache
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch(domain, type)
|
40
|
+
prune_cache if CACHE.size > MAX_CACHE_SIZE
|
41
|
+
|
42
|
+
domain = domain.downcase
|
43
|
+
cache_key = [domain, type]
|
44
|
+
cache_entry = CACHE[cache_key]
|
45
|
+
|
46
|
+
if cache_entry && Time.now - cache_entry.cached_at < cache_entry.ttl
|
47
|
+
return cache_entry.records
|
48
|
+
else
|
49
|
+
CACHE.delete(cache_key)
|
50
|
+
end
|
51
|
+
|
52
|
+
records = Resolv::DNS.open(resolv_config) do |dns|
|
53
|
+
dns.timeouts = @dns_timeout
|
54
|
+
dns.getresources(domain, type)
|
55
|
+
end
|
56
|
+
|
57
|
+
if records.any?
|
58
|
+
ttl = records.map(&:ttl).min
|
59
|
+
CACHE[cache_key] = CacheEntry.new(records, Time.now, ttl)
|
60
|
+
end
|
61
|
+
|
62
|
+
records
|
63
|
+
end
|
64
|
+
|
65
|
+
def resolv_config
|
66
|
+
config = Resolv::DNS::Config.default_config_hash
|
67
|
+
config[:nameserver] = @dns_nameserver if @dns_nameserver
|
68
|
+
config
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "valid_email2/address"
|
2
|
+
require "logger" # Fix concurrent-ruby removing logger dependency which Rails itself does not have
|
2
3
|
require "active_model"
|
3
4
|
require "active_model/validations"
|
4
5
|
|
@@ -12,7 +13,8 @@ module ValidEmail2
|
|
12
13
|
return unless value.present?
|
13
14
|
options = default_options.merge(self.options)
|
14
15
|
|
15
|
-
|
16
|
+
dns = ValidEmail2::Dns.new(options[:dns_timeout], options[:dns_nameserver])
|
17
|
+
addresses = sanitized_values(value).map { |v| ValidEmail2::Address.new(v, dns) }
|
16
18
|
|
17
19
|
error(record, attribute) && return unless addresses.all?(&:valid?)
|
18
20
|
|
data/lib/valid_email2/version.rb
CHANGED
data/pull_mailchecker_emails.rb
CHANGED
@@ -29,6 +29,16 @@ remote_emails = [
|
|
29
29
|
resp.body.split("\n").flatten
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
deny_listed_tlds = %w[me ml id]
|
33
|
+
|
34
|
+
result_emails = (existing_emails + remote_emails).map(&:strip) - allow_listed_emails
|
35
|
+
result_emails = result_emails.map do |line|
|
36
|
+
ts = line.chomp.split(".")
|
37
|
+
if ts.last.size == 2 && !deny_listed_tlds.include?(ts.last)
|
38
|
+
ts.last(3).join(".")
|
39
|
+
else
|
40
|
+
ts.last(2).join(".")
|
41
|
+
end
|
42
|
+
end.uniq.sort
|
43
|
+
|
44
|
+
File.open("config/disposable_email_domains.txt", "w") { |f| f.puts(result_emails) }
|
data/spec/address_spec.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
describe ValidEmail2::Address do
|
6
|
+
before do
|
7
|
+
ValidEmail2::Dns.clear_cache
|
8
|
+
end
|
9
|
+
|
6
10
|
describe "#valid?" do
|
7
11
|
it "is valid" do
|
8
12
|
address = described_class.new("foo@bar123.com")
|
@@ -51,18 +55,63 @@ describe ValidEmail2::Address do
|
|
51
55
|
|
52
56
|
it "is valid if it contains special scandinavian characters" do
|
53
57
|
address = described_class.new("jørgen@email.dk")
|
54
|
-
expect(address.valid?).to
|
58
|
+
expect(address.valid?).to be_truthy
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#disposable_domain?" do
|
64
|
+
context "when the disposable domain does not have subdomains" do
|
65
|
+
let(:disposable_domain) { ValidEmail2.disposable_emails.select { |domain| domain.count(".") == 1 }.sample }
|
66
|
+
|
67
|
+
it "is true if the domain is in the disposable_emails list" do
|
68
|
+
address = described_class.new("foo@#{disposable_domain}")
|
69
|
+
expect(address.disposable_domain?).to be_truthy
|
70
|
+
end
|
71
|
+
|
72
|
+
it "is true if the domain is a subdomain of a disposable domain" do
|
73
|
+
address = described_class.new("foo@sub.#{disposable_domain}")
|
74
|
+
expect(address.disposable_domain?).to be_truthy
|
75
|
+
end
|
76
|
+
|
77
|
+
it "is true if the domain is a deeply nested subdomain of a disposable domain" do
|
78
|
+
address = described_class.new("foo@sub3.sub2.sub1.#{disposable_domain}")
|
79
|
+
expect(address.disposable_domain?).to be_truthy
|
80
|
+
end
|
81
|
+
|
82
|
+
it "is false if the domain is not in the disposable_emails list" do
|
83
|
+
address = described_class.new("foo@example.com")
|
84
|
+
expect(address.disposable_domain?).to eq false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when the disposable domain has subdomains" do
|
89
|
+
let(:disposable_domain) { ValidEmail2.disposable_emails.select { |domain| domain.count(".") > 1 }.sample }
|
90
|
+
|
91
|
+
it "is true if the domain is in the disposable_emails list" do
|
92
|
+
address = described_class.new("foo@#{disposable_domain}")
|
93
|
+
expect(address.disposable_domain?).to be_truthy
|
94
|
+
end
|
95
|
+
|
96
|
+
it "is true if the domain is a subdomain of a disposable domain" do
|
97
|
+
address = described_class.new("foo@sub.#{disposable_domain}")
|
98
|
+
expect(address.disposable_domain?).to be_truthy
|
99
|
+
end
|
100
|
+
|
101
|
+
it "is true if the domain is a deeply nested subdomain of a disposable domain" do
|
102
|
+
address = described_class.new("foo@sub3.sub2.sub1.#{disposable_domain}")
|
103
|
+
expect(address.disposable_domain?).to be_truthy
|
55
104
|
end
|
56
105
|
end
|
57
106
|
end
|
58
107
|
|
59
108
|
describe "caching" do
|
60
109
|
let(:email_address) { "example@ymail.com" }
|
61
|
-
let(:
|
62
|
-
let(:
|
110
|
+
let(:dns_instance) { ValidEmail2::Dns.new }
|
111
|
+
let(:email_instance) { described_class.new(email_address, dns_instance) }
|
63
112
|
let(:ttl) { 1_000 }
|
64
113
|
let(:mock_resolv_dns) { instance_double(Resolv::DNS) }
|
65
|
-
let(:mock_mx_records) { [double("MX", exchange: "mx.ymail.com", preference: 10, ttl:
|
114
|
+
let(:mock_mx_records) { [double("MX", exchange: "mx.ymail.com", preference: 10, ttl:)] }
|
66
115
|
|
67
116
|
before do
|
68
117
|
allow(email_instance).to receive(:null_mx?).and_return(false)
|
@@ -70,9 +119,50 @@ describe ValidEmail2::Address do
|
|
70
119
|
allow(mock_resolv_dns).to receive(:timeouts=)
|
71
120
|
end
|
72
121
|
|
122
|
+
describe "#disposable_mx_server?" do
|
123
|
+
let(:disposable_email_address) { "example@10minutemail.com" }
|
124
|
+
let(:disposable_mx_server) { ValidEmail2.disposable_emails.select { |domain| domain.count(".") == 1 }.sample }
|
125
|
+
let(:disposable_email_instance) { described_class.new(disposable_email_address, dns_instance) }
|
126
|
+
let(:mock_disposable_mx_records) { [double("MX", exchange: "mx.#{disposable_mx_server}", preference: 10, ttl:)] }
|
127
|
+
|
128
|
+
before do
|
129
|
+
allow(mock_resolv_dns).to receive(:getresources)
|
130
|
+
.with(disposable_email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
131
|
+
.and_return(mock_disposable_mx_records)
|
132
|
+
|
133
|
+
allow(mock_resolv_dns).to receive(:getresources)
|
134
|
+
.with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
135
|
+
.and_return(mock_mx_records)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "is false if the MX server is not in the disposable_emails list" do
|
139
|
+
expect(email_instance.send(:disposable_mx_server?)).not_to be_truthy
|
140
|
+
end
|
141
|
+
|
142
|
+
it "is true if the MX server is in the disposable_emails list" do
|
143
|
+
expect(disposable_email_instance.send(:disposable_mx_server?)).to be_truthy
|
144
|
+
end
|
145
|
+
|
146
|
+
it "is false and then true when the MX record changes from non-disposable to disposable" do
|
147
|
+
allow(mock_resolv_dns).to receive(:getresources)
|
148
|
+
.with(disposable_email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
149
|
+
.and_return(mock_mx_records) # non-disposable MX records
|
150
|
+
|
151
|
+
expect(disposable_email_instance.send(:disposable_mx_server?)).not_to be_truthy
|
152
|
+
|
153
|
+
ValidEmail2::Dns.clear_cache
|
154
|
+
|
155
|
+
allow(mock_resolv_dns).to receive(:getresources)
|
156
|
+
.with(disposable_email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
157
|
+
.and_return(mock_disposable_mx_records) # disposable MX records
|
158
|
+
|
159
|
+
expect(disposable_email_instance.send(:disposable_mx_server?)).to be_truthy
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
73
163
|
describe "#valid_strict_mx?" do
|
74
164
|
let(:cached_at) { Time.now }
|
75
|
-
let(:mock_cache_data) { { email_instance.address.domain =>
|
165
|
+
let(:mock_cache_data) { { [email_instance.address.domain, Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_mx_records, cached_at, ttl) } }
|
76
166
|
|
77
167
|
before do
|
78
168
|
allow(mock_resolv_dns).to receive(:getresources)
|
@@ -108,9 +198,9 @@ describe ValidEmail2::Address do
|
|
108
198
|
|
109
199
|
describe "ttl" do
|
110
200
|
before do
|
111
|
-
|
112
|
-
allow(ValidEmail2::
|
113
|
-
allow(
|
201
|
+
stub_const("ValidEmail2::Dns::CACHE", mock_cache_data)
|
202
|
+
allow(ValidEmail2::Dns).to receive(:new).and_return(dns_instance)
|
203
|
+
allow(dns_instance).to receive(:fetch).with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX).and_call_original
|
114
204
|
end
|
115
205
|
|
116
206
|
context "when the time since last lookup is less than the cached ttl entry" do
|
@@ -136,18 +226,18 @@ describe ValidEmail2::Address do
|
|
136
226
|
|
137
227
|
describe "cache size" do
|
138
228
|
before do
|
139
|
-
|
140
|
-
allow(ValidEmail2::
|
141
|
-
allow(
|
229
|
+
stub_const("ValidEmail2::Dns::CACHE", mock_cache_data)
|
230
|
+
allow(ValidEmail2::Dns).to receive(:new).and_return(dns_instance)
|
231
|
+
allow(dns_instance).to receive(:fetch).with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX).and_call_original
|
142
232
|
end
|
143
233
|
|
144
234
|
context "when the cache size is less than or equal to the max cache size" do
|
145
235
|
before do
|
146
|
-
stub_const("ValidEmail2::
|
236
|
+
stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 1)
|
147
237
|
end
|
148
238
|
|
149
239
|
it "does not prune the cache" do
|
150
|
-
expect(
|
240
|
+
expect(dns_instance).not_to receive(:prune_cache)
|
151
241
|
|
152
242
|
email_instance.valid_strict_mx?
|
153
243
|
end
|
@@ -159,42 +249,40 @@ describe ValidEmail2::Address do
|
|
159
249
|
end
|
160
250
|
|
161
251
|
context "and there are older cached entries" do
|
162
|
-
let(:mock_cache_data) { { "another_domain.com" =>
|
252
|
+
let(:mock_cache_data) { { ["another_domain.com", Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_mx_records, cached_at - 100, ttl) } }
|
163
253
|
|
164
254
|
it "does not prune those entries" do
|
165
255
|
email_instance.valid_strict_mx?
|
166
256
|
|
167
|
-
expect(
|
168
|
-
expect(dns_records_cache_instance.instance_variable_get(:@cache).keys).to match_array([email_instance.address.domain, "another_domain.com"])
|
257
|
+
expect(ValidEmail2::Dns::CACHE.keys).to match_array([[email_instance.address.domain, Resolv::DNS::Resource::IN::MX], ["another_domain.com", Resolv::DNS::Resource::IN::MX]])
|
169
258
|
end
|
170
259
|
end
|
171
260
|
end
|
172
261
|
|
173
262
|
context "when the cache size is greater than the max cache size" do
|
174
263
|
before do
|
175
|
-
stub_const("ValidEmail2::
|
264
|
+
stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
|
176
265
|
end
|
177
266
|
|
178
267
|
it "prunes the cache" do
|
179
|
-
expect(
|
268
|
+
expect(dns_instance).to receive(:prune_cache).once
|
180
269
|
|
181
270
|
email_instance.valid_strict_mx?
|
182
271
|
end
|
183
272
|
|
184
|
-
it "calls the the MX servers lookup" do
|
273
|
+
it "calls the the MX servers lookup" do
|
185
274
|
email_instance.valid_strict_mx?
|
186
275
|
|
187
276
|
expect(Resolv::DNS).to have_received(:open).once
|
188
277
|
end
|
189
278
|
|
190
279
|
context "and there are older cached entries" do
|
191
|
-
let(:mock_cache_data) { { "another_domain.com" =>
|
280
|
+
let(:mock_cache_data) { { ["another_domain.com", Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_mx_records, cached_at - 100, ttl) } }
|
192
281
|
|
193
282
|
it "prunes those entries" do
|
194
283
|
email_instance.valid_strict_mx?
|
195
284
|
|
196
|
-
expect(
|
197
|
-
expect(dns_records_cache_instance.instance_variable_get(:@cache).keys).to match_array([email_instance.address.domain])
|
285
|
+
expect(ValidEmail2::Dns::CACHE.keys).to match_array([[email_instance.address.domain, Resolv::DNS::Resource::IN::MX]])
|
198
286
|
end
|
199
287
|
end
|
200
288
|
end
|
@@ -203,13 +291,13 @@ describe ValidEmail2::Address do
|
|
203
291
|
|
204
292
|
describe "#valid_mx?" do
|
205
293
|
let(:cached_at) { Time.now }
|
206
|
-
let(:mock_cache_data) { { email_instance.address.domain =>
|
207
|
-
let(:mock_a_records) { [double("A", address: "192.168.1.1", ttl:
|
294
|
+
let(:mock_cache_data) { { [email_instance.address.domain, Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_a_records, cached_at, ttl) } }
|
295
|
+
let(:mock_a_records) { [double("A", address: "192.168.1.1", ttl:)] }
|
208
296
|
|
209
297
|
before do
|
210
298
|
allow(email_instance).to receive(:mx_servers).and_return(mock_mx_records)
|
211
299
|
allow(mock_resolv_dns).to receive(:getresources)
|
212
|
-
.with(email_instance.address.domain, Resolv::DNS::Resource::IN::
|
300
|
+
.with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX)
|
213
301
|
.and_return(mock_a_records)
|
214
302
|
end
|
215
303
|
|
@@ -241,9 +329,9 @@ describe ValidEmail2::Address do
|
|
241
329
|
|
242
330
|
describe "ttl" do
|
243
331
|
before do
|
244
|
-
|
245
|
-
allow(ValidEmail2::
|
246
|
-
allow(
|
332
|
+
stub_const("ValidEmail2::Dns::CACHE", mock_cache_data)
|
333
|
+
allow(ValidEmail2::Dns).to receive(:new).and_return(dns_instance)
|
334
|
+
allow(dns_instance).to receive(:fetch).with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX).and_call_original
|
247
335
|
end
|
248
336
|
|
249
337
|
context "when the time since last lookup is less than the cached ttl entry" do
|
@@ -269,14 +357,14 @@ describe ValidEmail2::Address do
|
|
269
357
|
|
270
358
|
describe "cache size" do
|
271
359
|
before do
|
272
|
-
|
273
|
-
allow(ValidEmail2::
|
274
|
-
allow(
|
360
|
+
stub_const("ValidEmail2::Dns::CACHE", mock_cache_data)
|
361
|
+
allow(ValidEmail2::Dns).to receive(:new).and_return(dns_instance)
|
362
|
+
allow(dns_instance).to receive(:fetch).with(email_instance.address.domain, Resolv::DNS::Resource::IN::MX).and_call_original
|
275
363
|
end
|
276
364
|
|
277
365
|
context "when the cache size is less than or equal to the max cache size" do
|
278
366
|
before do
|
279
|
-
stub_const("ValidEmail2::
|
367
|
+
stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 1)
|
280
368
|
end
|
281
369
|
|
282
370
|
it "does not prune the cache" do
|
@@ -292,24 +380,23 @@ describe ValidEmail2::Address do
|
|
292
380
|
end
|
293
381
|
|
294
382
|
context "and there are older cached entries" do
|
295
|
-
let(:mock_cache_data) { { "another_domain.com" =>
|
383
|
+
let(:mock_cache_data) { { ["another_domain.com", Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_a_records, cached_at - 100, ttl) } }
|
296
384
|
|
297
385
|
it "does not prune those entries" do
|
298
386
|
email_instance.valid_mx?
|
299
387
|
|
300
|
-
expect(
|
301
|
-
expect(dns_records_cache_instance.instance_variable_get(:@cache).keys).to match_array([email_instance.address.domain, "another_domain.com"])
|
388
|
+
expect(ValidEmail2::Dns::CACHE.keys).to match_array([[email_instance.address.domain, Resolv::DNS::Resource::IN::MX], ["another_domain.com", Resolv::DNS::Resource::IN::MX]])
|
302
389
|
end
|
303
390
|
end
|
304
391
|
end
|
305
392
|
|
306
393
|
context "when the cache size is greater than the max cache size" do
|
307
394
|
before do
|
308
|
-
stub_const("ValidEmail2::
|
395
|
+
stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
|
309
396
|
end
|
310
397
|
|
311
|
-
it "prunes the cache" do
|
312
|
-
expect(
|
398
|
+
it "prunes the cache" do
|
399
|
+
expect(dns_instance).to receive(:prune_cache).once
|
313
400
|
|
314
401
|
email_instance.valid_mx?
|
315
402
|
end
|
@@ -321,17 +408,16 @@ describe ValidEmail2::Address do
|
|
321
408
|
end
|
322
409
|
|
323
410
|
context "and there are older cached entries" do
|
324
|
-
let(:mock_cache_data) { { "another_domain.com" =>
|
411
|
+
let(:mock_cache_data) { { ["another_domain.com", Resolv::DNS::Resource::IN::MX] => ValidEmail2::Dns::CacheEntry.new(mock_a_records, cached_at - 100, ttl) } }
|
325
412
|
|
326
413
|
it "prunes those entries" do
|
327
414
|
email_instance.valid_mx?
|
328
415
|
|
329
|
-
expect(
|
330
|
-
expect(dns_records_cache_instance.instance_variable_get(:@cache).keys).to match_array([email_instance.address.domain])
|
416
|
+
expect(ValidEmail2::Dns::CACHE.keys).to match_array([[email_instance.address.domain, Resolv::DNS::Resource::IN::MX]])
|
331
417
|
end
|
332
418
|
end
|
333
419
|
end
|
334
|
-
end
|
420
|
+
end
|
335
421
|
end
|
336
422
|
end
|
337
423
|
end
|
data/spec/benchmark_spec.rb
CHANGED
@@ -5,7 +5,7 @@ require "spec_helper"
|
|
5
5
|
describe "Performance testing" do
|
6
6
|
let(:disposable_domain) { ValidEmail2.disposable_emails.first }
|
7
7
|
|
8
|
-
it "has acceptable lookup performance" do
|
8
|
+
it "disposable_domain? has acceptable lookup performance" do
|
9
9
|
address = ValidEmail2::Address.new("test@example.com")
|
10
10
|
|
11
11
|
# preload list and check size
|
@@ -13,6 +13,17 @@ describe "Performance testing" do
|
|
13
13
|
expect(ValidEmail2.disposable_emails.count).to be > 30000
|
14
14
|
|
15
15
|
# check lookup timing
|
16
|
-
expect { address.disposable_domain? }.to perform_under(0.0001).sample(10).times
|
16
|
+
expect { address.disposable_domain? }.to perform_under(0.0001).sec.sample(10).times
|
17
|
+
end
|
18
|
+
|
19
|
+
it "disposable_mx_server? has acceptable lookup performance" do
|
20
|
+
address = ValidEmail2::Address.new("test@gmail.com")
|
21
|
+
|
22
|
+
# preload list and check size
|
23
|
+
expect(ValidEmail2.disposable_emails).to be_a(Set)
|
24
|
+
expect(ValidEmail2.disposable_emails.count).to be > 30000
|
25
|
+
|
26
|
+
# check lookup timing
|
27
|
+
expect { address.send(:disposable_mx_server?) }.to perform_under(0.00015).sec.sample(10).times
|
17
28
|
end
|
18
29
|
end
|
data/spec/dns_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe ValidEmail2::Dns do
|
6
|
+
describe "#mx_servers" do
|
7
|
+
it "gets a record" do
|
8
|
+
dns = described_class.new
|
9
|
+
records = dns.mx_servers("gmail.com")
|
10
|
+
expect(records.size).to_not be_zero
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#a_servers" do
|
15
|
+
it "gets a record" do
|
16
|
+
dns = described_class.new
|
17
|
+
records = dns.a_servers("gmail.com")
|
18
|
+
expect(records.size).to_not be_zero
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
CHANGED