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.
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activemodel', '~> 8.0'
4
+
5
+ gemspec path: '../'
@@ -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/dns_records_cache"
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, dns_timeout = 5, dns_nameserver = nil)
31
+ def initialize(address, dns = Dns.new)
33
32
  @parse_error = false
34
33
  @raw_address = address
35
- @dns_timeout = dns_timeout
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
- mx_or_a_servers.any?
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 domain_is_in?(domain_list)
119
- address_domain = address.domain.downcase
120
- return true if domain_list.include?(address_domain)
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 mx_server_is_in?(domain_list)
129
- mx_servers.any? { |mx_server|
130
- return false unless mx_server.respond_to?(:exchange)
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
- mx_server = mx_server.exchange.to_s
122
+ tokens = address_domain.split('.')
123
+ return false if tokens.length < 3
133
124
 
134
- domain_list.any? { |domain|
135
- mx_server.end_with?(domain) && mx_server =~ /\A(?:.+\.)*?#{domain}\z/
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
- addresses = sanitized_values(value).map { |v| ValidEmail2::Address.new(v, options[:dns_timeout], options[:dns_nameserver]) }
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal:true
2
2
 
3
3
  module ValidEmail2
4
- VERSION = "7.0.0"
4
+ VERSION = "7.0.11"
5
5
  end
@@ -29,6 +29,16 @@ remote_emails = [
29
29
  resp.body.split("\n").flatten
30
30
  end
31
31
 
32
- result_emails = (existing_emails + remote_emails).map(&:strip).uniq.sort - allow_listed_emails
33
-
34
- File.write("config/disposable_email_domains.txt", result_emails.join("\n"))
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 eq true
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(:email_instance) { described_class.new(email_address) }
62
- let(:dns_records_cache_instance) { ValidEmail2::DnsRecordsCache.new }
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: 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 => { records: mock_mx_records, cached_at: cached_at, ttl: ttl } } }
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
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
112
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
113
- allow(dns_records_cache_instance).to receive(:fetch).with(email_instance.address.domain).and_call_original
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
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
140
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
141
- allow(dns_records_cache_instance).to receive(:fetch).with(email_instance.address.domain).and_call_original
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::DnsRecordsCache::MAX_CACHE_SIZE", 1)
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(dns_records_cache_instance).not_to receive(:prune_cache)
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" => { records: mock_mx_records, cached_at: cached_at - 100, ttl: ttl } } }
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(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 2
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::DnsRecordsCache::MAX_CACHE_SIZE", 0)
264
+ stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
176
265
  end
177
266
 
178
267
  it "prunes the cache" do
179
- expect(dns_records_cache_instance).to receive(:prune_cache).once
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" => { records: mock_mx_records, cached_at: cached_at - 100, ttl: ttl } } }
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(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 1
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 => { records: mock_a_records, cached_at: cached_at, ttl: ttl } } }
207
- let(:mock_a_records) { [double("A", address: "192.168.1.1", ttl: 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::A)
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
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
245
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
246
- allow(dns_records_cache_instance).to receive(:fetch).with(email_instance.address.domain).and_call_original
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
- dns_records_cache_instance.instance_variable_set(:@cache, mock_cache_data)
273
- allow(ValidEmail2::DnsRecordsCache).to receive(:new).and_return(dns_records_cache_instance)
274
- allow(dns_records_cache_instance).to receive(:fetch).with(email_instance.address.domain).and_call_original
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::DnsRecordsCache::MAX_CACHE_SIZE", 1)
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" => { records: mock_a_records, cached_at: cached_at - 100, ttl: ttl } } }
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(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 2
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::DnsRecordsCache::MAX_CACHE_SIZE", 0)
395
+ stub_const("ValidEmail2::Dns::MAX_CACHE_SIZE", 0)
309
396
  end
310
397
 
311
- it "prunes the cache" do
312
- expect(dns_records_cache_instance).to receive(:prune_cache).once
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" => { records: mock_a_records, cached_at: cached_at - 100, ttl: ttl } } }
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(dns_records_cache_instance.instance_variable_get(:@cache).keys.size).to eq 1
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
@@ -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
@@ -1,5 +1,6 @@
1
1
  $:.unshift File.expand_path("../lib",__FILE__)
2
2
  require "valid_email2"
3
+ require "debug"
3
4
 
4
5
  # Include and configure benchmark
5
6
  require 'rspec-benchmark'
@@ -76,6 +76,9 @@ class TestUserMultiple < TestModel
76
76
  end
77
77
 
78
78
  describe ValidEmail2 do
79
+ before do
80
+ ValidEmail2::Dns.clear_cache
81
+ end
79
82
 
80
83
  let(:disposable_domain) { ValidEmail2.disposable_emails.first }
81
84