heycarsten-email-veracity 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +72 -0
- data/Rakefile +93 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/email_veracity.rb +56 -0
- data/lib/email_veracity/address.rb +41 -0
- data/lib/email_veracity/config.rb +56 -0
- data/lib/email_veracity/core_extensions.rb +27 -0
- data/lib/email_veracity/domain.rb +69 -0
- data/lib/email_veracity/errors.rb +9 -0
- data/lib/email_veracity/resolver.rb +46 -0
- data/lib/email_veracity/server.rb +20 -0
- data/lib/email_veracity/validatability.rb +34 -0
- data/test/mocks/class_with_validation_mock.rb +16 -0
- data/test/test_helper.rb +33 -0
- data/test/unit/address_test.rb +49 -0
- data/test/unit/config_test.rb +105 -0
- data/test/unit/core_extensions_test.rb +28 -0
- data/test/unit/domain_test.rb +73 -0
- data/test/unit/resolver_test.rb +25 -0
- data/test/unit/server_test.rb +30 -0
- data/test/unit/validatability_test.rb +36 -0
- metadata +78 -0
data/README.rdoc
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= Email Veracity
|
2
|
+
|
3
|
+
A straight-forward Ruby library for checking the real-world validity of email
|
4
|
+
addresses.
|
5
|
+
|
6
|
+
|
7
|
+
=== It Can
|
8
|
+
|
9
|
+
* Validate email addresses for proper form against a pattern.
|
10
|
+
* Accept and reject addresses based whitelists and blacklists of domains.
|
11
|
+
* Check an address' domain for MX and/or A records.
|
12
|
+
* Be configured for a variety of use-cases, to be as discerning or as
|
13
|
+
indiscriminate as you would like.
|
14
|
+
|
15
|
+
|
16
|
+
=== It Can Not
|
17
|
+
|
18
|
+
* Validate all possible permutations of addresses to the RFC 2822
|
19
|
+
specification.
|
20
|
+
|
21
|
+
|
22
|
+
== Using The Gem
|
23
|
+
|
24
|
+
In your project, you must require +email_veracity+ after that you can try it
|
25
|
+
out, consider the following examples:
|
26
|
+
|
27
|
+
|
28
|
+
require 'email_veracity'
|
29
|
+
|
30
|
+
address = EmailVeracity::Address.new('heycarsten@gmail.com')
|
31
|
+
|
32
|
+
address.valid?
|
33
|
+
# => true
|
34
|
+
|
35
|
+
address.domain.to_s
|
36
|
+
# => 'gmail.com'
|
37
|
+
|
38
|
+
address.domain.address_servers.collect { |s| s.to_s }
|
39
|
+
# => ["64.233.171.83", "64.233.161.83", "209.85.171.83"]
|
40
|
+
|
41
|
+
address = EmailVeracity::Address.new('fakey@crazy-z3d9df-domain.com')
|
42
|
+
|
43
|
+
address.valid?
|
44
|
+
# => false
|
45
|
+
|
46
|
+
address.errors
|
47
|
+
# => [:no_address_servers]
|
48
|
+
|
49
|
+
|
50
|
+
As you can see, playing with the core library is pretty fun. The basic building
|
51
|
+
blocks are:
|
52
|
+
|
53
|
+
==== Address
|
54
|
+
|
55
|
+
Responsible for parsing full email addresses and checking for pattern-based
|
56
|
+
validity.
|
57
|
+
|
58
|
+
==== Domain
|
59
|
+
|
60
|
+
Contains methods to query the domain for information.
|
61
|
+
|
62
|
+
==== Resolver
|
63
|
+
|
64
|
+
Abstracts Resolv::DNS into a super-simple single public method, this is where
|
65
|
+
the timeout error is raised.
|
66
|
+
|
67
|
+
|
68
|
+
== Running The Tests
|
69
|
+
|
70
|
+
A few of the tests will fail if you don't have a live connection to the
|
71
|
+
internet, this is obviously not cool and it's high on my list of TODOs. [See
|
72
|
+
TODO]
|
data/Rakefile
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# I used Haml as a template for these tasks: Thank you Nex3!
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
|
5
|
+
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
require 'rake/testtask'
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
### Packaging
|
19
|
+
require 'rake/gempackagetask'
|
20
|
+
load 'email_veracity.gemspec'
|
21
|
+
Rake::GemPackageTask.new(EMAIL_VERACITY_GEMSPEC) do |pkg|
|
22
|
+
if Rake.application.top_level_tasks.include?('release')
|
23
|
+
pkg.need_tar_gz = true
|
24
|
+
pkg.need_tar_bz2 = true
|
25
|
+
pkg.need_zip = true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
task :revision_file do
|
31
|
+
require 'lib/email_veracity'
|
32
|
+
|
33
|
+
if EmailVeracity.version[:rev] && !Rake.application.top_level_tasks.include?('release')
|
34
|
+
File.open('REVISION', 'w') { |f| f.puts EmailVeracity.version[:rev] }
|
35
|
+
elsif Rake.application.top_level_tasks.include?('release')
|
36
|
+
File.open('REVISION', 'w') { |f| f.puts '(release)' }
|
37
|
+
else
|
38
|
+
File.open('REVISION', 'w') { |f| f.puts '(unknown)' }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
Rake::Task[:package].prerequisites.insert(0, :revision_file)
|
42
|
+
|
43
|
+
# We also need to get rid of this file after packaging.
|
44
|
+
at_exit { File.delete('REVISION') rescue nil }
|
45
|
+
|
46
|
+
|
47
|
+
task :install => [:package] do
|
48
|
+
sudo = RUBY_PLATFORM =~ /win32/ ? '' : 'sudo'
|
49
|
+
`#{sudo} gem install --no-ri pkg/email-veracity-#{File.read('VERSION').strip}`
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
task :release => [:package] do
|
54
|
+
name, version = ENV['NAME'], ENV['VERSION']
|
55
|
+
raise "Must supply NAME and VERSION for release task." unless name && version
|
56
|
+
`rubyforge login`
|
57
|
+
`rubyforge add_release email-veracity email-veracity "#{name} (v#{version})" pkg/email-veracity-#{version}.gem`
|
58
|
+
`rubyforge add_file email-veracity email-veracity "#{name} (v#{version})" pkg/email-veracity-#{version}.tar.gz`
|
59
|
+
`rubyforge add_file email-veracity email-veracity "#{name} (v#{version})" pkg/email-veracity-#{version}.tar.bz2`
|
60
|
+
`rubyforge add_file email-veracity email-veracity "#{name} (v#{version})" pkg/email-veracity-#{version}.zip`
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
### Documentation
|
66
|
+
require 'rake/rdoctask'
|
67
|
+
|
68
|
+
Rake::RDocTask.new do |rdoc|
|
69
|
+
rdoc.title = 'Email Veracity'
|
70
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
71
|
+
rdoc.rdoc_files.include(*FileList.new('*') do |list|
|
72
|
+
list.exclude(/(^|[^.a-z])[a-z]+/)
|
73
|
+
list.exclude('TODO')
|
74
|
+
end.to_a)
|
75
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
76
|
+
rdoc.rdoc_files.exclude('TODO')
|
77
|
+
rdoc.rdoc_dir = 'rdoc'
|
78
|
+
rdoc.main = 'README.rdoc'
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
### Coverage
|
84
|
+
require 'rcov/rcovtask'
|
85
|
+
|
86
|
+
Rcov::RcovTask.new do |t|
|
87
|
+
t.test_files = FileList['test/**/*_test.rb']
|
88
|
+
t.rcov_opts << '-x' << '"^\/"'
|
89
|
+
if ENV['NON_NATIVE']
|
90
|
+
t.rcov_opts << "--no-rcovrt"
|
91
|
+
end
|
92
|
+
t.verbose = true
|
93
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'email_veracity'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
3
|
+
|
4
|
+
require 'resolv'
|
5
|
+
require 'timeout'
|
6
|
+
require 'email_veracity/core_extensions'
|
7
|
+
require 'email_veracity/validatability'
|
8
|
+
require 'email_veracity/errors'
|
9
|
+
require 'email_veracity/config'
|
10
|
+
require 'email_veracity/server'
|
11
|
+
require 'email_veracity/resolver'
|
12
|
+
require 'email_veracity/domain'
|
13
|
+
require 'email_veracity/address'
|
14
|
+
|
15
|
+
|
16
|
+
module EmailVeracity
|
17
|
+
|
18
|
+
def self.version # :nodoc:
|
19
|
+
return @@version if defined?(@@version)
|
20
|
+
|
21
|
+
numbers = File.read(scope('VERSION')).strip.split('.').map { |n| n.to_i }
|
22
|
+
|
23
|
+
@@version = {
|
24
|
+
:major => numbers[0],
|
25
|
+
:minor => numbers[1],
|
26
|
+
:teeny => numbers[2] }
|
27
|
+
|
28
|
+
@@version[:string] = [:major, :minor, :teeny].map { |comp| @@version[comp] }.compact.join('.')
|
29
|
+
|
30
|
+
if File.exists?(scope('REVISION'))
|
31
|
+
rev = File.read(scope('REVISION')).strip
|
32
|
+
rev = nil if rev !~ /[a-f0-9]+/
|
33
|
+
end
|
34
|
+
|
35
|
+
if rev.nil? && File.exists?(scope('.git/HEAD'))
|
36
|
+
rev = File.read(scope('.git/HEAD')).strip
|
37
|
+
if rev =~ /^ref: (.*)$/
|
38
|
+
rev = File.read(scope(".git/#{$1}")).strip
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if rev
|
43
|
+
@@version[:rev] = rev
|
44
|
+
@@version[:string] << "." << rev[0...7]
|
45
|
+
end
|
46
|
+
|
47
|
+
@@version
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.scope(file) # :nodoc:
|
51
|
+
File.join(File.dirname(__FILE__), '..', file)
|
52
|
+
end
|
53
|
+
|
54
|
+
VERSION = version[:string] unless defined?(EmailVeracity::VERSION)
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module EmailVeracity
|
2
|
+
|
3
|
+
|
4
|
+
class Address
|
5
|
+
|
6
|
+
include Validatability
|
7
|
+
|
8
|
+
attr_reader :domain
|
9
|
+
|
10
|
+
def initialize(email = '')
|
11
|
+
self.email_address = email
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
email_address
|
16
|
+
end
|
17
|
+
|
18
|
+
def email_address
|
19
|
+
@email_address.to_s.strip
|
20
|
+
end
|
21
|
+
|
22
|
+
def email_address=(new_email_address)
|
23
|
+
@email_address = new_email_address
|
24
|
+
@domain = Domain.new(@email_address.split('@')[1] || '')
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
def validate!
|
29
|
+
add_error(:malformed) if !pattern_valid?
|
30
|
+
return unless Config[:lookup]
|
31
|
+
add_errors(domain.errors)
|
32
|
+
end
|
33
|
+
|
34
|
+
def pattern_valid?
|
35
|
+
@email_address =~ Config[:valid_pattern]
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module EmailVeracity
|
2
|
+
|
3
|
+
|
4
|
+
class Config
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
:whitelist => %w[ aol.com gmail.com hotmail.com mac.com msn.com
|
8
|
+
rogers.com sympatico.ca yahoo.com telus.com sprint.com sprint.ca ],
|
9
|
+
:blacklist => %w[ dodgeit.com mintemail.com mintemail.uni.cc
|
10
|
+
1mintemail.mooo.com spammotel.com trashmail.net ],
|
11
|
+
:valid_pattern => /\A(([\d\w\+_\-\.]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})){1}\Z/i,
|
12
|
+
:timeout => 2,
|
13
|
+
:lookup => [:a],
|
14
|
+
:enforce_blacklist => false,
|
15
|
+
:enforce_whitelist => true }
|
16
|
+
@options = DEFAULT_OPTIONS.clone
|
17
|
+
|
18
|
+
def Config.[](key)
|
19
|
+
@options[key.to_sym]
|
20
|
+
end
|
21
|
+
|
22
|
+
def Config.[]=(key, value)
|
23
|
+
@options[key.to_sym] = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def Config.options
|
27
|
+
@options
|
28
|
+
end
|
29
|
+
|
30
|
+
def Config.options=(options)
|
31
|
+
unless options.is_a?(Hash)
|
32
|
+
raise ArgumentError, "options must be a Hash not #{options.class}"
|
33
|
+
end
|
34
|
+
@options = options
|
35
|
+
end
|
36
|
+
|
37
|
+
def Config.update(options)
|
38
|
+
unless options.is_a?(Hash)
|
39
|
+
raise ArgumentError, "options must be a Hash not #{options.class}"
|
40
|
+
end
|
41
|
+
@options.update(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def Config.enforce_lookup?(record)
|
45
|
+
return unless @options[:lookup].is_a?(Array)
|
46
|
+
@options[:lookup].any? { |i| i.to_s == record.to_s }
|
47
|
+
end
|
48
|
+
|
49
|
+
def Config.revert!
|
50
|
+
@options = DEFAULT_OPTIONS.clone
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module EmailVeracity
|
2
|
+
|
3
|
+
|
4
|
+
class ::Array
|
5
|
+
|
6
|
+
def reject_blank_items
|
7
|
+
reject { |i| i.to_s.strip.blank? }
|
8
|
+
end
|
9
|
+
|
10
|
+
def contains_single_item?
|
11
|
+
size == 1
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class ::Object
|
18
|
+
|
19
|
+
# Snaked from Rails.
|
20
|
+
def blank?
|
21
|
+
respond_to?(:empty?) ? empty? : !self
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module EmailVeracity
|
2
|
+
|
3
|
+
|
4
|
+
class Domain
|
5
|
+
|
6
|
+
include Validatability
|
7
|
+
|
8
|
+
def Domain.whitelisted?(name)
|
9
|
+
Config[:whitelist].include?(name.downcase.strip)
|
10
|
+
end
|
11
|
+
|
12
|
+
def Domain.blacklisted?(name)
|
13
|
+
Config[:blacklist].include?(name.downcase.strip)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(name = '')
|
17
|
+
@name = name
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
name
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
@name.to_s.downcase.strip
|
26
|
+
end
|
27
|
+
|
28
|
+
def whitelisted?
|
29
|
+
Domain.whitelisted?(name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def blacklisted?
|
33
|
+
Domain.blacklisted?(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def address_servers
|
37
|
+
@address_servers ||= servers_in(:a)
|
38
|
+
end
|
39
|
+
|
40
|
+
def exchange_servers
|
41
|
+
@exchange_servers ||= servers_in(:mx)
|
42
|
+
end
|
43
|
+
|
44
|
+
def servers
|
45
|
+
address_servers + exchange_servers
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
def validate!
|
50
|
+
return if whitelisted?
|
51
|
+
add_error(:blacklisted) if blacklisted? &&
|
52
|
+
Config[:enforce_blacklist]
|
53
|
+
add_error(:no_address_servers) if address_servers.empty? &&
|
54
|
+
Config.enforce_lookup?(:a)
|
55
|
+
add_error(:no_exchange_servers) if exchange_servers.empty? &&
|
56
|
+
Config.enforce_lookup?(:mx)
|
57
|
+
end
|
58
|
+
|
59
|
+
def servers_in(record)
|
60
|
+
return [] if name.blank?
|
61
|
+
Resolver.get_servers_for(name, record)
|
62
|
+
rescue DomainResourcesTimeoutError
|
63
|
+
add_error :timed_out
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module EmailVeracity
|
2
|
+
|
3
|
+
|
4
|
+
class Resolver
|
5
|
+
|
6
|
+
RECORD_NAMES_TO_RESOLVE_MAP = {
|
7
|
+
:a => {
|
8
|
+
:method => 'address',
|
9
|
+
:type => AddressServer,
|
10
|
+
:constant => Resolv::DNS::Resource::IN::A },
|
11
|
+
:mx => {
|
12
|
+
:method => 'exchange',
|
13
|
+
:type => ExchangeServer,
|
14
|
+
:constant => Resolv::DNS::Resource::IN::MX } }
|
15
|
+
|
16
|
+
def Resolver.get_servers_for(domain_name, record = :a)
|
17
|
+
Timeout::timeout(Config[:timeout]) do
|
18
|
+
get_resources_for(domain_name, record).collect do |server_name|
|
19
|
+
type = RECORD_NAMES_TO_RESOLVE_MAP[record.to_sym][:type]
|
20
|
+
type.new(server_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
rescue Timeout::Error
|
24
|
+
raise DomainResourcesTimeoutError,
|
25
|
+
"Timed out while try to resolve #{domain_name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def Resolver.get_resources_for(domain_name, record = :a)
|
30
|
+
Resolv::DNS.open do |server|
|
31
|
+
record_map = RECORD_NAMES_TO_RESOLVE_MAP[record]
|
32
|
+
resources = server.getresources(domain_name, record_map[:constant])
|
33
|
+
resources_to_servers(resources, record_map[:method])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def Resolver.resources_to_servers(resources, resolve_method)
|
38
|
+
resources.inject([]) do |array, resource|
|
39
|
+
array << resource.method(resolve_method).call.to_s.strip
|
40
|
+
end.reject_blank_items
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module EmailVeracity
|
2
|
+
|
3
|
+
|
4
|
+
class Server
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
alias_method :to_s, :name
|
8
|
+
|
9
|
+
def initialize(name = '')
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
class AddressServer < Server; end
|
17
|
+
class ExchangeServer < Server; end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module EmailVeracity
|
2
|
+
|
3
|
+
|
4
|
+
module Validatability
|
5
|
+
|
6
|
+
def valid?
|
7
|
+
self.errors.empty?
|
8
|
+
end
|
9
|
+
|
10
|
+
def errors
|
11
|
+
self.clear_errors!
|
12
|
+
self.validate!
|
13
|
+
@errors
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
def validate!
|
18
|
+
# Adds errors to the object.
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear_errors!
|
22
|
+
@errors = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_error(*new_errors)
|
26
|
+
@errors ||= []
|
27
|
+
@errors.concat(new_errors.flatten)
|
28
|
+
end
|
29
|
+
alias_method :add_errors, :add_error
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class ClassWithValidationMock
|
2
|
+
|
3
|
+
include EmailVeracity::Validatability
|
4
|
+
|
5
|
+
attr_accessor :give_error
|
6
|
+
attr_accessor :give_errors
|
7
|
+
attr_accessor :give_array_of_errors
|
8
|
+
|
9
|
+
def validate!
|
10
|
+
add_error(:one) if give_error
|
11
|
+
add_errors(:two, :three) if give_errors
|
12
|
+
add_errors([:four, :five]) if give_array_of_errors
|
13
|
+
add_errors []
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'lib/email_veracity'
|
3
|
+
Dir.glob('test/mocks/*.rb') { |f| require(f) }
|
4
|
+
|
5
|
+
|
6
|
+
class Test::Unit::TestCase
|
7
|
+
|
8
|
+
def domain_names
|
9
|
+
%w[ viarails.net heycarsten.com yahoo.com gmail.com savvica.com
|
10
|
+
learnhub.com github.com google.com rogers.com amd.com adobe.com
|
11
|
+
unspace.ca xerox.com webkit.org cooltown.net aiderss.com
|
12
|
+
del.icio.us ]
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_empty(array, message = nil)
|
16
|
+
unless array.is_a?(Array)
|
17
|
+
raise ArgumentError, 'First argument must be an Array'
|
18
|
+
end
|
19
|
+
message = [message, "Expected #{array.inspect} to be empty."].
|
20
|
+
flatten.join("\n")
|
21
|
+
assert_block(message) { array.empty? }
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_not_empty(array, message = nil)
|
25
|
+
unless array.is_a?(Array)
|
26
|
+
raise ArgumentError, 'First argument must be an Array'
|
27
|
+
end
|
28
|
+
message = [message, "Expected #{array.inspect} to contain items."].
|
29
|
+
flatten.join("\n")
|
30
|
+
assert_block(message) { !array.empty? }
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class AddressTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_initialize
|
7
|
+
assert_instance_of EmailVeracity::Address,
|
8
|
+
EmailVeracity::Address.new('heycarsten@gmail.com'),
|
9
|
+
'Should create a new Address object.'
|
10
|
+
assert_instance_of Array,
|
11
|
+
EmailVeracity::Address.new('heycarsten@gmail.com').errors,
|
12
|
+
'Should errors should be an array.'
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
class DefaultConfigurationAddressValidationsTest < Test::Unit::TestCase
|
19
|
+
|
20
|
+
def test_a_well_formed_address_with_a_whitelisted_domain
|
21
|
+
address = new_address('heycarsten@gmail.com')
|
22
|
+
assert address.valid?,
|
23
|
+
"Should be valid. @errors: #{address.errors.inspect}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_a_well_formed_address_with_a_blacklisted_domain
|
27
|
+
address = new_address('heycarsten@dodgeit.com')
|
28
|
+
assert address.valid?,
|
29
|
+
"Should be valid. @errors: #{address.errors.inspect}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_a_well_formed_address_that_does_not_exist
|
33
|
+
address = new_address('heycarsten@i-surely-do-not-exist.nil')
|
34
|
+
assert !address.valid?,
|
35
|
+
'Should not be valid.'
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_a_well_formed_address_that_exists
|
39
|
+
address = new_address('itsme@heycarsten.com')
|
40
|
+
assert address.valid?,
|
41
|
+
"Should be valid. @errors: #{address.errors.inspect}"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def new_address(address = '')
|
46
|
+
EmailVeracity::Address.new(address)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class ConfigTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_default_whitelist_domains
|
7
|
+
assert_instance_of Array, EmailVeracity::Config[:whitelist]
|
8
|
+
assert_not_empty EmailVeracity::Config[:whitelist],
|
9
|
+
'Should have more than one item.'
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_default_blacklist_domains
|
13
|
+
assert_instance_of Array, EmailVeracity::Config[:blacklist]
|
14
|
+
assert_not_empty EmailVeracity::Config[:blacklist],
|
15
|
+
'Should have more than one item.'
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_lookup_default_setting
|
19
|
+
assert_instance_of Array, EmailVeracity::Config[:lookup]
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_enforce_lookup_with_symbols
|
23
|
+
assert EmailVeracity::Config.enforce_lookup?(:a),
|
24
|
+
'Should check for A records by default'
|
25
|
+
assert !EmailVeracity::Config.enforce_lookup?(:mx),
|
26
|
+
'Should not check for MX records be default'
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_enforce_lookup_with_strings
|
30
|
+
assert EmailVeracity::Config.enforce_lookup?('a'),
|
31
|
+
'Should check for A records by default'
|
32
|
+
assert !EmailVeracity::Config.enforce_lookup?('mx'),
|
33
|
+
'Should not check for MX records be default'
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_changing_and_reverting_configuration
|
37
|
+
EmailVeracity::Config.update(:lookup => false, :timeout => 3)
|
38
|
+
assert_equal false, EmailVeracity::Config[:lookup],
|
39
|
+
'Should change configuration.'
|
40
|
+
assert_equal 3, EmailVeracity::Config[:timeout]
|
41
|
+
'Should change configuration.'
|
42
|
+
EmailVeracity::Config.revert!
|
43
|
+
assert_equal EmailVeracity::Config::DEFAULT_OPTIONS,
|
44
|
+
EmailVeracity::Config.options,
|
45
|
+
'Should revert configuration'
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class DefaultValidAddressPatternTest < Test::Unit::TestCase
|
52
|
+
|
53
|
+
def test_default_valid_address_pattern
|
54
|
+
assert_instance_of Regexp, EmailVeracity::Config[:valid_pattern],
|
55
|
+
'Should exist as a regular expression.'
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_valid_email_addresses
|
59
|
+
%w[
|
60
|
+
goto@rubyfringe.ca
|
61
|
+
stuff+heyd00d@gmail.com
|
62
|
+
carsten_nielsen@gmail.com
|
63
|
+
carsten-nielsen@gmail.com
|
64
|
+
iwenttowestern@ivyleague.ca
|
65
|
+
old-skool@mail.mysite.on.ca
|
66
|
+
heycarsten@del.icio.us
|
67
|
+
nex3@haml.town
|
68
|
+
1234@aplace.com
|
69
|
+
carsten2@happyland.net
|
70
|
+
sweetCandy4@me-and-you.ca
|
71
|
+
jesus@god.com
|
72
|
+
neat@b.eat
|
73
|
+
].each do |address|
|
74
|
+
assert_match EmailVeracity::Config[:valid_pattern],
|
75
|
+
address,
|
76
|
+
"#{address} should be valid."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_invalid_email_addresses
|
81
|
+
%w[
|
82
|
+
@failure.net
|
83
|
+
craptastic@
|
84
|
+
!!!!!@gmail.com
|
85
|
+
oh-noez@f4iL/\/\@il.net
|
86
|
+
someone@somewhere
|
87
|
+
this!fails@comtown.com
|
88
|
+
$oWrong@fail.net
|
89
|
+
charles\ babbage@gmail.com
|
90
|
+
,@crap.com
|
91
|
+
dis%20blos@dot.com
|
92
|
+
&^%$#$%@yojimbo.nil
|
93
|
+
"greetings\ friend"@comtastic.dk
|
94
|
+
this,fails@ice-t.com
|
95
|
+
ungültige@adresse.de
|
96
|
+
failure@10.0.0.1
|
97
|
+
douche@@bag.net
|
98
|
+
].each do |address|
|
99
|
+
assert_no_match EmailVeracity::Config[:valid_pattern],
|
100
|
+
address,
|
101
|
+
"#{address} should be invalid."
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class ArrayExtensionsTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_reject_blank_items
|
7
|
+
array = [[], {}, '', nil, ' ', 'good times!']
|
8
|
+
assert_equal ['good times!'], array.reject_blank_items, 'Should reject all blank and empty objects.'
|
9
|
+
array = [{:neat => 'o'}, ['cool'], 1, 's']
|
10
|
+
assert_equal array, array.reject_blank_items, 'Should not reject any filled or not-blank objects.'
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_contains_single_item
|
14
|
+
assert [''].contains_single_item?, 'Should contain one item.'
|
15
|
+
assert [nil].contains_single_item?, 'Should contain one item.'
|
16
|
+
assert [[]].contains_single_item?, 'Should contain one item.'
|
17
|
+
assert ![].contains_single_item?, 'Should not contain one item.'
|
18
|
+
assert ![nil, false].contains_single_item?, 'Should not contain one item.'
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_blank
|
22
|
+
assert [].blank?, '[] should be blank.'
|
23
|
+
assert ''.blank?, '"" should be blank.'
|
24
|
+
assert Hash.new.blank?, '{} should be blank.'
|
25
|
+
assert nil.blank?, 'nil should be blank.'
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class DomainTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_blacklisted_domain
|
7
|
+
assert EmailVeracity::Domain.blacklisted?('dodgeit.com'),
|
8
|
+
'Should match a blacklisted domain.'
|
9
|
+
assert EmailVeracity::Domain.blacklisted?('DoDgEiT.cOm'),
|
10
|
+
'Should match a blacklisted domain regardless of case.'
|
11
|
+
assert EmailVeracity::Domain.blacklisted?(" dodgeit.com \r\n "),
|
12
|
+
'Should match a blacklisted domain regardless of whitespace.'
|
13
|
+
assert !EmailVeracity::Domain.blacklisted?('iamnotblacklisted.com'),
|
14
|
+
'Should not match a non-blacklisted domain.'
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_whitelisted_domain
|
18
|
+
assert EmailVeracity::Domain.whitelisted?('gmail.com'),
|
19
|
+
'Should match a whitelisted domain.'
|
20
|
+
assert EmailVeracity::Domain.whitelisted?('GmAiL.cOm'),
|
21
|
+
'Should match a whitelisted domain regardless of case.'
|
22
|
+
assert EmailVeracity::Domain.whitelisted?(" gmail.com \r\n "),
|
23
|
+
'Should match a whitelisted domain regardless of whitespace.'
|
24
|
+
assert !EmailVeracity::Domain.whitelisted?('iamnotwhitelisted.com'),
|
25
|
+
'Should not match a non-whitelisted domain.'
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_initializing_a_new_domain_with_whitespace
|
29
|
+
domain = new_domain(' heycarsten.com ')
|
30
|
+
assert_equal 'heycarsten.com', domain.name, 'Should strip whitespace.'
|
31
|
+
assert_respond_to domain, :to_s, 'Should have a to_s method.'
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_a_valid_domain_for_servers
|
35
|
+
domain = new_domain('gmail.com')
|
36
|
+
assert_not_empty domain.exchange_servers, 'Should contain mail servers.'
|
37
|
+
assert_not_empty domain.address_servers, 'Should contain address servers.'
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_a_valid_domain_with_whitespace_for_servers
|
41
|
+
domain = new_domain(' learnhub.com ')
|
42
|
+
assert_not_empty domain.exchange_servers, 'Should contain mail servers.'
|
43
|
+
assert_not_empty domain.address_servers, 'Should contain address servers.'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_an_invalid_domain_for_servers
|
47
|
+
domain = new_domain('i-surely-do-not.exist')
|
48
|
+
assert_empty domain.exchange_servers, 'Should not contain exchange servers.'
|
49
|
+
assert_empty domain.address_servers, 'Should not contain address servers.'
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_a_blank_domain_for_servers
|
53
|
+
domain = new_domain('')
|
54
|
+
assert_empty domain.exchange_servers, 'Should not contain exchange servers.'
|
55
|
+
assert_empty domain.address_servers, 'Should not contain address servers.'
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_for_errors_on_a_valid_domain
|
59
|
+
domain = new_domain('yahoo.com')
|
60
|
+
assert_empty domain.errors, 'Should not have errors.'
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_for_errors_on_an_invalid_domain
|
64
|
+
domain = new_domain('i-surely-do-not.exist')
|
65
|
+
assert_not_empty domain.errors, 'Should have errors.'
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def new_domain(name = 'heycarsten.com')
|
70
|
+
EmailVeracity::Domain.new(name)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class ResolverTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_consecutive_queries
|
7
|
+
EmailVeracity::Config[:timeout] = 60
|
8
|
+
assert_nothing_raised do
|
9
|
+
domain_names.each do |domain|
|
10
|
+
assert_instance_of Array,
|
11
|
+
EmailVeracity::Resolver.get_servers_for(domain),
|
12
|
+
'Should return an array of servers'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_timing_out_while_resolving_a_domain
|
18
|
+
EmailVeracity::Config[:timeout] = 0.001
|
19
|
+
assert_raise EmailVeracity::DomainResourcesTimeoutError, 'Should time out' do
|
20
|
+
EmailVeracity::Resolver.get_servers_for('learnhub.com')
|
21
|
+
end
|
22
|
+
EmailVeracity::Config[:timeout] = 2
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class ServerTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_creating_a_new_blank_server_object
|
7
|
+
new_server = EmailVeracity::Server.new
|
8
|
+
assert_equal '', new_server.to_s,
|
9
|
+
'Should yield a blank string on call to to_s.'
|
10
|
+
assert_equal '', new_server.name,
|
11
|
+
'Should yield a blank string on call to name.'
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_creating_a_new_server_object_with_a_name_and_type
|
15
|
+
name = 'cooltown.ca'
|
16
|
+
new_server = EmailVeracity::Server.new(name)
|
17
|
+
assert_equal name, new_server.name,
|
18
|
+
'Should yield the provided name on call to name.'
|
19
|
+
assert_equal name, new_server.to_s,
|
20
|
+
'Should yield the provided name on call to to_s.'
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_creating_a_new_blank_server_object_and_setting_its_name_after_initialization
|
24
|
+
new_server = EmailVeracity::Server.new('igvita.com')
|
25
|
+
assert_raise NoMethodError, 'Should fail miserably.' do
|
26
|
+
new_server.name = name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class ValidatabilityTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_includes_proper_methods
|
7
|
+
%w[ valid? validate! clear_errors! add_error errors ].each do |method_name|
|
8
|
+
assert_respond_to ClassWithValidationMock.new, method_name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_add_error
|
13
|
+
mock = ClassWithValidationMock.new
|
14
|
+
mock.give_error = true
|
15
|
+
assert_equal 1, mock.errors.size, 'Should set one error.'
|
16
|
+
mock.give_errors = true
|
17
|
+
assert_equal [:one, :two, :three], mock.errors,
|
18
|
+
'Should push tow new errors for a total of three.'
|
19
|
+
mock.give_array_of_errors = true
|
20
|
+
assert_equal [:one, :two, :three, :four, :five], mock.errors,
|
21
|
+
'Should concat the array leaving two new errors for a total of five.'
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_valid?
|
25
|
+
mock = ClassWithValidationMock.new
|
26
|
+
assert mock.valid?, 'Should be valid by default.'
|
27
|
+
mock.give_error = true
|
28
|
+
assert !mock.valid?, 'Should not be valid if errors are set.'
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_errors
|
32
|
+
assert ClassWithValidationMock.new.errors.empty?,
|
33
|
+
'Should be empty by default.'
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heycarsten-email-veracity
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Carsten Nielsen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-07-06 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Email Veracity abstracts an email address into a series of objects which makes it easy to see if an address is invalid, and if so, why.
|
17
|
+
email: heycarsten@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- lib/email_veracity/address.rb
|
26
|
+
- lib/email_veracity/config.rb
|
27
|
+
- lib/email_veracity/core_extensions.rb
|
28
|
+
- lib/email_veracity/domain.rb
|
29
|
+
- lib/email_veracity/errors.rb
|
30
|
+
- lib/email_veracity/resolver.rb
|
31
|
+
- lib/email_veracity/server.rb
|
32
|
+
- lib/email_veracity/validatability.rb
|
33
|
+
- lib/email_veracity.rb
|
34
|
+
- README.rdoc
|
35
|
+
- VERSION
|
36
|
+
- Rakefile
|
37
|
+
- init.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/heycarsten/email-veracity
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --title
|
43
|
+
- Email Veracity
|
44
|
+
- --main
|
45
|
+
- README.rdoc
|
46
|
+
- --line-numbers
|
47
|
+
- --inline-source
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.2.0
|
66
|
+
signing_key:
|
67
|
+
specification_version: 2
|
68
|
+
summary: A simple library for checking the real-world validity of email addresses.
|
69
|
+
test_files:
|
70
|
+
- test/mocks/class_with_validation_mock.rb
|
71
|
+
- test/test_helper.rb
|
72
|
+
- test/unit/address_test.rb
|
73
|
+
- test/unit/config_test.rb
|
74
|
+
- test/unit/core_extensions_test.rb
|
75
|
+
- test/unit/domain_test.rb
|
76
|
+
- test/unit/resolver_test.rb
|
77
|
+
- test/unit/server_test.rb
|
78
|
+
- test/unit/validatability_test.rb
|