email_validator 2.0.1 → 2.1.0
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/CHANGELOG.md +81 -0
- data/README.md +177 -20
- data/lib/email_validator.rb +91 -19
- data/spec/email_validator_spec.rb +810 -91
- data/spec/spec_helper.rb +12 -2
- metadata +15 -45
- data/.document +0 -5
- data/.gitignore +0 -22
- data/.travis.yml +0 -12
- data/Changes.md +0 -20
- data/Gemfile +0 -3
- data/Rakefile +0 -10
- data/email_validator.gemspec +0 -23
- data/lib/email_validator/strict.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc66e9e16fb1ba1babe7b102865ba9b112eb373392057313c9e7aeffcef2bf23
|
4
|
+
data.tar.gz: a4a991392b379b60dbf04105469c28424a8c294d62557095e920cee7d2cf8e27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 864e0e44385e3252be96087fd57f76b81e26cfdd4bec1a08a880afaf1335efbba609b7b28ef821cb4df63519776ec95a520d6f6f1910d77b28221cacae8cab28
|
7
|
+
data.tar.gz: bb48dc83509e3a87d581153aa8f3950431a2daf106c4a2bd5be798925a8a3629e054f4397127c748b5f99eba69b55e0084a7b6e0597343d02f3ed617e057f46c
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# CHANGELOG
|
2
|
+
|
3
|
+
This file is used to list changes made in `email_validator`.
|
4
|
+
|
5
|
+
All notable changes to this project will be documented in this file.
|
6
|
+
This project adheres to [Semantic Versioning](http://semver.org/).
|
7
|
+
|
8
|
+
## 2.1.0 (2020-12-09)
|
9
|
+
|
10
|
+
* [karlwilbur] - Add linters and commit hooks to validate code prior to commits
|
11
|
+
* [karlwilbur] - Add `:mode` config option; values `:loose`, `:moderate`, `:strict`; default to `:loose`
|
12
|
+
* [karlwilbur] - Merge in changes from <https://github.com/karlwilbur/email_validator> fork
|
13
|
+
|
14
|
+
## 1.9.0.pre (2020-10-14)
|
15
|
+
|
16
|
+
* [karlwilbur] - Add `require_fqdn` option, require FQDN by default
|
17
|
+
* [karlwilbur] - Add support for IPv4 and IPv6 address hosts
|
18
|
+
* [karlwilbur] - Add Rubocop, `.editorconfig`; code cleanup/linting
|
19
|
+
|
20
|
+
## 1.8.0 (2019-06-14)
|
21
|
+
|
22
|
+
* [karlwilbur] - Refactor class methods for readability
|
23
|
+
* [karlwilbur] - `gemspec` meta updates
|
24
|
+
* [karlwilbur] - Use POSIX classes for better performance
|
25
|
+
* [karlwilbur] - Refactored tests to check specical characters one at a time
|
26
|
+
* [karlwilbur] - Refactored validation regex to be more techincally correct
|
27
|
+
* [karlwilbur] - Add this `CHANGELOG`
|
28
|
+
|
29
|
+
## 1.7.0 (2019-04-20)
|
30
|
+
|
31
|
+
* [karlwilbur] - Added test coverage badge to README
|
32
|
+
* [karlwilbur] - Added I18n directive to remove warning message in testing
|
33
|
+
* [karlwilbur] - Added RFC-2822 reference
|
34
|
+
* [karlwilbur] - Ignore local rspec config file
|
35
|
+
* [karlwilbur] - Check for invalid double dots in strict mode
|
36
|
+
* [karlwilbur] - Updated spec_helper to remove Code Climate Test Reporter; it is to be run separately now
|
37
|
+
* [karlwilbur] - Allow leading/trailing whitespace in normal, not strict
|
38
|
+
* [karlwilbur] - Added `invalid?` as inverse of `valid?`
|
39
|
+
* [karlwilbur] - Add the ability to limit to a domain
|
40
|
+
* [karlwilbur] - Removed CodeShip badge
|
41
|
+
* [karlwilbur] - Make the dot in the domain part non-conditional
|
42
|
+
* [karlwilbur] - Fix domain label pattern to allow numbers per rfc5321
|
43
|
+
|
44
|
+
## 1.6.0 (2015-06-14)
|
45
|
+
|
46
|
+
* [karlwilbur] - Fixed validation to be closer to RFC-5321
|
47
|
+
* [karlwilbur] - Updated specs to use Rspec 3 syntax
|
48
|
+
* [karlwilbur] - Added unicode suport to validation regexp
|
49
|
+
* [karlwilbur] - Added class access to regexp, and `valid?` calss method
|
50
|
+
* [karlwilbur] - Simplified code using new methods
|
51
|
+
* [karlwilbur] - Added CodeClimate and SimpleCov
|
52
|
+
* [karlwilbur] - Updated version and contact info
|
53
|
+
|
54
|
+
*** Forked from <https://github.com/balexand/email_validator>
|
55
|
+
|
56
|
+
## 2.0.1 (2019-03-09)
|
57
|
+
|
58
|
+
* Add email value to error details [f1sherman #50]
|
59
|
+
* CI doesn't test Ruby versions that no longer receive updates [f1sherman #51]
|
60
|
+
|
61
|
+
## 2.0.0 (2019-03-02)
|
62
|
+
|
63
|
+
* Looser validation [#49]
|
64
|
+
|
65
|
+
## 1.6.0 (2015-05-12)
|
66
|
+
|
67
|
+
* Unicode characters support [i7an #24]
|
68
|
+
|
69
|
+
## 1.5.0 (2014-12-08)
|
70
|
+
|
71
|
+
* Add a class method for simpler validation [TiteiKo and cluesque #19]
|
72
|
+
* RSpec 3.0 syntax [strivedi183 #17]
|
73
|
+
* Create Changes.md
|
74
|
+
|
75
|
+
---
|
76
|
+
|
77
|
+
Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax)
|
78
|
+
for help with Markdown.
|
79
|
+
|
80
|
+
The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/)
|
81
|
+
describes the differences between markdown on github and standard markdown.
|
data/README.md
CHANGED
@@ -1,6 +1,29 @@
|
|
1
|
-
|
1
|
+
# EmailValidator
|
2
2
|
|
3
|
-
|
3
|
+
[](http://travis-ci.com/K-and-R/email_validator)
|
4
|
+
[](https://codeclimate.com/github/karlwilbur/email_validator)
|
5
|
+
[](https://codeclimate.com/github/karlwilbur/email_validator/coverage)
|
6
|
+
|
7
|
+
An email validator for Rails 3+.
|
8
|
+
|
9
|
+
Supports RFC-2822-compliant and RFC-5321-compliant email validation.
|
10
|
+
|
11
|
+
Formerly found at: <https://github.com/balexand/email_validator>
|
12
|
+
|
13
|
+
## Validation philosophy
|
14
|
+
|
15
|
+
The default validation provided by this gem (the `:loose` configuration option)
|
16
|
+
is extremely loose. It just checks that there's an `@` with something before and
|
17
|
+
after it without any whitespace. See [this article by David Gilbertson](https://hackernoon.com/the-100-correct-way-to-validate-email-addresses-7c4818f24643)
|
18
|
+
for an explanation of why.
|
19
|
+
|
20
|
+
We understand that maybe use cases require more stricy validation and this is
|
21
|
+
supported by using the `:moderate` validation mode. Additionally, the `:strict`
|
22
|
+
RFC-compliant mode will consider technically valid emails address as valid which
|
23
|
+
may not be wanted, such as the valid `user` or `user@somehost` addresses. These
|
24
|
+
would be valid in `:strict` mode but not valid in `:loose` or `:moderate`.
|
25
|
+
|
26
|
+
## Installation
|
4
27
|
|
5
28
|
Add to your Gemfile:
|
6
29
|
|
@@ -10,47 +33,181 @@ gem 'email_validator'
|
|
10
33
|
|
11
34
|
Run:
|
12
35
|
|
13
|
-
```
|
36
|
+
```bash
|
14
37
|
bundle install
|
15
38
|
```
|
16
39
|
|
17
|
-
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
Add the following to your model:
|
18
43
|
|
19
44
|
```ruby
|
20
45
|
validates :my_email_attribute, email: true
|
21
46
|
```
|
22
47
|
|
48
|
+
You may wish to allow domains without a FDQN, like `user@somehost`. While this
|
49
|
+
is technically a valid address, it is uncommon to consider such address valid.
|
50
|
+
We will consider them valid by default with the `:loose` checking. Disallowed
|
51
|
+
by setting `require_fqdn: true` or by enabling `:moderate` checking:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
validates :my_email_attribute, email: {mode: :moderate, require_fqdn: true}
|
55
|
+
```
|
56
|
+
|
57
|
+
You can also limit to a single domain (e.g: this might help if, for example, you
|
58
|
+
have separate `User` and `AdminUser` models and want to ensure that `AdminUser`
|
59
|
+
emails are on a specific domain):
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
validates :my_email_attribute, email: {domain: 'example.com'}
|
63
|
+
```
|
64
|
+
|
65
|
+
## Configuration
|
66
|
+
|
67
|
+
Default configuration can be overridden by setting options in `config/initializers/email_validator.rb`:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
if defined?(EmailValidator)
|
71
|
+
# To completly override the defaults
|
72
|
+
EmailValidator.default_options = {
|
73
|
+
allow_nil: false,
|
74
|
+
domain: nil,
|
75
|
+
require_fqdn: nil,
|
76
|
+
mode: :loose
|
77
|
+
}
|
78
|
+
|
79
|
+
# or just a few options
|
80
|
+
EmailValidator.default_options.merge!({ domain: 'mydomain.com' })
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
### Loose mode
|
85
|
+
|
86
|
+
This it the default validation mode of this gem. It is intentionally extremely
|
87
|
+
loose (see the [Validation Philosophy section](#validation_philosophy) above. It
|
88
|
+
just checks that there's an `@` with something before and after it without any
|
89
|
+
whitespace. The `:domain` and `:require_fqdn` option are ignored in `:loose` mode.
|
90
|
+
|
91
|
+
### Moderate mode
|
92
|
+
|
93
|
+
Enabling `:moderate` checking will check for a "normal" email format that would
|
94
|
+
be expected in most common everyday usage. Moderate mode basically checks for a
|
95
|
+
properly sized and formatted mailbox label, a single "@" symbol, and a properly
|
96
|
+
sized and formatted FQDN. Enabling `:moderate` mode will also enable `:require_fqdn`
|
97
|
+
configuration option.
|
98
|
+
|
99
|
+
Moderate mode can be enabled globally by requiring `email_validator/moderate` in
|
100
|
+
your `Gemfile`, by setting the option in `config/initializers/email_validator.rb`,
|
101
|
+
or by specifying the option in a specific `validates` call.
|
102
|
+
|
103
|
+
* `Gemfile`:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
gem 'email_validator', github: 'karlwilbur/email_validator', require: 'email_validator/moderate'
|
107
|
+
```
|
108
|
+
|
109
|
+
* `config/initializers/email_validator.rb`:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
if defined?(EmailValidator)
|
113
|
+
EmailValidator.default_options[:mode] = :moderate
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
* `validates` call:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
validates :my_email_attribute, email: {mode: :moderate}
|
121
|
+
```
|
122
|
+
|
123
|
+
### Strict mode
|
124
|
+
|
125
|
+
In order to have stricter validation (according to [http://www.remote.org/jochen/mail/info/chars.html](https://web.archive.org/web/20150508102948/http://www.remote.org/jochen/mail/info/chars.html))
|
126
|
+
enable `:strict` mode.
|
127
|
+
|
128
|
+
You can do this globally by requiring `email_validator/strict` in your `Gemfile`,
|
129
|
+
by setting the options in `config/initializers/email_validator.rb`, or you can do
|
130
|
+
this in a specific `validates` call.
|
131
|
+
|
132
|
+
* `Gemfile`:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
gem 'email_validator', github: 'karlwilbur/email_validator', require: 'email_validator/strict'
|
136
|
+
```
|
137
|
+
|
138
|
+
* `config/initializers/email_validator.rb`:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
if defined?(EmailValidator)
|
142
|
+
EmailValidator.default_options[:mode] = :strict
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
* `validates` call:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
validates :my_email_attribute, email: {mode: :strict}
|
150
|
+
```
|
151
|
+
|
23
152
|
## Validation outside a model
|
24
153
|
|
25
|
-
If you
|
154
|
+
If you need to validate an email outside a model, you can get the regexp:
|
155
|
+
|
156
|
+
### Loose/default mode
|
26
157
|
|
27
158
|
```ruby
|
28
|
-
EmailValidator.
|
29
|
-
EmailValidator.valid?('narf@example.com') # => true
|
30
|
-
EmailValidator.invalid?('narf@example.com') # => false
|
159
|
+
EmailValidator.valid?('narf@example.com') # boolean
|
31
160
|
```
|
32
161
|
|
33
|
-
|
162
|
+
### Requiring a FQDN
|
34
163
|
|
35
|
-
|
164
|
+
```ruby
|
165
|
+
EmailValidator.valid?('narf@somehost') # boolean false
|
166
|
+
EmailValidator.invalid?('narf@somehost', require_fqdn: false) # boolean true
|
167
|
+
```
|
36
168
|
|
37
|
-
|
169
|
+
### Requiring a specific domain
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
EmailValidator.valid?('narf@example.com', domain: 'foo.com') # boolean false
|
173
|
+
EmailValidator.invalid?('narf@example.com', domain: 'foo.com') # boolean true
|
174
|
+
```
|
38
175
|
|
39
|
-
|
176
|
+
### Moderate mode
|
40
177
|
|
41
178
|
```ruby
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
179
|
+
EmailValidator.regexp(mode: :moderate) # returns the regex
|
180
|
+
EmailValidator.valid?('narf@example.com', mode: :moderate) # boolean
|
181
|
+
```
|
182
|
+
|
183
|
+
### Strict mode
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
EmailValidator.regexp(mode: :strict) # returns the regex
|
187
|
+
EmailValidator.valid?('narf@example.com', mode: :strict) # boolean
|
46
188
|
```
|
47
189
|
|
48
|
-
|
190
|
+
## Thread safety
|
191
|
+
|
192
|
+
This gem is thread safe, with one caveat: `EmailValidator.default_options` must
|
193
|
+
be configured before use in a multi-threaded environment. If you configure
|
194
|
+
`default_options` in a Rails initializer file, then you're good to go since
|
195
|
+
initializers are run before worker threads are spawned.
|
49
196
|
|
50
197
|
## Alternative gems
|
51
198
|
|
52
|
-
Do you prefer a different email validation gem? If so, open an issue with a brief
|
199
|
+
Do you prefer a different email validation gem? If so, open an issue with a brief
|
200
|
+
explanation of how it differs from this gem. I'll add a link to it in this README.
|
53
201
|
|
54
|
-
|
202
|
+
* [`email_address`](https://github.com/afair/email_address) (<https://github.com/K-and-R/email_validator/issues/58>)
|
203
|
+
* [`email_verifier`](https://github.com/kamilc/email_verifier) (<https://github.com/K-and-R/email_validator/issues/65>)
|
204
|
+
|
205
|
+
## Maintainers
|
206
|
+
|
207
|
+
All thanks is given to [Brian Alexander (balexand)](https://github.com/balexand)
|
208
|
+
for is initial work on this gem.
|
209
|
+
|
210
|
+
Currently maintained by:
|
55
211
|
|
56
|
-
|
212
|
+
* Karl Wilbur (<https://github.com/karlwilbur>)
|
213
|
+
* K&R Software (<https://github.com/K-and-R>)
|
data/lib/email_validator.rb
CHANGED
@@ -1,29 +1,101 @@
|
|
1
|
-
#
|
2
|
-
require 'active_model'
|
3
|
-
|
1
|
+
# Based on work from http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/
|
4
2
|
class EmailValidator < ActiveModel::EachValidator
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
# rubocop:disable Style/ClassVars
|
4
|
+
@@default_options = {
|
5
|
+
:allow_nil => false,
|
6
|
+
:domain => nil,
|
7
|
+
:require_fqdn => nil,
|
8
|
+
:mode => :loose
|
9
|
+
}
|
10
|
+
# rubocop:enable Style/ClassVars
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def default_options
|
14
|
+
@@default_options
|
11
15
|
end
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
def valid?(value, options = {})
|
18
|
+
options = parse_options(options)
|
19
|
+
return true if value.nil? && options[:allow_nil] == true
|
20
|
+
return false if value.nil?
|
21
|
+
!!(value =~ regexp(options))
|
22
|
+
end
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
24
|
+
def invalid?(value, options = {})
|
25
|
+
!valid?(value, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Refs:
|
29
|
+
# https://tools.ietf.org/html/rfc2822 : 3.2. Lexical Tokens, 3.4.1. Addr-spec specification
|
30
|
+
# https://tools.ietf.org/html/rfc5321 : 4.1.2. Command Argument Syntax
|
31
|
+
def regexp(options = {})
|
32
|
+
options = parse_options(options)
|
33
|
+
if options[:mode] == :loose
|
34
|
+
return /\A[^\s]+@[^\s]+\z/ if options[:domain].nil?
|
35
|
+
return /\A[^\s]+@#{domain_pattern(options)}\z/
|
36
|
+
end
|
37
|
+
/\A(?>#{local_part_pattern})@#{domain_pattern(options)}\z/i
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def alpha
|
43
|
+
'[[:alpha:]]'
|
44
|
+
end
|
45
|
+
|
46
|
+
def alnum
|
47
|
+
'[[:alnum:]]'
|
48
|
+
end
|
49
|
+
|
50
|
+
def alnumhy
|
51
|
+
"(?:#{alnum}|-)"
|
52
|
+
end
|
53
|
+
|
54
|
+
def ipv4
|
55
|
+
'\d{1,3}(?:\.\d{1,3}){3}'
|
56
|
+
end
|
57
|
+
|
58
|
+
def ipv6
|
59
|
+
# only supporting full IPv6 addresses right now
|
60
|
+
'IPv6:[[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}'
|
61
|
+
end
|
62
|
+
|
63
|
+
def address_literal
|
64
|
+
"\\[(?:#{ipv4}|#{ipv6})\\]"
|
65
|
+
end
|
66
|
+
|
67
|
+
def label_pattern
|
68
|
+
"#{alpha}(?:#{alnumhy}{,62}#{alnum})?"
|
69
|
+
end
|
70
|
+
|
71
|
+
def atom_char
|
72
|
+
# The `atext` spec
|
73
|
+
# We are looking at this without whitespace; no whitespace support here
|
74
|
+
"[-#{alpha}#{alnum}+_!\"'#$%^&*{}/=?`|~]"
|
75
|
+
end
|
76
|
+
|
77
|
+
def local_part_pattern
|
78
|
+
# the `dot-atom-text` spec, but with a 64 character limit
|
79
|
+
"#{atom_char}(?:\\.?#{atom_char}){,63}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def domain_pattern(options)
|
83
|
+
return options[:domain].sub(/\./, '\.') if options[:domain].present?
|
84
|
+
return "(?:#{label_pattern}\\.)+#{label_pattern}" if options[:require_fqdn]
|
85
|
+
"(?:#{address_literal}|(?:#{label_pattern}\\.)*#{label_pattern})"
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
19
89
|
|
20
|
-
|
21
|
-
|
90
|
+
def parse_options(options)
|
91
|
+
# `:moderate` mode enables `:require_fqdn`, unless it is already explicitly disabled
|
92
|
+
options[:require_fqdn] = true if options[:require_fqdn].nil? && options[:mode] == :moderate
|
93
|
+
default_options.merge(options)
|
94
|
+
end
|
22
95
|
end
|
23
96
|
|
24
97
|
def validate_each(record, attribute, value)
|
25
|
-
|
26
|
-
|
27
|
-
end
|
98
|
+
options = @@default_options.merge(self.options)
|
99
|
+
record.errors.add(attribute, options[:message] || :invalid) unless self.class.valid?(value, options)
|
28
100
|
end
|
29
101
|
end
|
@@ -1,153 +1,872 @@
|
|
1
|
-
# encoding: UTF-8
|
2
1
|
require 'spec_helper'
|
3
2
|
|
4
|
-
class
|
3
|
+
class DefaultUser < TestModel
|
5
4
|
validates :email, :email => true
|
6
5
|
end
|
7
6
|
|
8
|
-
class
|
9
|
-
validates :email, :email => {:
|
7
|
+
class ModerateUser < TestModel
|
8
|
+
validates :email, :email => { :mode => :moderate }
|
10
9
|
end
|
11
10
|
|
12
|
-
class
|
13
|
-
validates :email, :email => {:
|
11
|
+
class StrictUser < TestModel
|
12
|
+
validates :email, :email => { :mode => :strict }
|
14
13
|
end
|
15
14
|
|
16
|
-
class
|
17
|
-
validates :
|
15
|
+
class AllowNilDefaultUser < TestModel
|
16
|
+
validates :email, :email => { :allow_nil => true }
|
18
17
|
end
|
19
18
|
|
20
|
-
|
19
|
+
class AllowNilModerateUser < TestModel
|
20
|
+
validates :email, :email => { :allow_nil => true, :mode => :moderate }
|
21
|
+
end
|
22
|
+
|
23
|
+
class AllowNilStrictUser < TestModel
|
24
|
+
validates :email, :email => { :allow_nil => true, :mode => :strict }
|
25
|
+
end
|
26
|
+
|
27
|
+
class DisallowNilDefaultUser < TestModel
|
28
|
+
validates :email, :email => { :allow_nil => false }
|
29
|
+
end
|
30
|
+
|
31
|
+
class DisallowNilModerateUser < TestModel
|
32
|
+
validates :email, :email => { :allow_nil => false, :mode => :moderate }
|
33
|
+
end
|
34
|
+
|
35
|
+
class DisallowNilStrictUser < TestModel
|
36
|
+
validates :email, :email => { :allow_nil => false, :mode => :strict }
|
37
|
+
end
|
38
|
+
|
39
|
+
class DomainModerateUser < TestModel
|
40
|
+
validates :email, :email => { :domain => 'example.com', :mode => :moderate }
|
41
|
+
end
|
42
|
+
|
43
|
+
class DomainStrictUser < TestModel
|
44
|
+
validates :email, :email => { :domain => 'example.com', :mode => :strict }
|
45
|
+
end
|
46
|
+
|
47
|
+
class NonFqdnModerateUser < TestModel
|
48
|
+
validates :email, :email => { :require_fqdn => false, :mode => :moderate }
|
49
|
+
end
|
50
|
+
|
51
|
+
class NonFqdnStrictUser < TestModel
|
52
|
+
validates :email, :email => { :require_fqdn => false, :mode => :strict }
|
53
|
+
end
|
54
|
+
|
55
|
+
class DefaultUserWithMessage < TestModel
|
56
|
+
validates :email_address, :email => { :message => 'is not looking very good!' }
|
57
|
+
end
|
58
|
+
|
59
|
+
RSpec.describe EmailValidator do
|
60
|
+
describe 'validation' do
|
61
|
+
valid_special_chars = {
|
62
|
+
:ampersand => '&',
|
63
|
+
:asterisk => '*',
|
64
|
+
:backtick => '`',
|
65
|
+
:braceleft => '{',
|
66
|
+
:braceright => '}',
|
67
|
+
:caret => '^',
|
68
|
+
:dollar => '$',
|
69
|
+
:equals => '=',
|
70
|
+
:exclaim => '!',
|
71
|
+
:hash => '#',
|
72
|
+
:hyphen => '-',
|
73
|
+
:percent => '%',
|
74
|
+
:plus => '+',
|
75
|
+
:pipe => '|',
|
76
|
+
:question => '?',
|
77
|
+
:quotedouble => '"',
|
78
|
+
:quotesingle => "'",
|
79
|
+
:slash => '/',
|
80
|
+
:tilde => '~',
|
81
|
+
:underscore => '_'
|
82
|
+
}
|
83
|
+
|
84
|
+
invalid_special_chars = {
|
85
|
+
:backslash => '\\',
|
86
|
+
:braketleft => '[',
|
87
|
+
:braketright => ']',
|
88
|
+
:colon => ':',
|
89
|
+
:comma => ',',
|
90
|
+
:greater => '>',
|
91
|
+
:lesser => '<',
|
92
|
+
:parenleft => '(',
|
93
|
+
:parenright => ')',
|
94
|
+
:semicolon => ';'
|
95
|
+
}
|
96
|
+
|
97
|
+
valid_includable = valid_special_chars.merge({ :dot => '.' })
|
98
|
+
valid_beginable = valid_special_chars
|
99
|
+
valid_endable = valid_special_chars
|
100
|
+
invalid_includable = { :at => '@' }
|
101
|
+
whitespace = { :newline => "\n", :tab => "\t", :carriage_return => "\r", :space => ' ' }
|
102
|
+
moderatly_invalid_includable = invalid_special_chars
|
103
|
+
moderatly_invalid_beginable = moderatly_invalid_includable.merge({ :dot => '.' })
|
104
|
+
moderatly_invalid_endable = moderatly_invalid_beginable
|
105
|
+
domain_invalid_beginable = invalid_special_chars.merge(valid_special_chars)
|
106
|
+
domain_invalid_endable = domain_invalid_beginable
|
107
|
+
domain_invalid_includable = domain_invalid_beginable.reject { |k, _v| k == :hyphen }
|
108
|
+
|
109
|
+
# rubocop:disable Layout/BlockEndNewline, Layout/MultilineBlockLayout, Layout/MultilineMethodCallBraceLayout, Style/BlockDelimiters, Style/MultilineBlockChain
|
110
|
+
context 'when given the valid email' do
|
111
|
+
valid_includable.map { |k, v| [
|
112
|
+
"include-#{v}-#{k}@valid-characters-in-local.dev"
|
113
|
+
]}.concat(valid_beginable.map { |k, v| [
|
114
|
+
"#{v}start-with-#{k}@valid-characters-in-local.dev"
|
115
|
+
]}).concat(valid_endable.map { |k, v| [
|
116
|
+
"end-with-#{k}-#{v}@valid-characters-in-local.dev"
|
117
|
+
]}).concat([
|
118
|
+
'a+b@plus-in-local.com',
|
119
|
+
'a_b@underscore-in-local.com',
|
120
|
+
'user@example.com',
|
121
|
+
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@letters-in-local.dev',
|
122
|
+
'01234567890@numbers-in-local.dev',
|
123
|
+
'a@single-character-in-local.dev',
|
124
|
+
'one-character-third-level@a.example.com',
|
125
|
+
'single-character-in-sld@x.dev',
|
126
|
+
'local@dash-in-sld.com',
|
127
|
+
'numbers-in-sld@s123.com',
|
128
|
+
'one-letter-sld@x.dev',
|
129
|
+
'uncommon-tld@sld.museum',
|
130
|
+
'uncommon-tld@sld.travel',
|
131
|
+
'uncommon-tld@sld.mobi',
|
132
|
+
'country-code-tld@sld.uk',
|
133
|
+
'country-code-tld@sld.rw',
|
134
|
+
'local@sld.newTLD',
|
135
|
+
'local@sub.domains.com',
|
136
|
+
'aaa@bbb.co.jp',
|
137
|
+
'nigel.worthington@big.co.uk',
|
138
|
+
'f@c.com',
|
139
|
+
'f@s.c',
|
140
|
+
'someuser@somehost.somedomain',
|
141
|
+
'mixed-1234-in-{+^}-local@sld.dev',
|
142
|
+
'partially."quoted"@sld.com',
|
143
|
+
'areallylongnameaasdfasdfasdfasdf@asdfasdfasdfasdfasdf.ab.cd.ef.gh.co.ca'
|
144
|
+
]).flatten.each do |email|
|
145
|
+
context 'when using defaults' do
|
146
|
+
it "#{email} should be valid" do
|
147
|
+
expect(DefaultUser.new(:email => email)).to be_valid
|
148
|
+
end
|
149
|
+
|
150
|
+
it "#{email} should be valid using EmailValidator.valid?" do
|
151
|
+
expect(described_class).to be_valid(email)
|
152
|
+
end
|
21
153
|
|
22
|
-
|
23
|
-
|
154
|
+
it "#{email} should not be invalid using EmailValidator.invalid?" do
|
155
|
+
expect(described_class).not_to be_invalid(email)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "#{email} should match the regexp" do
|
159
|
+
expect(!!(email.strip =~ described_class.regexp)).to be(true)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when in `:moderate` mode' do
|
164
|
+
it "#{email} should be valid" do
|
165
|
+
expect(ModerateUser.new(:email => email)).to be_valid
|
166
|
+
end
|
167
|
+
|
168
|
+
it "#{email} should be valid using EmailValidator.valid?" do
|
169
|
+
expect(described_class).to be_valid(email, :mode => :moderate)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "#{email} should not be invalid using EmailValidator.valid?" do
|
173
|
+
expect(described_class).not_to be_invalid(email, :mode => :moderate)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "#{email} should match the regexp" do
|
177
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :moderate))).to be(true)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context 'when in `:strict` mode' do
|
182
|
+
it "#{email} should be valid" do
|
183
|
+
expect(StrictUser.new(:email => email)).to be_valid
|
184
|
+
end
|
185
|
+
|
186
|
+
it "#{email} should be valid using EmailValidator.valid?" do
|
187
|
+
expect(described_class).to be_valid(email, :mode => :strict)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "#{email} should not be invalid using EmailValidator.invalid?" do
|
191
|
+
expect(described_class).not_to be_invalid(email, :mode => :strict)
|
192
|
+
end
|
193
|
+
|
194
|
+
it "#{email} should match the regexp" do
|
195
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(true)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when given the valid host-only email' do
|
24
202
|
[
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
" user@example.com ",
|
29
|
-
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@letters-in-local.org",
|
30
|
-
"01234567890@numbers-in-local.net",
|
31
|
-
"a@single-character-in-local.org",
|
32
|
-
"one-character-third-level@a.example.com",
|
33
|
-
"single-character-in-sld@x.org",
|
34
|
-
"local@dash-in-sld.com",
|
35
|
-
"letters-in-sld@123.com",
|
36
|
-
"one-letter-sld@x.org",
|
37
|
-
"uncommon-tld@sld.museum",
|
38
|
-
"uncommon-tld@sld.travel",
|
39
|
-
"uncommon-tld@sld.mobi",
|
40
|
-
"country-code-tld@sld.uk",
|
41
|
-
"country-code-tld@sld.rw",
|
42
|
-
"local@sld.newTLD",
|
43
|
-
"local@sub.domains.com",
|
44
|
-
"aaa@bbb.co.jp",
|
45
|
-
"nigel.worthington@big.co.uk",
|
46
|
-
"f@c.com",
|
47
|
-
"areallylongnameaasdfasdfasdfasdf@asdfasdfasdfasdfasdf.ab.cd.ef.gh.co.ca",
|
48
|
-
"ящик@яндекс.рф",
|
49
|
-
"test@test.testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttes",
|
50
|
-
"hans,peter@example.com",
|
51
|
-
"hans(peter@example.com",
|
52
|
-
"hans)peter@example.com",
|
53
|
-
"partially.\"quoted\"@sld.com",
|
54
|
-
"&'*+-./=?^_{}~@other-valid-characters-in-local.net",
|
55
|
-
"mixed-1234-in-{+^}-local@sld.net",
|
56
|
-
"user@domain.рф",
|
57
|
-
"寿司@example.com"
|
203
|
+
'f@s',
|
204
|
+
'user@localhost',
|
205
|
+
'someuser@somehost'
|
58
206
|
].each do |email|
|
207
|
+
context 'when using defaults' do
|
208
|
+
it "#{email} should be valid" do
|
209
|
+
expect(DefaultUser.new(:email => email)).to be_valid
|
210
|
+
end
|
59
211
|
|
60
|
-
|
61
|
-
|
212
|
+
it "#{email} should be valid using EmailValidator.valid?" do
|
213
|
+
expect(described_class).to be_valid(email)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "#{email} should not be invalid using EmailValidator.invalid?" do
|
217
|
+
expect(described_class).not_to be_invalid(email)
|
218
|
+
end
|
219
|
+
|
220
|
+
it "#{email} should match the regexp" do
|
221
|
+
expect(!!(email.strip =~ described_class.regexp)).to be(true)
|
222
|
+
end
|
62
223
|
end
|
63
224
|
|
64
|
-
|
65
|
-
|
225
|
+
context 'when in `:moderate` mode' do
|
226
|
+
it "#{email} should not be valid" do
|
227
|
+
expect(ModerateUser.new(:email => email)).not_to be_valid
|
228
|
+
end
|
229
|
+
|
230
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
231
|
+
expect(described_class).not_to be_valid(email, :mode => :moderate)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
235
|
+
expect(described_class).to be_invalid(email, :mode => :moderate)
|
236
|
+
end
|
237
|
+
|
238
|
+
it "#{email} should not match the regexp" do
|
239
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :moderate))).to be(false)
|
240
|
+
end
|
66
241
|
end
|
67
242
|
|
68
|
-
|
69
|
-
|
243
|
+
context 'when in `:strict` mode' do
|
244
|
+
it "#{email} should be valid" do
|
245
|
+
expect(StrictUser.new(:email => email)).to be_valid
|
246
|
+
end
|
247
|
+
|
248
|
+
it "#{email} should be valid using EmailValidator.valid?" do
|
249
|
+
expect(described_class).to be_valid(email, :mode => :strict)
|
250
|
+
end
|
251
|
+
|
252
|
+
it "#{email} should not be invalid using EmailValidator.invalid?" do
|
253
|
+
expect(described_class).not_to be_invalid(email, :mode => :strict)
|
254
|
+
end
|
255
|
+
|
256
|
+
it "#{email} should match the regexp" do
|
257
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(true)
|
258
|
+
end
|
70
259
|
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
context 'when given the valid IP address email' do
|
264
|
+
[
|
265
|
+
'bracketed-IP@[127.0.0.1]',
|
266
|
+
'bracketed-and-labeled-IPv6@[IPv6:abcd:ef01:1234:5678:9abc:def0:1234:5678]'
|
267
|
+
].each do |email|
|
268
|
+
context 'when using defaults' do
|
269
|
+
it "#{email} should be valid" do
|
270
|
+
expect(DefaultUser.new(:email => email)).to be_valid
|
271
|
+
end
|
272
|
+
|
273
|
+
it "#{email} should be valid using EmailValidator.valid?" do
|
274
|
+
expect(described_class).to be_valid(email)
|
275
|
+
end
|
276
|
+
|
277
|
+
it "#{email} should not be invalid using EmailValidator.invalid?" do
|
278
|
+
expect(described_class).not_to be_invalid(email)
|
279
|
+
end
|
71
280
|
|
72
|
-
|
73
|
-
|
281
|
+
it "#{email} should match the regexp" do
|
282
|
+
expect(!!(email.strip =~ described_class.regexp)).to be(true)
|
283
|
+
end
|
74
284
|
end
|
75
285
|
|
286
|
+
context 'when in `:moderate` mode' do
|
287
|
+
it "#{email} should not be valid" do
|
288
|
+
expect(ModerateUser.new(:email => email)).not_to be_valid
|
289
|
+
end
|
290
|
+
|
291
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
292
|
+
expect(described_class).not_to be_valid(email, :mode => :moderate)
|
293
|
+
end
|
294
|
+
|
295
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
296
|
+
expect(described_class).to be_invalid(email, :mode => :moderate)
|
297
|
+
end
|
298
|
+
|
299
|
+
it "#{email} should not match the strict regexp" do
|
300
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :moderate))).to be(false)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
context 'when in `:strict` mode' do
|
305
|
+
it "#{email} should be valid" do
|
306
|
+
expect(StrictUser.new(:email => email)).to be_valid
|
307
|
+
end
|
308
|
+
|
309
|
+
it "#{email} should be valid using EmailValidator.valid?" do
|
310
|
+
expect(described_class).to be_valid(email, :mode => :strict)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "#{email} should not be invalid using EmailValidator.invalid?" do
|
314
|
+
expect(described_class).not_to be_invalid(email, :mode => :strict)
|
315
|
+
end
|
316
|
+
|
317
|
+
it "#{email} should match the strict regexp" do
|
318
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(true)
|
319
|
+
end
|
320
|
+
end
|
76
321
|
end
|
322
|
+
end
|
323
|
+
|
324
|
+
context 'when given the invalid email' do
|
325
|
+
invalid_includable.map { |k, v| [
|
326
|
+
"include-#{v}-#{k}@invalid-characters-in-local.dev"
|
327
|
+
]}.concat(domain_invalid_beginable.map { |k, v| [
|
328
|
+
"start-with-#{k}@#{v}invalid-characters-in-domain.dev"
|
329
|
+
]}).concat(domain_invalid_endable.map { |k, v| [
|
330
|
+
"end-with-#{k}@invalid-characters-in-domain#{v}.dev"
|
331
|
+
]}).concat(domain_invalid_includable.map { |k, v| [
|
332
|
+
"include-#{k}@invalid-characters-#{v}-in-domain.dev"
|
333
|
+
]}).concat([
|
334
|
+
'test@example.com@example.com',
|
335
|
+
'missing-sld@.com',
|
336
|
+
'missing-tld@sld.',
|
337
|
+
'only-numbers-in-domain-label@sub.123.com',
|
338
|
+
'only-numbers-in-domain-label@123.example.com',
|
339
|
+
'unbracketed-IPv6@abcd:ef01:1234:5678:9abc:def0:1234:5678',
|
340
|
+
'unbracketed-and-labled-IPv6@IPv6:abcd:ef01:1234:5678:9abc:def0:1234:5678',
|
341
|
+
'bracketed-and-unlabeled-IPv6@[abcd:ef01:1234:5678:9abc:def0:1234:5678]',
|
342
|
+
'unbracketed-IPv4@127.0.0.1',
|
343
|
+
'invalid-IPv4@127.0.0.1.26',
|
344
|
+
'another-invalid-IPv4@127.0.0.256',
|
345
|
+
'IPv4-and-port@127.0.0.1:25',
|
346
|
+
'host-beginning-with-dot@.example.com',
|
347
|
+
'domain-beginning-with-dash@-example.com',
|
348
|
+
'domain-ending-with-dash@example-.com',
|
349
|
+
'the-local-part-is-invalid-if-it-is-longer-than-sixty-four-characters@sld.dev',
|
350
|
+
"user@example.com<script>alert('hello')</script>"
|
351
|
+
]).flatten.each do |email|
|
352
|
+
context 'when using defaults' do
|
353
|
+
it "#{email} should be valid" do
|
354
|
+
expect(DefaultUser.new(:email => email)).to be_valid
|
355
|
+
end
|
356
|
+
|
357
|
+
it "#{email} should be valid using EmailValidator.valid?" do
|
358
|
+
expect(described_class).to be_valid(email)
|
359
|
+
end
|
360
|
+
|
361
|
+
it "#{email} should not be invalid using EmailValidator.invalid?" do
|
362
|
+
expect(described_class).not_to be_invalid(email)
|
363
|
+
end
|
364
|
+
|
365
|
+
it "#{email} should match the regexp" do
|
366
|
+
expect(!!(email.strip =~ described_class.regexp)).to be(true)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
context 'when in `:moderate` mode' do
|
371
|
+
it "#{email} should not be valid" do
|
372
|
+
expect(ModerateUser.new(:email => email)).not_to be_valid
|
373
|
+
end
|
374
|
+
|
375
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
376
|
+
expect(described_class).not_to be_valid(email, :mode => :moderate)
|
377
|
+
end
|
378
|
+
|
379
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
380
|
+
expect(described_class).to be_invalid(email, :mode => :moderate)
|
381
|
+
end
|
77
382
|
|
383
|
+
it "#{email} should not match the strict regexp" do
|
384
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :moderate))).to be(false)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
context 'when in `:strict` mode' do
|
389
|
+
it "#{email} should not be valid" do
|
390
|
+
expect(StrictUser.new(:email => email)).not_to be_valid
|
391
|
+
end
|
392
|
+
|
393
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
394
|
+
expect(described_class).not_to be_valid(email, :mode => :strict)
|
395
|
+
end
|
396
|
+
|
397
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
398
|
+
expect(described_class).to be_invalid(email, :mode => :strict)
|
399
|
+
end
|
400
|
+
|
401
|
+
it "#{email} should not match the strict regexp" do
|
402
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
context 'when given the invalid email with whitespace in parts' do
|
409
|
+
whitespace.map { |k, v| [
|
410
|
+
"include-#{v}-#{k}@invalid-characters-in-local.dev"
|
411
|
+
]}.concat([
|
412
|
+
'foo @bar.com',
|
413
|
+
"foo\t@bar.com",
|
414
|
+
"foo\n@bar.com",
|
415
|
+
"foo\r@bar.com",
|
416
|
+
'test@ example.com',
|
417
|
+
'user@example .com',
|
418
|
+
"user@example\t.com",
|
419
|
+
"user@example\n.com",
|
420
|
+
"user@example\r.com",
|
421
|
+
'user@exam ple.com',
|
422
|
+
"user@exam\tple.com",
|
423
|
+
"user@exam\nple.com",
|
424
|
+
"user@exam\rple.com",
|
425
|
+
'us er@example.com',
|
426
|
+
"us\ter@example.com",
|
427
|
+
"us\ner@example.com",
|
428
|
+
"us\rer@example.com",
|
429
|
+
"user@example.com\n<script>alert('hello')</script>",
|
430
|
+
"user@example.com\t<script>alert('hello')</script>",
|
431
|
+
"user@example.com\r<script>alert('hello')</script>",
|
432
|
+
"user@example.com <script>alert('hello')</script>",
|
433
|
+
' leading-whitespace@example.com',
|
434
|
+
'trailing-whitespace@example.com ',
|
435
|
+
' leading-and-trailing-whitespace@example.com ',
|
436
|
+
' user-with-leading-whitespace-space@example.com',
|
437
|
+
"\tuser-with-leading-whitespace-tab@example.com",
|
438
|
+
"
|
439
|
+
user-with-leading-whitespace-newline@example.com",
|
440
|
+
'domain-with-trailing-whitespace-space@example.com ',
|
441
|
+
"domain-with-trailing-whitespace-tab@example.com\t",
|
442
|
+
"domain-with-trailing-whitespace-newline@example.com
|
443
|
+
"
|
444
|
+
]).flatten.each do |email|
|
445
|
+
context 'when using defaults' do
|
446
|
+
it "#{email} should not be valid" do
|
447
|
+
expect(DefaultUser.new(:email => email)).not_to be_valid
|
448
|
+
end
|
449
|
+
|
450
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
451
|
+
expect(described_class).not_to be_valid(email)
|
452
|
+
end
|
453
|
+
|
454
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
455
|
+
expect(described_class).to be_invalid(email)
|
456
|
+
end
|
457
|
+
|
458
|
+
it "#{email} should not match the regexp" do
|
459
|
+
expect(!!(email =~ described_class.regexp)).to be(false)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
context 'when in `:moderate` mode' do
|
464
|
+
it "#{email} should not be valid" do
|
465
|
+
expect(ModerateUser.new(:email => email)).not_to be_valid
|
466
|
+
end
|
467
|
+
|
468
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
469
|
+
expect(described_class).not_to be_valid(email, :mode => :moderate)
|
470
|
+
end
|
471
|
+
|
472
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
473
|
+
expect(described_class).to be_invalid(email, :mode => :moderate)
|
474
|
+
end
|
475
|
+
|
476
|
+
it "#{email} should not match the strict regexp" do
|
477
|
+
expect(!!(email =~ described_class.regexp(:mode => :moderate))).to be(false)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
context 'when in `:strict` mode' do
|
482
|
+
it "#{email} should not be valid" do
|
483
|
+
expect(StrictUser.new(:email => email)).not_to be_valid
|
484
|
+
end
|
485
|
+
|
486
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
487
|
+
expect(described_class).not_to be_valid(email, :mode => :strict)
|
488
|
+
end
|
489
|
+
|
490
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
491
|
+
expect(described_class).to be_invalid(email, :mode => :strict)
|
492
|
+
end
|
493
|
+
|
494
|
+
it "#{email} should not match the strict regexp" do
|
495
|
+
expect(!!(email =~ described_class.regexp(:mode => :strict))).to be(false)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
78
499
|
end
|
79
500
|
|
80
|
-
context
|
501
|
+
context 'when given the invalid email with missing parts' do
|
81
502
|
[
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
"missing-at-sign.net",
|
503
|
+
'',
|
504
|
+
'@bar.com',
|
505
|
+
'test@',
|
506
|
+
'@missing-local.dev',
|
507
|
+
' ',
|
508
|
+
'missing-at-sign.dev'
|
89
509
|
].each do |email|
|
510
|
+
context 'when using defaults' do
|
511
|
+
it "#{email} should not be valid" do
|
512
|
+
expect(DefaultUser.new(:email => email)).not_to be_valid
|
513
|
+
end
|
514
|
+
|
515
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
516
|
+
expect(described_class).not_to be_valid(email)
|
517
|
+
end
|
518
|
+
|
519
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
520
|
+
expect(described_class).to be_invalid(email)
|
521
|
+
end
|
522
|
+
|
523
|
+
it "#{email} should not match the regexp" do
|
524
|
+
expect(!!(email.strip =~ described_class.regexp)).to be(false)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
context 'when in `:moderate` mode' do
|
529
|
+
it "#{email} should not be valid" do
|
530
|
+
expect(ModerateUser.new(:email => email)).not_to be_valid
|
531
|
+
end
|
90
532
|
|
91
|
-
|
92
|
-
|
533
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
534
|
+
expect(described_class).not_to be_valid(email, :mode => :moderate)
|
535
|
+
end
|
536
|
+
|
537
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
538
|
+
expect(described_class).to be_invalid(email, :mode => :moderate)
|
539
|
+
end
|
540
|
+
|
541
|
+
it "#{email} should not match the strict regexp" do
|
542
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :moderate))).to be(false)
|
543
|
+
end
|
93
544
|
end
|
94
545
|
|
95
|
-
|
96
|
-
|
546
|
+
context 'when in `:strict` mode' do
|
547
|
+
it "#{email} should not be valid" do
|
548
|
+
expect(StrictUser.new(:email => email)).not_to be_valid
|
549
|
+
end
|
550
|
+
|
551
|
+
it "#{email} should not be valid using EmailValidator.valid?" do
|
552
|
+
expect(described_class).not_to be_valid(email, :mode => :strict)
|
553
|
+
end
|
554
|
+
|
555
|
+
it "#{email} should be invalid using EmailValidator.invalid?" do
|
556
|
+
expect(described_class).to be_invalid(email, :mode => :strict)
|
557
|
+
end
|
558
|
+
|
559
|
+
it "#{email} should not match the strict regexp" do
|
560
|
+
expect(!!(email.strip =~ described_class.regexp(:mode => :strict))).to be(false)
|
561
|
+
end
|
97
562
|
end
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
context 'when given the moderatly invalid email' do
|
567
|
+
moderatly_invalid_includable.map { |k, v| [
|
568
|
+
"include-#{v}-#{k}@invalid-characters-in-local.dev"
|
569
|
+
]}.concat(moderatly_invalid_beginable.map { |k, v| [
|
570
|
+
"#{v}start-with-#{k}@invalid-characters-in-local.dev"
|
571
|
+
]}).concat(moderatly_invalid_endable.map { |k, v| [
|
572
|
+
"end-with-#{k}#{v}@invalid-characters-in-local.dev"
|
573
|
+
]}).concat([
|
574
|
+
'user..-with-double-dots@example.com',
|
575
|
+
'.user-beginning-with-dot@example.com',
|
576
|
+
'user-ending-with-dot.@example.com'
|
577
|
+
]).flatten.each do |email|
|
578
|
+
context 'when using defaults' do
|
579
|
+
it "#{email.strip} in a model should be valid" do
|
580
|
+
expect(DefaultUser.new(:email => email)).to be_valid
|
581
|
+
end
|
582
|
+
|
583
|
+
it "#{email.strip} should be valid using EmailValidator.valid?" do
|
584
|
+
expect(described_class).to be_valid(email)
|
585
|
+
end
|
98
586
|
|
99
|
-
|
100
|
-
|
587
|
+
it "#{email.strip} should not be invalid using EmailValidator.invalid?" do
|
588
|
+
expect(described_class).not_to be_invalid(email)
|
589
|
+
end
|
590
|
+
|
591
|
+
it "#{email.strip} should match the regexp" do
|
592
|
+
expect(!!(email =~ described_class.regexp)).to be(true)
|
593
|
+
end
|
101
594
|
end
|
102
595
|
|
103
|
-
|
104
|
-
|
596
|
+
context 'when in `:moderate` mode' do
|
597
|
+
it "#{email.strip} in a model should be valid" do
|
598
|
+
expect(ModerateUser.new(:email => email)).not_to be_valid
|
599
|
+
end
|
600
|
+
|
601
|
+
it "#{email.strip} should not be valid using EmailValidator.valid?" do
|
602
|
+
expect(described_class).not_to be_valid(email, :mode => :moderate)
|
603
|
+
end
|
604
|
+
|
605
|
+
it "#{email.strip} should be invalid using EmailValidator.invalid?" do
|
606
|
+
expect(described_class).to be_invalid(email, :mode => :moderate)
|
607
|
+
end
|
608
|
+
|
609
|
+
it "#{email.strip} should not match the strict regexp" do
|
610
|
+
expect(!!(email =~ described_class.regexp(:mode => :moderate))).to be(false)
|
611
|
+
end
|
105
612
|
end
|
106
613
|
|
614
|
+
context 'when in `:strict` mode' do
|
615
|
+
it "#{email.strip} in a model should not be valid" do
|
616
|
+
expect(StrictUser.new(:email => email)).not_to be_valid
|
617
|
+
end
|
618
|
+
|
619
|
+
it "#{email.strip} should not be valid using EmailValidator.valid?" do
|
620
|
+
expect(described_class).not_to be_valid(email, :mode => :strict)
|
621
|
+
end
|
622
|
+
|
623
|
+
it "#{email.strip} should be invalid using EmailValidator.invalid?" do
|
624
|
+
expect(described_class).to be_invalid(email, :mode => :strict)
|
625
|
+
end
|
626
|
+
|
627
|
+
it "#{email.strip} should not match the strict regexp" do
|
628
|
+
expect(!!(email =~ described_class.regexp(:mode => :strict))).to be(false)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
context 'when `require_fqdn` is explicitly disabled' do
|
635
|
+
let(:opts) { { :require_fqdn => false } }
|
636
|
+
|
637
|
+
context 'when given a valid hostname-only email' do
|
638
|
+
let(:email) { 'someuser@somehost' }
|
639
|
+
|
640
|
+
context 'when using defaults' do
|
641
|
+
it 'is valid using EmailValidator.valid?' do
|
642
|
+
expect(described_class).to be_valid(email, opts)
|
643
|
+
end
|
644
|
+
|
645
|
+
it 'is not invalid using EmailValidator.invalid?' do
|
646
|
+
expect(described_class).not_to be_invalid(email, opts)
|
647
|
+
end
|
648
|
+
|
649
|
+
it 'matches the regexp' do
|
650
|
+
expect(!!(email =~ described_class.regexp(opts))).to be(true)
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
context 'when in `"moderate` mode' do
|
655
|
+
let(:opts) { { :require_fqdn => false, :mode => :moderate } }
|
656
|
+
|
657
|
+
it 'is valid' do
|
658
|
+
expect(NonFqdnModerateUser.new(:email => email)).to be_valid
|
659
|
+
end
|
660
|
+
|
661
|
+
it 'is valid using EmailValidator.valid?' do
|
662
|
+
expect(described_class).to be_valid(email, opts)
|
663
|
+
end
|
664
|
+
|
665
|
+
it 'is not invalid using EmailValidator.invalid?' do
|
666
|
+
expect(described_class).not_to be_invalid(email, opts)
|
667
|
+
end
|
668
|
+
|
669
|
+
it 'matches the regexp' do
|
670
|
+
expect(!!(email =~ described_class.regexp(opts))).to be(true)
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
context 'when in `:strict` mode' do
|
675
|
+
let(:opts) { { :require_fqdn => false, :mode => :strict } }
|
676
|
+
|
677
|
+
it 'is valid in strict mode' do
|
678
|
+
expect(NonFqdnStrictUser.new(:email => email)).to be_valid
|
679
|
+
end
|
680
|
+
|
681
|
+
it 'is valid using EmailValidator.valid?' do
|
682
|
+
expect(described_class).to be_valid(email, opts)
|
683
|
+
end
|
684
|
+
|
685
|
+
it 'is not invalid using EmailValidator.invalid?' do
|
686
|
+
expect(described_class).not_to be_invalid(email, opts)
|
687
|
+
end
|
688
|
+
|
689
|
+
it 'does not match the strict regexp' do
|
690
|
+
expect(!!(email =~ described_class.regexp(opts))).to be(true)
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
context 'when given a valid email using an FQDN' do
|
696
|
+
let(:email) { 'someuser@somehost.somedomain' }
|
697
|
+
|
698
|
+
it 'is valid' do
|
699
|
+
expect(NonFqdnModerateUser.new(:email => email)).to be_valid
|
700
|
+
end
|
701
|
+
|
702
|
+
# rubocop:disable RSpec/PredicateMatcher
|
703
|
+
it 'is valid using EmailValidator.valid?' do
|
704
|
+
expect(described_class.valid?(email, opts)).to be(true)
|
705
|
+
end
|
706
|
+
|
707
|
+
it 'is not invalid using EmailValidator.invalid?' do
|
708
|
+
expect(described_class.invalid?(email, opts)).to be(false)
|
709
|
+
end
|
710
|
+
# rubocop:enable RSpec/PredicateMatcher
|
711
|
+
|
712
|
+
it 'matches the regexp' do
|
713
|
+
expect(!!(email =~ described_class.regexp(opts))).to be(true)
|
714
|
+
end
|
715
|
+
|
716
|
+
context 'when in `:strict` mode' do
|
717
|
+
let(:opts) { { :require_fqdn => false, :mode => :strict } }
|
718
|
+
|
719
|
+
# rubocop:disable RSpec/PredicateMatcher
|
720
|
+
it 'is valid using EmailValidator.valid?' do
|
721
|
+
expect(described_class.valid?(email, opts)).to be(true)
|
722
|
+
end
|
723
|
+
|
724
|
+
it 'is not invalid using EmailValidator.invalid?' do
|
725
|
+
expect(described_class.invalid?(email, opts)).to be(false)
|
726
|
+
end
|
727
|
+
# rubocop:enable RSpec/PredicateMatcher
|
728
|
+
|
729
|
+
it 'is valid in strict mode' do
|
730
|
+
expect(NonFqdnStrictUser.new(:email => email)).to be_valid
|
731
|
+
end
|
732
|
+
|
733
|
+
it 'does not match the strict regexp' do
|
734
|
+
expect(!!(email =~ described_class.regexp(opts))).to be(true)
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
context 'when requiring a non-matching domain' do
|
739
|
+
let(:domain) { 'example.com' }
|
740
|
+
let(:opts) { { :domain => domain } }
|
741
|
+
|
742
|
+
it 'is not valid' do
|
743
|
+
expect(DomainModerateUser.new(:email => email)).not_to be_valid
|
744
|
+
end
|
745
|
+
|
746
|
+
it 'is not valid using EmailValidator.valid?' do
|
747
|
+
expect(described_class).not_to be_valid(email, opts)
|
748
|
+
end
|
749
|
+
|
750
|
+
it 'is invalid using EmailValidator.invalid?' do
|
751
|
+
expect(described_class).to be_invalid(email, opts)
|
752
|
+
end
|
753
|
+
|
754
|
+
it 'does not matches the regexp' do
|
755
|
+
expect(!!(email =~ described_class.regexp(opts))).to be(false)
|
756
|
+
end
|
757
|
+
|
758
|
+
context 'when in `:strict` mode' do
|
759
|
+
let(:opts) { { :domain => domain, :require_fqdn => false, :mode => :strict } }
|
760
|
+
|
761
|
+
it 'is not valid using EmailValidator.valid?' do
|
762
|
+
expect(described_class).not_to be_valid(email, opts)
|
763
|
+
end
|
764
|
+
|
765
|
+
it 'is invalid using EmailValidator.invalid?' do
|
766
|
+
expect(described_class).to be_invalid(email, opts)
|
767
|
+
end
|
768
|
+
|
769
|
+
it 'is not valid' do
|
770
|
+
expect(DomainStrictUser.new(:email => email)).not_to be_valid
|
771
|
+
end
|
772
|
+
|
773
|
+
it 'does not match the regexp' do
|
774
|
+
expect(!!(email =~ described_class.regexp(opts))).to be(false)
|
775
|
+
end
|
776
|
+
end
|
777
|
+
end
|
107
778
|
end
|
108
779
|
end
|
109
780
|
end
|
781
|
+
# rubocop:enable Layout/BlockEndNewline, Layout/MultilineBlockLayout, Layout/MultilineMethodCallBraceLayout, Style/BlockDelimiters, Style/MultilineBlockChain
|
782
|
+
|
783
|
+
describe 'error messages' do
|
784
|
+
context 'when the message is not defined' do
|
785
|
+
let(:model) { DefaultUser.new :email => 'invalidemail@' }
|
110
786
|
|
111
|
-
|
112
|
-
context "when the message is not defined" do
|
113
|
-
subject { TestUser.new :email => 'invalidemail@' }
|
114
|
-
before { subject.valid? }
|
787
|
+
before { model.valid? }
|
115
788
|
|
116
|
-
it
|
117
|
-
expect(
|
789
|
+
it 'adds the default message' do
|
790
|
+
expect(model.errors[:email]).to include 'is invalid'
|
118
791
|
end
|
119
792
|
end
|
120
793
|
|
121
|
-
context
|
122
|
-
|
123
|
-
|
794
|
+
context 'when the message is defined' do
|
795
|
+
let(:model) { DefaultUserWithMessage.new :email_address => 'invalidemail@' }
|
796
|
+
|
797
|
+
before { model.valid? }
|
124
798
|
|
125
|
-
it
|
126
|
-
expect(
|
799
|
+
it 'adds the customized message' do
|
800
|
+
expect(model.errors[:email_address]).to include 'is not looking very good!'
|
127
801
|
end
|
128
802
|
end
|
129
803
|
end
|
130
804
|
|
131
|
-
describe
|
132
|
-
|
133
|
-
|
805
|
+
describe 'nil email' do
|
806
|
+
it 'is not valid when :allow_nil option is missing' do
|
807
|
+
expect(DefaultUser.new(:email => nil)).not_to be_valid
|
808
|
+
end
|
809
|
+
|
810
|
+
it 'is valid when :allow_nil options is set to true' do
|
811
|
+
expect(AllowNilDefaultUser.new(:email => nil)).to be_valid
|
812
|
+
end
|
134
813
|
|
135
|
-
it
|
136
|
-
expect(
|
814
|
+
it 'is not valid when :allow_nil option is set to false' do
|
815
|
+
expect(DisallowNilDefaultUser.new(:email => nil)).not_to be_valid
|
137
816
|
end
|
138
817
|
end
|
139
818
|
|
140
|
-
describe
|
141
|
-
|
142
|
-
|
819
|
+
describe 'limited to a domain' do
|
820
|
+
context 'when in `:moderate` mode' do
|
821
|
+
it 'is not valid with mismatched domain' do
|
822
|
+
expect(DomainModerateUser.new(:email => 'user@not-matching.io')).not_to be_valid
|
823
|
+
end
|
824
|
+
|
825
|
+
it 'is valid with matching domain' do
|
826
|
+
expect(DomainModerateUser.new(:email => 'user@example.com')).to be_valid
|
827
|
+
end
|
828
|
+
|
829
|
+
it 'does not interpret the dot as any character' do
|
830
|
+
expect(DomainModerateUser.new(:email => 'user@example-com')).not_to be_valid
|
831
|
+
end
|
143
832
|
end
|
144
833
|
|
145
|
-
|
146
|
-
|
834
|
+
context 'when in strict mode' do
|
835
|
+
it 'does not interpret the dot as any character' do
|
836
|
+
expect(DomainStrictUser.new(:email => 'user@example-com')).not_to be_valid
|
837
|
+
end
|
838
|
+
|
839
|
+
it 'is valid with matching domain' do
|
840
|
+
expect(DomainStrictUser.new(:email => 'user@example.com')).to be_valid
|
841
|
+
end
|
842
|
+
|
843
|
+
it 'is not valid with mismatched domain' do
|
844
|
+
expect(DomainStrictUser.new(:email => 'user@not-matching.io')).not_to be_valid
|
845
|
+
end
|
846
|
+
end
|
847
|
+
end
|
848
|
+
|
849
|
+
describe 'default_options' do
|
850
|
+
let(:email) { 'includes-whitespace-in-otherwise-valid-email@local' }
|
851
|
+
|
852
|
+
it 'validates using `:loose` mode' do
|
853
|
+
expect(DefaultUser.new(:email => email)).to be_valid
|
147
854
|
end
|
148
855
|
|
149
|
-
|
150
|
-
|
856
|
+
context 'when `email_validator/moderate` has been required' do
|
857
|
+
before { require 'email_validator/moderate' }
|
858
|
+
|
859
|
+
it 'validates using `:moderate` mode' do
|
860
|
+
expect(DefaultUser.new(:email => email)).not_to be_valid
|
861
|
+
end
|
862
|
+
end
|
863
|
+
|
864
|
+
context 'when `email_validator/strict` has been required' do
|
865
|
+
before { require 'email_validator/strict' }
|
866
|
+
|
867
|
+
it 'validate using `:strict` mode' do
|
868
|
+
expect(DefaultUser.new(:email => email)).to be_valid
|
869
|
+
end
|
151
870
|
end
|
152
871
|
end
|
153
872
|
end
|