remarkable_devise 1.0.0.alpha3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +70 -7
- data/lib/remarkable/devise/matchers/base_matcher.rb +17 -0
- data/lib/remarkable/devise/matchers/be_a_confirmable_matcher.rb +7 -3
- data/lib/remarkable/devise/matchers/be_a_database_authenticatable_matcher.rb +11 -4
- data/lib/remarkable/devise/matchers/be_a_lockable_matcher.rb +34 -0
- data/lib/remarkable/devise/matchers/be_a_registerable_matcher.rb +19 -0
- data/lib/remarkable/devise/matchers/be_a_rememberable_matcher.rb +7 -3
- data/lib/remarkable/devise/matchers/be_a_timeoutable_matcher.rb +23 -0
- data/lib/remarkable/devise/matchers/be_a_token_authenticatable_matcher.rb +7 -3
- data/lib/remarkable/devise/matchers/be_a_validatable_matcher.rb +7 -3
- data/lib/remarkable/devise/matchers/be_an_authenticatable_matcher.rb +19 -0
- data/lib/remarkable/devise/version.rb +1 -1
- data/locale/en.yml +70 -0
- data/remarkable_devise.gemspec +1 -1
- data/spec/example_models.rb +10 -2
- data/spec/examples/user_spec.rb +24 -0
- data/spec/matchers/be_a_confirmable_spec.rb +32 -4
- data/spec/matchers/be_a_database_authenticatable_spec.rb +55 -5
- data/spec/matchers/be_a_lockable_spec.rb +274 -0
- data/spec/matchers/be_a_registerable_spec.rb +27 -0
- data/spec/matchers/be_a_rememberable_spec.rb +81 -1
- data/spec/matchers/be_a_timeoutable_spec.rb +59 -0
- data/spec/matchers/be_a_token_authenticatable_spec.rb +33 -1
- data/spec/matchers/be_a_validatable_spec.rb +58 -5
- data/spec/matchers_spec.rb +23 -5
- data/spec/shared_examples.rb +122 -0
- data/spec/spec_helper.rb +13 -4
- metadata +23 -12
data/README.markdown
CHANGED
@@ -15,18 +15,81 @@ Add the require after the remarkable/active_record line in your spec_heplers.rb:
|
|
15
15
|
require 'remarkable/devise'
|
16
16
|
|
17
17
|
## Usage
|
18
|
+
|
19
|
+
Suppose that we require authentication of users. And we want to use Devise. The problem that we face a number of requirements:
|
20
|
+
|
21
|
+
* The user must be authorized by email or login
|
22
|
+
* Password should contain 8 to 20 characters
|
23
|
+
* After 3 unsuccessful attempts to authenticate, the account should be locked in 5 hours
|
24
|
+
* and much more
|
25
|
+
|
26
|
+
Following the BDD way, we first write a specs:
|
18
27
|
|
19
28
|
# spec/models/user_spec.rb
|
20
29
|
describe User do
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
30
|
+
it { should be_a_confirmable(:confirm_within => 2.days) }
|
31
|
+
it { should be_a_rememberable(:remember_for => 2.weeks) }
|
32
|
+
it { should be_a_validatable(:password_length => 8..20) }
|
33
|
+
it { should be_a_timeoutable(:timeout_in => 15.minutes) }
|
34
|
+
it { should be_a_token_authenticatable(:token_authentication_key => :auth_token) }
|
35
|
+
it { should be_a_trackable }
|
36
|
+
it { should be_a_registerable }
|
37
|
+
it { should be_a_recoverable }
|
38
|
+
|
39
|
+
should_be_a_lockable do |o|
|
40
|
+
o.lock_strategy = :failed_attempts
|
41
|
+
o.maximum_attempts => 3
|
42
|
+
o.unlock_strategy => :time
|
43
|
+
end
|
44
|
+
|
45
|
+
should_be_a_database_authenticatable do |o|
|
46
|
+
o.stretches = 20
|
47
|
+
o.encryptor = :clearance_sha1
|
48
|
+
o.params_authenticatable = false
|
49
|
+
o.authentication_keys = [:email, :login]
|
50
|
+
end
|
28
51
|
end
|
29
52
|
|
53
|
+
After number of steps (red/green/refactor) we will get the following
|
54
|
+
result (assuming that you are familiar with how to use Devise and
|
55
|
+
know what to do):
|
56
|
+
|
57
|
+
# app/models/user.rb
|
58
|
+
class User < ActiveRecord::Base
|
59
|
+
devise :database_authenticatable, :stretches => 20, :encryptor => :clearance_sha1,
|
60
|
+
:authentication_keys => [:email, :login], :params_authenticatable => false
|
61
|
+
|
62
|
+
devise :confirmable, :confirm_within => 2.days
|
63
|
+
devise :recoverable
|
64
|
+
devise :rememberable, :remember_for => 2.weeks, :extend_remember_period => true
|
65
|
+
devise :trackable
|
66
|
+
devise :validatable, :password_length => 8..20
|
67
|
+
devise :token_authenticatable, :token_authentication_key => :auth_token
|
68
|
+
devise :timeoutable, :timeout_in => 15.minutes
|
69
|
+
|
70
|
+
devise :lockable, :maximum_attempts => 3, :lock_strategy => :failed_attempts,
|
71
|
+
:unlock_strategy => :time, :unlock_in => 5.hours
|
72
|
+
|
73
|
+
devise :registerable
|
74
|
+
end
|
75
|
+
|
76
|
+
# rspec spec/models/user_spec.rb --format=documentation
|
77
|
+
User
|
78
|
+
should be a confirmable within 2 days
|
79
|
+
should be a rememberable with 14 days remember period and with extendable remember period
|
80
|
+
should be a validatable with password length 8..20
|
81
|
+
should be a timeoutable within 900 seconds
|
82
|
+
should be a token authenticatable with :auth_token as authentication key
|
83
|
+
should be a trackable
|
84
|
+
should be a registerable
|
85
|
+
should be a recoverable
|
86
|
+
should be a lockable with :failed_attempts lock strategy, with :time unlock strategy, with unlock in 5 hours, and with 3 maxumum attempts
|
87
|
+
should be a database authenticatable with [:email, :login] as authentication keys, without params authenticatable, with password stretches 20, and with :clearance_sha1 password encryptor
|
88
|
+
|
89
|
+
## Documentation
|
90
|
+
|
91
|
+
Coming soon
|
92
|
+
|
30
93
|
## See alse
|
31
94
|
|
32
95
|
* [http://github.com/remarkable/remarkable](http://github.com/remarkable/remarkable)
|
@@ -8,6 +8,23 @@ module Remarkable
|
|
8
8
|
subject_class.column_names.include?(column_name)
|
9
9
|
end
|
10
10
|
|
11
|
+
def options_match?
|
12
|
+
actual = get_devise_model_options(@options.keys)
|
13
|
+
|
14
|
+
return actual == @options, :actual => actual.inspect
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_devise_model_options(keys)
|
18
|
+
keys.inject({}) do |hash, key|
|
19
|
+
hash[key] = subject_class.send(key.to_sym)
|
20
|
+
hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def interpolation_options
|
25
|
+
{ :options => @options.inspect }
|
26
|
+
end
|
27
|
+
|
11
28
|
def method_missing(m, *args)
|
12
29
|
if m.to_s.match(/has_(.+)_column?/)
|
13
30
|
return has_column?($1)
|
@@ -2,8 +2,12 @@ module Remarkable
|
|
2
2
|
module Devise
|
3
3
|
module Matchers
|
4
4
|
class BeAConfirmableMatcher < Base
|
5
|
+
arguments
|
6
|
+
|
5
7
|
assertion :included?, :has_confirmation_token_column?, :has_confirmed_at_column?,
|
6
|
-
:has_confirmation_sent_at_column?
|
8
|
+
:has_confirmation_sent_at_column?, :options_match?
|
9
|
+
|
10
|
+
optional :confirm_within
|
7
11
|
|
8
12
|
protected
|
9
13
|
|
@@ -12,8 +16,8 @@ module Remarkable
|
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
15
|
-
def be_a_confirmable
|
16
|
-
BeAConfirmableMatcher.new
|
19
|
+
def be_a_confirmable(*args, &block)
|
20
|
+
BeAConfirmableMatcher.new(*args, &block).spec(self)
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
@@ -1,8 +1,15 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'be_an_authenticatable_matcher')
|
2
|
+
|
1
3
|
module Remarkable
|
2
4
|
module Devise
|
3
5
|
module Matchers
|
4
|
-
class BeADatabaseAuthenticatableMatcher <
|
5
|
-
|
6
|
+
class BeADatabaseAuthenticatableMatcher < BeAnAuthenticatableMatcher
|
7
|
+
arguments
|
8
|
+
|
9
|
+
assertion :included?, :has_email_column?, :has_encrypted_password_column?, :has_password_salt_column?,
|
10
|
+
:options_match?
|
11
|
+
|
12
|
+
optionals :stretches, :encryptor
|
6
13
|
|
7
14
|
protected
|
8
15
|
|
@@ -11,8 +18,8 @@ module Remarkable
|
|
11
18
|
end
|
12
19
|
end
|
13
20
|
|
14
|
-
def be_a_database_authenticatable
|
15
|
-
BeADatabaseAuthenticatableMatcher.new
|
21
|
+
def be_a_database_authenticatable(*args, &block)
|
22
|
+
BeADatabaseAuthenticatableMatcher.new(*args, &block).spec(self)
|
16
23
|
end
|
17
24
|
end
|
18
25
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module Devise
|
3
|
+
module Matchers
|
4
|
+
class BeALockableMatcher < Base
|
5
|
+
arguments
|
6
|
+
|
7
|
+
assertion :included?, :has_failed_attempts_column?, :has_unlock_token_column?, :has_locked_at_column?,
|
8
|
+
:options_match?
|
9
|
+
|
10
|
+
optionals :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def included?
|
15
|
+
subject_class.ancestors.include?(::Devise::Models::Lockable)
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_failed_attempts_column?
|
19
|
+
return true if subject_class.lock_strategy == :none
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_unlock_token_column?
|
24
|
+
return true unless [:email, :both].include?(subject_class.unlock_strategy)
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def be_a_lockable(*args, &block)
|
30
|
+
BeALockableMatcher.new(*args, &block).spec(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module Devise
|
3
|
+
module Matchers
|
4
|
+
class BeARegisterableMatcher < Base
|
5
|
+
assertion :included?
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def included?
|
10
|
+
subject_class.ancestors.include?(::Devise::Models::Registerable)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def be_a_registerable
|
15
|
+
BeARegisterableMatcher.new.spec(self)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,7 +2,11 @@ module Remarkable
|
|
2
2
|
module Devise
|
3
3
|
module Matchers
|
4
4
|
class BeARememberableMatcher < Base
|
5
|
-
|
5
|
+
arguments
|
6
|
+
|
7
|
+
assertions :included?, :has_remember_token_column?, :has_remember_created_at_column?, :options_match?
|
8
|
+
|
9
|
+
optionals :remember_for, :extend_remember_period, :cookie_domain
|
6
10
|
|
7
11
|
protected
|
8
12
|
|
@@ -11,8 +15,8 @@ module Remarkable
|
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
|
-
def be_a_rememberable
|
15
|
-
BeARememberableMatcher.new
|
18
|
+
def be_a_rememberable(*args, &block)
|
19
|
+
BeARememberableMatcher.new(*args, &block).spec(self)
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module Devise
|
3
|
+
module Matchers
|
4
|
+
class BeATimeoutableMatcher < Base
|
5
|
+
arguments
|
6
|
+
|
7
|
+
assertion :included?, :options_match?
|
8
|
+
|
9
|
+
optional :timeout_in
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def included?
|
14
|
+
subject_class.ancestors.include?(::Devise::Models::Timeoutable)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def be_a_timeoutable(*args, &block)
|
19
|
+
BeATimeoutableMatcher.new(*args, &block).spec(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -2,8 +2,12 @@ module Remarkable
|
|
2
2
|
module Devise
|
3
3
|
module Matchers
|
4
4
|
class BeATokenAuthenticatableMatcher < Base
|
5
|
-
|
5
|
+
arguments
|
6
|
+
|
7
|
+
assertions :included?, :has_authentication_token_column?, :options_match?
|
6
8
|
|
9
|
+
optionals :token_authentication_key
|
10
|
+
|
7
11
|
protected
|
8
12
|
|
9
13
|
def included?
|
@@ -11,8 +15,8 @@ module Remarkable
|
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
|
-
def be_a_token_authenticatable
|
15
|
-
BeATokenAuthenticatableMatcher.new
|
18
|
+
def be_a_token_authenticatable(*args, &block)
|
19
|
+
BeATokenAuthenticatableMatcher.new(*args, &block).spec(self)
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -2,7 +2,11 @@ module Remarkable
|
|
2
2
|
module Devise
|
3
3
|
module Matchers
|
4
4
|
class BeAValidatableMatcher < Base
|
5
|
-
|
5
|
+
arguments
|
6
|
+
|
7
|
+
assertion :included?, :options_match?
|
8
|
+
|
9
|
+
optionals :password_length, :email_regexp
|
6
10
|
|
7
11
|
protected
|
8
12
|
|
@@ -11,8 +15,8 @@ module Remarkable
|
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
|
-
def be_a_validatable
|
15
|
-
BeAValidatableMatcher.new
|
18
|
+
def be_a_validatable(*args, &block)
|
19
|
+
BeAValidatableMatcher.new(*args, &block).spec(self)
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module Devise
|
3
|
+
module Matchers
|
4
|
+
class BeAnAuthenticatableMatcher < Base
|
5
|
+
arguments
|
6
|
+
|
7
|
+
assertion :has_authenticatable_module_included?, :options_match?
|
8
|
+
|
9
|
+
optionals :authentication_keys, :params_authenticatable
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def has_authenticatable_module_included?
|
14
|
+
subject_class.ancestors.include?(::Devise::Models::Authenticatable)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/locale/en.yml
CHANGED
@@ -4,18 +4,37 @@ en:
|
|
4
4
|
be_a_database_authenticatable:
|
5
5
|
description: "be a database authenticatable"
|
6
6
|
expectations:
|
7
|
+
has_authenticatable_module_included: "%{subject_name} to have Devise :authenticatable model"
|
7
8
|
included: "%{subject_name} to include Devise :database_authenticatable model"
|
9
|
+
options_match: "%{subject_name} to be a database authenticatable with options %{options}, got %{actual}"
|
8
10
|
has_email_column: "%{subject_name} to have email column"
|
9
11
|
has_encrypted_password_column: "%{subject_name} to have encrypted_password column"
|
10
12
|
has_password_salt_column: "%{subject_name} to have password_salt column"
|
13
|
+
stretches_match: "%{subject_name} to have password stretches equal to %{stretches}, got %{actual}"
|
14
|
+
encryptor_match: "%{subject_name} to have %{encryptor} password encryptor, got %{actual}"
|
15
|
+
optionals:
|
16
|
+
stretches:
|
17
|
+
positive: "with password stretches %{inspect}"
|
18
|
+
encryptor:
|
19
|
+
positive: "with %{inspect} password encryptor"
|
20
|
+
authentication_keys:
|
21
|
+
positive: "with %{inspect} as authentication keys"
|
22
|
+
params_authenticatable:
|
23
|
+
positive: "with params authenticatable"
|
24
|
+
negative: "without params authenticatable"
|
11
25
|
|
12
26
|
be_a_confirmable:
|
13
27
|
description: "be a confirmable"
|
14
28
|
expectations:
|
15
29
|
included: "%{subject_name} to include Devise :confirmable model"
|
30
|
+
options_match: "%{subject_name} to be a confirmable with options %{options}, got %{actual}"
|
16
31
|
has_confirmation_token_column: "%{subject_name} to have confirmation_token column"
|
17
32
|
has_confirmed_at_column: "%{subject_name} to have confirmed_at column"
|
18
33
|
has_confirmation_sent_at_column: "%{subject_name} to have confirmation_sent_at column"
|
34
|
+
confirmation_period_matches: "%{subject_name} to have %{confirm_within} of confirmation period, got %{actual}"
|
35
|
+
optionals:
|
36
|
+
confirm_within:
|
37
|
+
positive: "within %{inspect}"
|
19
38
|
|
20
39
|
be_a_recoverable:
|
21
40
|
description: "be a recoverable"
|
@@ -29,6 +48,15 @@ en:
|
|
29
48
|
included: "%{subject_name} to include Devise :rememberable model"
|
30
49
|
has_remember_token_column: "%{subject_name} to have remember_token column"
|
31
50
|
has_remember_created_at_column: "%{subject_name} to have remember_created_at column"
|
51
|
+
options_match: "%{subject_name} to be rememberable with options %{options}, got %{actual}"
|
52
|
+
optionals:
|
53
|
+
remember_for:
|
54
|
+
positive: "with %{inspect} remember period"
|
55
|
+
extend_remember_period:
|
56
|
+
positive: "with extendable remember period"
|
57
|
+
negative: "without extandable remember period"
|
58
|
+
cookie_domain:
|
59
|
+
positive: "with %{inspect} cookie domain"
|
32
60
|
|
33
61
|
be_a_trackable:
|
34
62
|
description: "be a trackable"
|
@@ -44,9 +72,51 @@ en:
|
|
44
72
|
description: "be a validatable"
|
45
73
|
expectations:
|
46
74
|
included: "%{subject_name} to include Devise :validatable model"
|
75
|
+
options_match: "%{subject_name} to be a validatable with options %{options}, got %{actual}"
|
76
|
+
optionals:
|
77
|
+
password_length:
|
78
|
+
positive: "with password length %{inspect}"
|
79
|
+
email_regexp:
|
80
|
+
positive: "with email regexp %{inspect}"
|
47
81
|
|
48
82
|
be_a_token_authenticatable:
|
49
83
|
description: "be a token authenticatable"
|
50
84
|
expectations:
|
51
85
|
included: "%{subject_name} to include Devise :token_authenticatable model"
|
52
86
|
has_authentication_token_column: "%{subject_name} to have authentication_token column"
|
87
|
+
options_match: "%{subject_name} to be a token authenticatable with options %{options}, got %{actual}"
|
88
|
+
optionals:
|
89
|
+
token_authentication_key:
|
90
|
+
positive: "with %{inspect} as token authentication key"
|
91
|
+
|
92
|
+
be_a_timeoutable:
|
93
|
+
description: "be a timeoutable"
|
94
|
+
expectations:
|
95
|
+
included: "%{subject_name} to include Devise :timeoutable model"
|
96
|
+
options_match: "%{subject_name} to be a timeoutable with options %{options}, got %{actual}"
|
97
|
+
optionals:
|
98
|
+
timeout_in:
|
99
|
+
positive: "within %{inspect}"
|
100
|
+
|
101
|
+
be_a_lockable:
|
102
|
+
description: "be a lockable"
|
103
|
+
expectations:
|
104
|
+
included: "%{subject_name} to have Devise :lockable model"
|
105
|
+
has_failed_attempts_column: "%{subject_name} to have failed_attempts column"
|
106
|
+
has_unlock_token_column: "%{subject_name} to have unlock_token column"
|
107
|
+
has_locked_at_column: "%{subject_name} to have locked_at column"
|
108
|
+
options_match: "%{subject_name} to be a lockable with options %{options}, got %{actual}"
|
109
|
+
optionals:
|
110
|
+
maximum_attempts:
|
111
|
+
positive: "with %{inspect} maximum attempts"
|
112
|
+
lock_strategy:
|
113
|
+
positive: "with %{inspect} lock strategy"
|
114
|
+
unlock_strategy:
|
115
|
+
positive: "with %{inspect} unlock strategy"
|
116
|
+
unlock_in:
|
117
|
+
positive: "with unlock in %{inspect}"
|
118
|
+
|
119
|
+
be_a_registerable:
|
120
|
+
description: "be a registerable"
|
121
|
+
expectations:
|
122
|
+
included: "%{subject_name} to have Devise :registerable model"
|