pillowfort 0.1.2 → 0.2.1
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/Rakefile +10 -0
- data/app/controllers/pillowfort/concerns/controller_activation.rb +27 -0
- data/app/controllers/pillowfort/concerns/controller_authentication.rb +2 -9
- data/app/models/pillowfort/concerns/model_activation.rb +84 -0
- data/app/models/pillowfort/concerns/model_authentication.rb +10 -25
- data/app/models/pillowfort/concerns/model_password_reset.rb +62 -0
- data/lib/pillowfort/controller_methods.rb +12 -0
- data/lib/pillowfort/model_finder.rb +7 -0
- data/lib/pillowfort/token_generator.rb +19 -0
- data/lib/pillowfort/version.rb +1 -1
- data/spec/{dummy/spec/controllers → controllers}/accounts_controller_spec.rb +19 -1
- data/spec/dummy/app/controllers/accounts_controller.rb +2 -0
- data/spec/dummy/app/models/account.rb +2 -0
- data/spec/dummy/config/database.yml +5 -1
- data/spec/dummy/config/environments/development.rb +42 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20150210215727_add_password_reset_tokens.rb +8 -0
- data/spec/dummy/db/migrate/20150211185152_add_activation_token_to_account.rb +9 -0
- data/spec/dummy/db/migrate/20150413161345_add_auth_token_ttl_to_account.rb +7 -0
- data/spec/dummy/db/schema.rb +9 -3
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +15233 -1641
- data/spec/dummy/spec/spec_helper.rb +1 -10
- data/spec/factories/accounts.rb +19 -0
- data/spec/models/account_spec.rb +531 -0
- data/spec/{dummy/spec/rails_helper.rb → rails_helper.rb} +1 -1
- data/spec/spec_helper.rb +25 -0
- data/spec/{dummy/spec/support → support}/helpers/authentication_helper.rb +0 -0
- metadata +62 -17
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/spec/factories/accounts.rb +0 -10
- data/spec/dummy/spec/models/account_spec.rb +0 -276
@@ -15,26 +15,17 @@
|
|
15
15
|
#
|
16
16
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
17
17
|
RSpec.configure do |config|
|
18
|
+
require 'rspec/its'
|
18
19
|
# rspec-expectations config goes here. You can use an alternate
|
19
20
|
# assertion/expectation library such as wrong or the stdlib/minitest
|
20
21
|
# assertions if you prefer.
|
21
22
|
config.expect_with :rspec do |expectations|
|
22
|
-
# This option will default to `true` in RSpec 4. It makes the `description`
|
23
|
-
# and `failure_message` of custom matchers include text for helper methods
|
24
|
-
# defined using `chain`, e.g.:
|
25
|
-
# be_bigger_than(2).and_smaller_than(4).description
|
26
|
-
# # => "be bigger than 2 and smaller than 4"
|
27
|
-
# ...rather than:
|
28
|
-
# # => "be bigger than 2"
|
29
23
|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
30
24
|
end
|
31
25
|
|
32
26
|
# rspec-mocks config goes here. You can use an alternate test double
|
33
27
|
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
34
28
|
config.mock_with :rspec do |mocks|
|
35
|
-
# Prevents you from mocking or stubbing a method that does not exist on
|
36
|
-
# a real object. This is generally recommended, and will default to
|
37
|
-
# `true` in RSpec 4.
|
38
29
|
mocks.verify_partial_doubles = true
|
39
30
|
end
|
40
31
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
sequence :email do |n|
|
3
|
+
"foo.bar.#{n}@baz.org"
|
4
|
+
end
|
5
|
+
|
6
|
+
factory :account do
|
7
|
+
email
|
8
|
+
password { "SuperSafe123" }
|
9
|
+
activation_token { "thisismytoken" }
|
10
|
+
activation_token_expires_at { 1.hour.from_now }
|
11
|
+
activated_at nil
|
12
|
+
|
13
|
+
trait :activated do
|
14
|
+
activation_token nil
|
15
|
+
activation_token_expires_at nil
|
16
|
+
activated_at { Time.now }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,531 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
# ------------------------------------------------------------------------------
|
4
|
+
# Shared Examples
|
5
|
+
# ------------------------------------------------------------------------------
|
6
|
+
|
7
|
+
RSpec.shared_examples 'an auth token resetter' do
|
8
|
+
describe 'its affect on the auth_token' do
|
9
|
+
subject { account.auth_token }
|
10
|
+
|
11
|
+
describe 'before the call' do
|
12
|
+
it { should eq(auth_token) }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'after the call' do
|
16
|
+
before { call_the_method }
|
17
|
+
it { should_not eq(auth_token) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'its affect on the auth_token_expires_at' do
|
22
|
+
subject { account.auth_token_expires_at }
|
23
|
+
|
24
|
+
describe 'before the call' do
|
25
|
+
it { should eq(auth_token_expires_at) }
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'after the call' do
|
29
|
+
before { call_the_method }
|
30
|
+
it { should be > auth_token_expires_at }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# ------------------------------------------------------------------------------
|
36
|
+
# The Spec!
|
37
|
+
# ------------------------------------------------------------------------------
|
38
|
+
|
39
|
+
RSpec.describe Account, :type => :model do
|
40
|
+
|
41
|
+
describe 'its validations' do
|
42
|
+
before { account.save }
|
43
|
+
subject { account.errors.messages }
|
44
|
+
|
45
|
+
describe 'email validations' do
|
46
|
+
let(:account) { FactoryGirl.build(:account, email: email) }
|
47
|
+
|
48
|
+
context 'presence_of' do
|
49
|
+
let(:email) { nil }
|
50
|
+
|
51
|
+
it { should include(email: ["can't be blank"]) }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'uniqueness' do
|
55
|
+
let(:email) { 'foobar@baz.com' }
|
56
|
+
let(:dup_account) { FactoryGirl.build(:account, email: email) }
|
57
|
+
before { dup_account.save }
|
58
|
+
subject { dup_account.errors.messages}
|
59
|
+
|
60
|
+
it { should include(email: ["has already been taken"]) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'password validations' do
|
65
|
+
let(:account) { FactoryGirl.build(:account, password: password) }
|
66
|
+
|
67
|
+
context 'presence_of' do
|
68
|
+
let(:password) { nil }
|
69
|
+
|
70
|
+
it { should include(password: [/can't be blank/]) }
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'length of' do
|
74
|
+
context "when it's too short" do
|
75
|
+
let(:password) { "x"*3 }
|
76
|
+
|
77
|
+
it { should include(password: [/is too short/])}
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when it's too long" do
|
81
|
+
let(:password) { "x"*80 }
|
82
|
+
|
83
|
+
it { should include(password: [/is too long/])}
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the record is persisted" do
|
87
|
+
let(:password) { 'foobarbaz' }
|
88
|
+
before { account.save }
|
89
|
+
|
90
|
+
context "when the password is updated with a nil value" do
|
91
|
+
before {account.update_attribute :password, nil }
|
92
|
+
it { should be_empty }
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when the password is updated with a short value" do
|
96
|
+
before {account.update_attributes password: '3' }
|
97
|
+
it { should include(password: [/is too short/]) }
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when the password is updated with a short value" do
|
101
|
+
before {account.update_attributes password: "x"*80 }
|
102
|
+
it { should include(password: [/is too long/]) }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'activation validations' do
|
108
|
+
let(:activation_token) { "my_token" }
|
109
|
+
let(:activation_token_expires_at) { 1.hour.from_now }
|
110
|
+
let(:activated_at) { nil }
|
111
|
+
|
112
|
+
let(:account) {
|
113
|
+
FactoryGirl.build(:account,
|
114
|
+
activation_token: activation_token,
|
115
|
+
activation_token_expires_at: activation_token_expires_at,
|
116
|
+
activated_at: activated_at
|
117
|
+
)
|
118
|
+
}
|
119
|
+
|
120
|
+
before { account.save }
|
121
|
+
subject { account.errors.messages }
|
122
|
+
|
123
|
+
it { should be_empty }
|
124
|
+
|
125
|
+
context 'all fields set' do
|
126
|
+
let(:activated_at) { 1.day.ago }
|
127
|
+
|
128
|
+
it { should include( activation_token_expires_at: [/must be blank/] ) }
|
129
|
+
it { should include( activated_at: [/must be blank/] ) }
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'no fields set' do
|
133
|
+
let(:activation_token) { nil }
|
134
|
+
let(:activation_token_expires_at) { nil }
|
135
|
+
|
136
|
+
it { should include( activation_token: [/can't be blank/] ) }
|
137
|
+
it { should include( activated_at: [/can't be blank/] ) }
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'duplicate activation token' do
|
141
|
+
let(:dup_account) {
|
142
|
+
FactoryGirl.build(:account, activation_token: activation_token)
|
143
|
+
}
|
144
|
+
before { dup_account.save }
|
145
|
+
subject { dup_account.errors.messages }
|
146
|
+
|
147
|
+
it { should include activation_token: [/has already been taken/] }
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'account activated under the old rules...' do
|
151
|
+
let(:activation_token) { nil }
|
152
|
+
let(:activated_at) { 1.day.ago }
|
153
|
+
|
154
|
+
it { should_not have_key :activation_token }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe 'the instance methods' do
|
161
|
+
let(:account) {
|
162
|
+
FactoryGirl.create :account,
|
163
|
+
auth_token: auth_token,
|
164
|
+
auth_token_expires_at: auth_token_expires_at,
|
165
|
+
auth_token_ttl: auth_token_ttl,
|
166
|
+
password_reset_token: password_reset_token,
|
167
|
+
password_reset_token_expires_at: password_reset_token_expires_at,
|
168
|
+
activation_token: activation_token,
|
169
|
+
activation_token_expires_at: activation_token_expires_at
|
170
|
+
}
|
171
|
+
|
172
|
+
let(:auth_token) { 'abc123def456' }
|
173
|
+
let(:auth_token_expires_at) { 1.day.from_now }
|
174
|
+
let(:auth_token_ttl) { 1.hour }
|
175
|
+
let(:password_reset_token) { '123abc456def' }
|
176
|
+
let(:password_reset_token_expires_at) { 1.hour.from_now }
|
177
|
+
let(:activation_token) { 'activateme' }
|
178
|
+
let(:activation_token_expires_at) { 1.hour.from_now }
|
179
|
+
|
180
|
+
describe '#ensure_auth_token' do
|
181
|
+
subject { account.auth_token }
|
182
|
+
before { account.ensure_auth_token }
|
183
|
+
|
184
|
+
context 'when the token is nil' do
|
185
|
+
let(:auth_token) { nil }
|
186
|
+
it { should_not be_nil }
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'when the token is not nil' do
|
190
|
+
let(:auth_token) { 'deadbeef' }
|
191
|
+
it { should eq('deadbeef') }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe '#reset_auth_token' do
|
196
|
+
let(:call_the_method) { account.reset_auth_token }
|
197
|
+
it_behaves_like 'an auth token resetter'
|
198
|
+
|
199
|
+
describe 'its persistence' do
|
200
|
+
subject { account }
|
201
|
+
after { call_the_method }
|
202
|
+
it { should_not receive(:save) }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe '#reset_auth_token!' do
|
207
|
+
let(:call_the_method) { account.reset_auth_token! }
|
208
|
+
it_behaves_like 'an auth token resetter'
|
209
|
+
|
210
|
+
describe 'its persistence' do
|
211
|
+
subject { account }
|
212
|
+
after { call_the_method }
|
213
|
+
it { should receive(:save) }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe '#auth_token_expired?' do
|
218
|
+
subject { account.auth_token_expired? }
|
219
|
+
|
220
|
+
context 'when the token expiration is in the future' do
|
221
|
+
let(:auth_token_expires_at) { 1.minute.from_now }
|
222
|
+
it { should be_falsey }
|
223
|
+
end
|
224
|
+
|
225
|
+
context 'when the token expiration is in the past' do
|
226
|
+
let(:auth_token_expires_at) { 1.minute.ago }
|
227
|
+
it { should be_truthy }
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe '#password=' do
|
232
|
+
let!(:current_password) { account.password.to_s }
|
233
|
+
subject { account.password.to_s }
|
234
|
+
|
235
|
+
describe 'before the call' do
|
236
|
+
it { should == (current_password) }
|
237
|
+
end
|
238
|
+
|
239
|
+
describe 'after the call' do
|
240
|
+
before { account.password = 'fudge_knuckles_45' }
|
241
|
+
it { should_not eq(current_password) }
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# ------------------------------------------------------------------------
|
246
|
+
# Password reset tokens
|
247
|
+
# ------------------------------------------------------------------------
|
248
|
+
describe '#password_token_expired?' do
|
249
|
+
subject { account.password_token_expired? }
|
250
|
+
describe 'an unexpired token' do
|
251
|
+
it { should be_falsey }
|
252
|
+
end
|
253
|
+
|
254
|
+
describe 'an expired token' do
|
255
|
+
let(:password_reset_token_expires_at) { 1.hour.ago }
|
256
|
+
it { should be_truthy }
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
shared_examples_for 'password token creator' do
|
261
|
+
describe '#password_reset_token' do
|
262
|
+
subject { account.password_reset_token }
|
263
|
+
it { should_not eq(password_reset_token) }
|
264
|
+
end
|
265
|
+
|
266
|
+
describe '#password_reset_token_expires_at' do
|
267
|
+
subject { account.password_reset_token_expires_at }
|
268
|
+
it { should_not eq(password_reset_token_expires_at) }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe '#create_password_reset_token with default expiration time' do
|
273
|
+
before { account.create_password_reset_token }
|
274
|
+
it_behaves_like 'password token creator'
|
275
|
+
|
276
|
+
describe '#password_reset_token_expires_at' do
|
277
|
+
subject { account.password_reset_token_expires_at }
|
278
|
+
it { should be_within(5.seconds).of 1.hour.from_now }
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
describe '#create_password_reset_token with specific expiration time' do
|
283
|
+
before { account.create_password_reset_token(expiry: 10.minutes.from_now) }
|
284
|
+
it_behaves_like 'password token creator'
|
285
|
+
describe '#password_reset_token_expires_at' do
|
286
|
+
subject { account.password_reset_token_expires_at }
|
287
|
+
it {should be_within(5.seconds).of 10.minutes.from_now }
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe '#clear_password_reset_token' do
|
292
|
+
before { account.clear_password_reset_token }
|
293
|
+
subject { account }
|
294
|
+
its(:password_reset_token) { should be_blank }
|
295
|
+
its(:password_reset_token_expires_at) { should be_blank }
|
296
|
+
its(:password_token_expired?) { should be_truthy }
|
297
|
+
end
|
298
|
+
|
299
|
+
# ------------------------------------------------------------------------
|
300
|
+
# Activation
|
301
|
+
# ------------------------------------------------------------------------
|
302
|
+
|
303
|
+
describe '#actived?' do
|
304
|
+
subject { account }
|
305
|
+
it { should_not be_activated }
|
306
|
+
its(:activated_at) { should be_blank }
|
307
|
+
its(:activation_token) { should eq(activation_token) }
|
308
|
+
its(:activation_token_expires_at) { should eq(activation_token_expires_at) }
|
309
|
+
its(:activation_token_expired?) { should be_falsey }
|
310
|
+
|
311
|
+
context 'already activated' do
|
312
|
+
before do
|
313
|
+
subject.activate!
|
314
|
+
end
|
315
|
+
|
316
|
+
it { should be_activated }
|
317
|
+
its(:activated_at) { should be_within(5.seconds).of Time.now }
|
318
|
+
its(:activation_token) { should_not be_blank }
|
319
|
+
its(:activation_token_expires_at) { should be_blank }
|
320
|
+
its(:activation_token_expired?) { should be_truthy }
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
describe '#create_activation_token' do
|
325
|
+
subject { account }
|
326
|
+
before { subject.create_activation_token }
|
327
|
+
|
328
|
+
its(:activation_token) { should_not be_blank }
|
329
|
+
its(:activation_token_expires_at) { should be_within(5.seconds).of 1.hour.from_now }
|
330
|
+
its(:activation_token_expired?) { should be_falsey }
|
331
|
+
|
332
|
+
context 'with specific expiration' do
|
333
|
+
before { subject.create_activation_token(expiry: 5.minutes.from_now) }
|
334
|
+
its(:activation_token_expires_at) { should be_within(5.seconds).of 5.minutes.from_now }
|
335
|
+
its(:activation_token_expired?) { should be_falsey }
|
336
|
+
end
|
337
|
+
|
338
|
+
context 'with expiration in the past' do
|
339
|
+
before { subject.create_activation_token(expiry: 10.minutes.ago) }
|
340
|
+
its(:activation_token_expired?) { should be_truthy }
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
describe 'the class methods' do
|
346
|
+
let(:email) { 'foobar@baz.com' }
|
347
|
+
let(:token) { 'deadbeef' }
|
348
|
+
let(:password) { 'admin4lolz' }
|
349
|
+
let(:auth_token_expires_at) { 1.day.from_now }
|
350
|
+
let(:auth_token_ttl) { 1.day }
|
351
|
+
let(:activation_token) { 'activateme' }
|
352
|
+
let(:activation_token_expires_at) { 1.hour.from_now }
|
353
|
+
let(:password_reset_token) { 'resetme' }
|
354
|
+
let(:password_reset_token_expires_at) { 1.hour.from_now }
|
355
|
+
|
356
|
+
let!(:account) {
|
357
|
+
FactoryGirl.create :account,
|
358
|
+
email: email,
|
359
|
+
auth_token: token,
|
360
|
+
password: password,
|
361
|
+
auth_token_expires_at: auth_token_expires_at,
|
362
|
+
auth_token_ttl: auth_token_ttl,
|
363
|
+
password_reset_token: password_reset_token,
|
364
|
+
password_reset_token_expires_at: password_reset_token_expires_at,
|
365
|
+
activation_token: activation_token,
|
366
|
+
activation_token_expires_at: activation_token_expires_at
|
367
|
+
}
|
368
|
+
|
369
|
+
describe '.authenticate_securely' do
|
370
|
+
let(:email_param) { email }
|
371
|
+
let(:token_param) { token }
|
372
|
+
let(:block) { ->(resource) {} }
|
373
|
+
|
374
|
+
subject { Account.authenticate_securely(email_param, token_param, &block) }
|
375
|
+
|
376
|
+
context 'when email is nil' do
|
377
|
+
let(:email_param) { nil }
|
378
|
+
it { should be_falsey }
|
379
|
+
end
|
380
|
+
|
381
|
+
context 'when token is nil' do
|
382
|
+
let(:token_param) { nil }
|
383
|
+
it { should be_falsey }
|
384
|
+
end
|
385
|
+
|
386
|
+
context 'when email and token are provided' do
|
387
|
+
|
388
|
+
context 'email case-sensitivity' do
|
389
|
+
describe 'when an uppercased email address is provided' do
|
390
|
+
let(:email_param) { email.upcase }
|
391
|
+
|
392
|
+
it 'should yield the matched account' do
|
393
|
+
expect { |b| Account.authenticate_securely(email_param, token_param, &b) }.to yield_with_args(account)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe 'when a downcased email address is provided' do
|
398
|
+
let(:email_param) { email.downcase }
|
399
|
+
|
400
|
+
it 'should yield the matched account' do
|
401
|
+
expect { |b| Account.authenticate_securely(email_param, token_param, &b) }.to yield_with_args(account)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
context 'when the resource has a longer auth token ttl date than usual...' do
|
406
|
+
let(:now) { Time.now }
|
407
|
+
let(:auth_token_ttl) { 1.week }
|
408
|
+
before { Account.authenticate_securely(email_param, token_param, &block) }
|
409
|
+
subject { account.reload }
|
410
|
+
|
411
|
+
its(:auth_token_expires_at) { should be_within(2.seconds).of(now + auth_token_ttl)}
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
context 'when the resource is located' do
|
416
|
+
|
417
|
+
context 'when the auth_token is expired' do
|
418
|
+
let(:auth_token_expires_at) { 1.week.ago }
|
419
|
+
|
420
|
+
it 'should reset the account auth_token' do
|
421
|
+
allow(Account).to receive(:find_by_email_case_insensitive) { account }
|
422
|
+
expect(account).to receive(:reset_auth_token!)
|
423
|
+
subject
|
424
|
+
end
|
425
|
+
|
426
|
+
it { should be_falsey }
|
427
|
+
end
|
428
|
+
|
429
|
+
context 'when the auth_token is current' do
|
430
|
+
|
431
|
+
context 'when the auth_token matches' do
|
432
|
+
it 'should yield the matched account' do
|
433
|
+
expect { |b| Account.authenticate_securely(email_param, token_param, &b) }.to yield_with_args(account)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
context 'when the auth_token does not match' do
|
438
|
+
it { should be_falsey }
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
context 'when the resource is not located' do
|
444
|
+
it { should be_falsey }
|
445
|
+
end
|
446
|
+
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
describe '.find_and_activate' do
|
451
|
+
let(:email_param) { email }
|
452
|
+
let(:token_param) { activation_token }
|
453
|
+
let(:block) { ->(resource) {} }
|
454
|
+
|
455
|
+
subject { Account.find_and_activate(email_param, token_param) }
|
456
|
+
|
457
|
+
context 'when the resource is located' do
|
458
|
+
context 'when the token matches' do
|
459
|
+
it 'should activate the matched account' do
|
460
|
+
expect_any_instance_of(Account).to receive(:activate!)
|
461
|
+
subject
|
462
|
+
end
|
463
|
+
|
464
|
+
it 'should yield the matched account' do
|
465
|
+
expect { |b| Account.find_and_activate(email_param, token_param, &b) }.to yield_with_args(account)
|
466
|
+
end
|
467
|
+
|
468
|
+
context "when the activation_token is expired" do
|
469
|
+
let(:activation_token_expires_at) { 1.day.ago }
|
470
|
+
it { should be_falsey }
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
context "when the activation_token doesn't match" do
|
475
|
+
let(:token_param) { 'notmytoken' }
|
476
|
+
it { should be_falsey }
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
context "when the email doesn't match" do
|
481
|
+
let(:email_param) { 'notmyemail@gmail.com' }
|
482
|
+
it { should be_falsey }
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
describe '.find_and_validate_password_reset_token' do
|
487
|
+
let(:email_param) { email }
|
488
|
+
let(:token_param) { password_reset_token }
|
489
|
+
subject { Account.find_and_validate_password_reset_token(email_param, token_param) }
|
490
|
+
|
491
|
+
context "when the email doesn't match" do
|
492
|
+
let(:email_param) { "bad_actor@gmail.com" }
|
493
|
+
it { should be_falsey }
|
494
|
+
end
|
495
|
+
|
496
|
+
context "when the token doesn't match" do
|
497
|
+
let(:token_param) { 'notmytoken' }
|
498
|
+
it { should be_falsey }
|
499
|
+
end
|
500
|
+
|
501
|
+
it 'should yield the matched account' do
|
502
|
+
expect { |b| Account.find_and_validate_password_reset_token(email_param, token_param, &b) }.to yield_with_args(account)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
describe '.find_and_authenticate' do
|
507
|
+
let(:email_param) { email }
|
508
|
+
let(:password_param) { password }
|
509
|
+
|
510
|
+
subject { Account.find_and_authenticate(email_param, password_param) }
|
511
|
+
|
512
|
+
|
513
|
+
context 'when the resource is located' do
|
514
|
+
|
515
|
+
context 'when the password matches' do
|
516
|
+
it { should eq(account) }
|
517
|
+
end
|
518
|
+
|
519
|
+
context 'when the password does not match' do
|
520
|
+
let(:password_param) { "#{password}_bad" }
|
521
|
+
it { should be_falsey }
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
context 'when the resource is not located' do
|
526
|
+
let(:email_param) { "#{email}_evil" }
|
527
|
+
it { should be_falsey }
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|