devise 3.3.0 → 3.5.10
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of devise might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +29 -20
- data/CHANGELOG.md +219 -102
- data/CODE_OF_CONDUCT.md +22 -0
- data/CONTRIBUTING.md +2 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +101 -80
- data/MIT-LICENSE +1 -1
- data/README.md +87 -43
- data/Rakefile +2 -1
- data/app/controllers/devise/confirmations_controller.rb +5 -1
- data/app/controllers/devise/omniauth_callbacks_controller.rb +4 -0
- data/app/controllers/devise/passwords_controller.rb +14 -4
- data/app/controllers/devise/registrations_controller.rb +10 -11
- data/app/controllers/devise/sessions_controller.rb +7 -2
- data/app/controllers/devise/unlocks_controller.rb +3 -0
- data/app/controllers/devise_controller.rb +44 -21
- data/app/mailers/devise/mailer.rb +4 -0
- data/app/views/devise/confirmations/new.html.erb +7 -3
- data/app/views/devise/mailer/password_change.html.erb +3 -0
- data/app/views/devise/passwords/edit.html.erb +14 -5
- data/app/views/devise/passwords/new.html.erb +7 -3
- data/app/views/devise/registrations/edit.html.erb +19 -9
- data/app/views/devise/registrations/new.html.erb +18 -7
- data/app/views/devise/sessions/new.html.erb +15 -6
- data/app/views/devise/shared/{_links.erb → _links.html.erb} +1 -1
- data/app/views/devise/unlocks/new.html.erb +7 -3
- data/config/locales/en.yml +4 -2
- data/devise.gemspec +2 -2
- data/gemfiles/Gemfile.rails-3.2-stable.lock +54 -48
- data/gemfiles/Gemfile.rails-4.0-stable +1 -0
- data/gemfiles/Gemfile.rails-4.0-stable.lock +63 -59
- data/gemfiles/{Gemfile.rails-head → Gemfile.rails-4.1-stable} +3 -5
- data/gemfiles/Gemfile.rails-4.1-stable.lock +171 -0
- data/gemfiles/Gemfile.rails-4.2-stable +30 -0
- data/gemfiles/Gemfile.rails-4.2-stable.lock +193 -0
- data/lib/devise/controllers/helpers.rb +12 -6
- data/lib/devise/controllers/rememberable.rb +9 -2
- data/lib/devise/controllers/sign_in_out.rb +2 -8
- data/lib/devise/controllers/store_location.rb +3 -1
- data/lib/devise/controllers/url_helpers.rb +7 -9
- data/lib/devise/encryptor.rb +22 -0
- data/lib/devise/failure_app.rb +56 -14
- data/lib/devise/hooks/timeoutable.rb +5 -7
- data/lib/devise/mapping.rb +2 -1
- data/lib/devise/models/authenticatable.rb +28 -28
- data/lib/devise/models/confirmable.rb +51 -17
- data/lib/devise/models/database_authenticatable.rb +17 -11
- data/lib/devise/models/lockable.rb +7 -3
- data/lib/devise/models/recoverable.rb +23 -15
- data/lib/devise/models/rememberable.rb +56 -22
- data/lib/devise/models/timeoutable.rb +0 -6
- data/lib/devise/models/trackable.rb +1 -2
- data/lib/devise/models/validatable.rb +3 -3
- data/lib/devise/models.rb +1 -1
- data/lib/devise/rails/routes.rb +33 -27
- data/lib/devise/rails.rb +1 -1
- data/lib/devise/strategies/authenticatable.rb +8 -6
- data/lib/devise/strategies/database_authenticatable.rb +2 -1
- data/lib/devise/strategies/rememberable.rb +13 -3
- data/lib/devise/test_helpers.rb +2 -2
- data/lib/devise/version.rb +1 -1
- data/lib/devise.rb +39 -37
- data/lib/generators/active_record/devise_generator.rb +2 -1
- data/lib/generators/active_record/templates/migration.rb +1 -1
- data/lib/generators/active_record/templates/migration_existing.rb +1 -1
- data/lib/generators/devise/controllers_generator.rb +44 -0
- data/lib/generators/devise/views_generator.rb +14 -3
- data/lib/generators/templates/controllers/README +14 -0
- data/lib/generators/templates/controllers/confirmations_controller.rb +28 -0
- data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +28 -0
- data/lib/generators/templates/controllers/passwords_controller.rb +32 -0
- data/lib/generators/templates/controllers/registrations_controller.rb +60 -0
- data/lib/generators/templates/controllers/sessions_controller.rb +25 -0
- data/lib/generators/templates/controllers/unlocks_controller.rb +28 -0
- data/lib/generators/templates/devise.rb +19 -13
- data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
- data/lib/generators/templates/markerb/password_change.markerb +3 -0
- data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
- data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
- data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +1 -1
- data/lib/generators/templates/simple_form_for/registrations/new.html.erb +1 -1
- data/lib/generators/templates/simple_form_for/sessions/new.html.erb +2 -2
- data/test/controllers/custom_registrations_controller_test.rb +6 -1
- data/test/controllers/helper_methods_test.rb +21 -0
- data/test/controllers/helpers_test.rb +5 -0
- data/test/controllers/inherited_controller_i18n_messages_test.rb +51 -0
- data/test/controllers/internal_helpers_test.rb +10 -4
- data/test/controllers/load_hooks_controller_test.rb +19 -0
- data/test/controllers/passwords_controller_test.rb +1 -1
- data/test/controllers/sessions_controller_test.rb +3 -3
- data/test/controllers/url_helpers_test.rb +6 -0
- data/test/devise_test.rb +3 -3
- data/test/failure_app_test.rb +47 -0
- data/test/generators/controllers_generator_test.rb +48 -0
- data/test/generators/views_generator_test.rb +8 -1
- data/test/helpers/devise_helper_test.rb +9 -12
- data/test/integration/authenticatable_test.rb +1 -1
- data/test/integration/database_authenticatable_test.rb +11 -0
- data/test/integration/http_authenticatable_test.rb +1 -1
- data/test/integration/omniauthable_test.rb +12 -10
- data/test/integration/recoverable_test.rb +13 -0
- data/test/integration/rememberable_test.rb +50 -3
- data/test/integration/timeoutable_test.rb +13 -18
- data/test/mailers/confirmation_instructions_test.rb +1 -1
- data/test/mapping_test.rb +7 -0
- data/test/models/authenticatable_test.rb +10 -0
- data/test/models/confirmable_test.rb +99 -42
- data/test/models/database_authenticatable_test.rb +20 -0
- data/test/models/lockable_test.rb +45 -17
- data/test/models/recoverable_test.rb +62 -7
- data/test/models/rememberable_test.rb +68 -97
- data/test/models/validatable_test.rb +5 -5
- data/test/models_test.rb +15 -6
- data/test/rails_app/app/active_record/user_without_email.rb +8 -0
- data/test/rails_app/app/controllers/admins_controller.rb +0 -5
- data/test/rails_app/app/controllers/custom/registrations_controller.rb +10 -0
- data/test/rails_app/app/mailers/users/from_proc_mailer.rb +3 -0
- data/test/rails_app/app/mailers/users/mailer.rb +0 -9
- data/test/rails_app/app/mailers/users/reply_to_mailer.rb +4 -0
- data/test/rails_app/app/mongoid/user_without_email.rb +33 -0
- data/test/rails_app/config/application.rb +1 -1
- data/test/rails_app/config/environments/production.rb +6 -2
- data/test/rails_app/config/environments/test.rb +7 -2
- data/test/rails_app/config/initializers/devise.rb +12 -15
- data/test/rails_app/config/routes.rb +6 -3
- data/test/rails_app/db/migrate/20100401102949_create_tables.rb +2 -2
- data/test/rails_app/lib/shared_user.rb +1 -1
- data/test/rails_app/lib/shared_user_without_email.rb +26 -0
- data/test/rails_test.rb +9 -0
- data/test/support/helpers.rb +13 -6
- data/test/support/integration.rb +2 -2
- data/test/test_helper.rb +5 -0
- data/test/test_helpers_test.rb +22 -7
- data/test/test_models.rb +2 -2
- data/test/time_helpers.rb +137 -0
- metadata +58 -8
- data/gemfiles/Gemfile.rails-head.lock +0 -190
@@ -31,7 +31,7 @@ Devise.setup do |config|
|
|
31
31
|
# session. If you need permissions, you should implement that in a before filter.
|
32
32
|
# You can also supply hash where the value is a boolean expliciting if authentication
|
33
33
|
# should be aborted or not if the value is not present. By default is empty.
|
34
|
-
# config.authentication_keys = [
|
34
|
+
# config.authentication_keys = [:email]
|
35
35
|
|
36
36
|
# Configure parameters from the request object used for authentication. Each entry
|
37
37
|
# given should be a request method and it will automatically be passed to
|
@@ -43,12 +43,12 @@ Devise.setup do |config|
|
|
43
43
|
# Configure which authentication keys should be case-insensitive.
|
44
44
|
# These keys will be downcased upon creating or modifying a user and when used
|
45
45
|
# to authenticate or find a user. Default is :email.
|
46
|
-
config.case_insensitive_keys = [
|
46
|
+
config.case_insensitive_keys = [:email]
|
47
47
|
|
48
48
|
# Configure which authentication keys should have whitespace stripped.
|
49
49
|
# These keys will have whitespace before and after removed upon creating or
|
50
50
|
# modifying a user and when used to authenticate or find a user. Default is :email.
|
51
|
-
config.strip_whitespace_keys = [
|
51
|
+
config.strip_whitespace_keys = [:email]
|
52
52
|
|
53
53
|
# Tell if authentication through request.params is enabled. True by default.
|
54
54
|
# config.params_authenticatable = true
|
@@ -77,21 +77,18 @@ Devise.setup do |config|
|
|
77
77
|
# config.allow_unconfirmed_access_for = 2.days
|
78
78
|
|
79
79
|
# Defines which key will be used when confirming an account
|
80
|
-
# config.confirmation_keys = [
|
80
|
+
# config.confirmation_keys = [:email]
|
81
81
|
|
82
82
|
# ==> Configuration for :rememberable
|
83
83
|
# The time the user will be remembered without asking for credentials again.
|
84
84
|
# config.remember_for = 2.weeks
|
85
85
|
|
86
|
-
# If true, a valid remember token can be re-used between multiple browsers.
|
87
|
-
# config.remember_across_browsers = true
|
88
|
-
|
89
86
|
# If true, extends the user's remember period when remembered via cookie.
|
90
87
|
# config.extend_remember_period = false
|
91
88
|
|
92
89
|
# ==> Configuration for :validatable
|
93
|
-
# Range for password length. Default is 8..
|
94
|
-
# config.password_length = 8..
|
90
|
+
# Range for password length. Default is 8..72.
|
91
|
+
# config.password_length = 8..72
|
95
92
|
|
96
93
|
# Regex to use to validate the email address
|
97
94
|
# config.email_regexp = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
|
@@ -108,7 +105,7 @@ Devise.setup do |config|
|
|
108
105
|
# config.lock_strategy = :failed_attempts
|
109
106
|
|
110
107
|
# Defines which key will be used when locking and unlocking an account
|
111
|
-
# config.unlock_keys = [
|
108
|
+
# config.unlock_keys = [:email]
|
112
109
|
|
113
110
|
# Defines which strategy will be used to unlock an account.
|
114
111
|
# :email = Sends an unlock link to the user email
|
@@ -127,20 +124,20 @@ Devise.setup do |config|
|
|
127
124
|
# ==> Configuration for :recoverable
|
128
125
|
#
|
129
126
|
# Defines which key will be used when recovering the password for an account
|
130
|
-
# config.reset_password_keys = [
|
127
|
+
# config.reset_password_keys = [:email]
|
131
128
|
|
132
129
|
# Time interval you can reset your password with a reset password key.
|
133
130
|
# Don't put a too small interval or your users won't have the time to
|
134
131
|
# change their passwords.
|
135
132
|
config.reset_password_within = 2.hours
|
136
133
|
|
134
|
+
# When set to false, does not sign a user in automatically after their password is
|
135
|
+
# reset. Defaults to true, so a user is signed in automatically after a reset.
|
136
|
+
# config.sign_in_after_reset_password = true
|
137
|
+
|
137
138
|
# Setup a pepper to generate the encrypted password.
|
138
139
|
config.pepper = "d142367154e5beacca404b1a6a4f8bc52c6fdcfa3ccc3cf8eb49f3458a688ee6ac3b9fae488432a3bfca863b8a90008368a9f3a3dfbe5a962e64b6ab8f3a3a1a"
|
139
140
|
|
140
|
-
# ==> Configuration for :token_authenticatable
|
141
|
-
# Defines name of the authentication token params key
|
142
|
-
# config.token_authentication_key = :auth_token
|
143
|
-
|
144
141
|
# ==> Scopes configuration
|
145
142
|
# Turn scoped views on. Before rendering "sessions/new", it will first check for
|
146
143
|
# "users/sessions/new". It's turned off by default because it's slower if you
|
@@ -13,9 +13,7 @@ Rails.application.routes.draw do
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
resources :admins, only: [:index]
|
17
|
-
get :expire, on: :member
|
18
|
-
end
|
16
|
+
resources :admins, only: [:index]
|
19
17
|
|
20
18
|
# Users scope
|
21
19
|
devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
|
@@ -30,6 +28,11 @@ Rails.application.routes.draw do
|
|
30
28
|
router_name: :fake_engine,
|
31
29
|
module: :devise
|
32
30
|
|
31
|
+
devise_for :user_without_email,
|
32
|
+
class_name: 'UserWithoutEmail',
|
33
|
+
router_name: :main_app,
|
34
|
+
module: :devise
|
35
|
+
|
33
36
|
as :user do
|
34
37
|
get "/as/sign_in", to: "devise/sessions#new"
|
35
38
|
end
|
@@ -33,7 +33,7 @@ class CreateTables < ActiveRecord::Migration
|
|
33
33
|
t.string :unlock_token # Only if unlock strategy is :email or :both
|
34
34
|
t.datetime :locked_at
|
35
35
|
|
36
|
-
t.timestamps
|
36
|
+
t.timestamps null: false
|
37
37
|
end
|
38
38
|
|
39
39
|
create_table :admins do |t|
|
@@ -60,7 +60,7 @@ class CreateTables < ActiveRecord::Migration
|
|
60
60
|
## Attribute for testing route blocks
|
61
61
|
t.boolean :active, default: false
|
62
62
|
|
63
|
-
t.timestamps
|
63
|
+
t.timestamps null: false
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
@@ -4,7 +4,7 @@ module SharedUser
|
|
4
4
|
included do
|
5
5
|
devise :database_authenticatable, :confirmable, :lockable, :recoverable,
|
6
6
|
:registerable, :rememberable, :timeoutable,
|
7
|
-
:trackable, :validatable, :omniauthable, password_length: 7..
|
7
|
+
:trackable, :validatable, :omniauthable, password_length: 7..72
|
8
8
|
|
9
9
|
attr_accessor :other_key
|
10
10
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SharedUserWithoutEmail
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
# NOTE: This is missing :validatable and :confirmable, as they both require
|
6
|
+
# an email field at the moment. It is also missing :omniauthable because that
|
7
|
+
# adds unnecessary complexity to the setup
|
8
|
+
devise :database_authenticatable, :lockable, :recoverable,
|
9
|
+
:registerable, :rememberable, :timeoutable,
|
10
|
+
:trackable
|
11
|
+
end
|
12
|
+
|
13
|
+
# This test stub is a bit rubbish because it's tied very closely to the
|
14
|
+
# implementation where we care about this one case. However, completely
|
15
|
+
# removing the email field breaks "recoverable" tests completely, so we are
|
16
|
+
# just taking the approach here that "email" is something that is a not an
|
17
|
+
# ActiveRecord field.
|
18
|
+
def email_changed?
|
19
|
+
raise NoMethodError
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_to?(method_name, include_all=false)
|
23
|
+
return false if method_name.to_sym == :email_changed?
|
24
|
+
super(method_name, include_all)
|
25
|
+
end
|
26
|
+
end
|
data/test/rails_test.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RailsTest < ActiveSupport::TestCase
|
4
|
+
test 'correct initializer position' do
|
5
|
+
initializer = Devise::Engine.initializers.detect { |i| i.name == 'devise.omniauth' }
|
6
|
+
assert_equal :load_config_initializers, initializer.after
|
7
|
+
assert_equal :build_middleware_stack, initializer.before
|
8
|
+
end
|
9
|
+
end
|
data/test/support/helpers.rb
CHANGED
@@ -8,12 +8,15 @@ class ActiveSupport::TestCase
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def store_translations(locale, translations, &block)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
# Calling 'available_locales' before storing the translations to ensure
|
12
|
+
# that the I18n backend will be initialized before we store our custom
|
13
|
+
# translations, so they will always override the translations for the
|
14
|
+
# YML file.
|
15
|
+
I18n.available_locales
|
16
|
+
I18n.backend.store_translations(locale, translations)
|
17
|
+
yield
|
18
|
+
ensure
|
19
|
+
I18n.reload!
|
17
20
|
end
|
18
21
|
|
19
22
|
def generate_unique_email
|
@@ -43,6 +46,10 @@ class ActiveSupport::TestCase
|
|
43
46
|
Admin.create!(valid_attributes)
|
44
47
|
end
|
45
48
|
|
49
|
+
def create_user_without_email(attributes={})
|
50
|
+
UserWithoutEmail.create!(valid_attributes(attributes))
|
51
|
+
end
|
52
|
+
|
46
53
|
# Execute the block setting the given values and restoring old values after
|
47
54
|
# the block is executed.
|
48
55
|
def swap(object, new_values)
|
data/test/support/integration.rb
CHANGED
@@ -15,7 +15,7 @@ class ActionDispatch::IntegrationTest
|
|
15
15
|
created_at: Time.now.utc
|
16
16
|
)
|
17
17
|
user.update_attribute(:confirmation_sent_at, options[:confirmation_sent_at]) if options[:confirmation_sent_at]
|
18
|
-
user.confirm
|
18
|
+
user.confirm unless options[:confirm] == false
|
19
19
|
user.lock_access! if options[:locked] == true
|
20
20
|
user
|
21
21
|
end
|
@@ -28,7 +28,7 @@ class ActionDispatch::IntegrationTest
|
|
28
28
|
password: '123456', password_confirmation: '123456',
|
29
29
|
active: options[:active]
|
30
30
|
)
|
31
|
-
admin.confirm
|
31
|
+
admin.confirm unless options[:confirm] == false
|
32
32
|
admin
|
33
33
|
end
|
34
34
|
end
|
data/test/test_helper.rb
CHANGED
@@ -17,6 +17,10 @@ Webrat.configure do |config|
|
|
17
17
|
config.open_error_files = false
|
18
18
|
end
|
19
19
|
|
20
|
+
if ActiveSupport.respond_to?(:test_order)
|
21
|
+
ActiveSupport.test_order = :random
|
22
|
+
end
|
23
|
+
|
20
24
|
OmniAuth.config.logger = Logger.new('/dev/null')
|
21
25
|
|
22
26
|
# Add support to load paths so we can overwrite broken webrat setup
|
@@ -27,3 +31,4 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
|
27
31
|
require "rails/generators/test_case"
|
28
32
|
require "generators/devise/install_generator"
|
29
33
|
require "generators/devise/views_generator"
|
34
|
+
require "generators/devise/controllers_generator"
|
data/test/test_helpers_test.rb
CHANGED
@@ -34,7 +34,7 @@ class TestHelpersTest < ActionController::TestCase
|
|
34
34
|
|
35
35
|
test "does not redirect with valid user" do
|
36
36
|
user = create_user
|
37
|
-
user.confirm
|
37
|
+
user.confirm
|
38
38
|
|
39
39
|
sign_in user
|
40
40
|
get :index
|
@@ -46,7 +46,7 @@ class TestHelpersTest < ActionController::TestCase
|
|
46
46
|
assert_response :redirect
|
47
47
|
|
48
48
|
user = create_user
|
49
|
-
user.confirm
|
49
|
+
user.confirm
|
50
50
|
|
51
51
|
sign_in user
|
52
52
|
get :index
|
@@ -55,7 +55,7 @@ class TestHelpersTest < ActionController::TestCase
|
|
55
55
|
|
56
56
|
test "redirects if valid user signed out" do
|
57
57
|
user = create_user
|
58
|
-
user.confirm
|
58
|
+
user.confirm
|
59
59
|
|
60
60
|
sign_in user
|
61
61
|
get :index
|
@@ -105,7 +105,7 @@ class TestHelpersTest < ActionController::TestCase
|
|
105
105
|
end
|
106
106
|
|
107
107
|
user = create_user
|
108
|
-
user.confirm
|
108
|
+
user.confirm
|
109
109
|
sign_in user
|
110
110
|
ensure
|
111
111
|
Warden::Manager._after_set_user.pop
|
@@ -118,7 +118,7 @@ class TestHelpersTest < ActionController::TestCase
|
|
118
118
|
flunk "callback was called while it should not"
|
119
119
|
end
|
120
120
|
user = create_user
|
121
|
-
user.confirm
|
121
|
+
user.confirm
|
122
122
|
|
123
123
|
sign_in user
|
124
124
|
sign_out user
|
@@ -146,7 +146,7 @@ class TestHelpersTest < ActionController::TestCase
|
|
146
146
|
|
147
147
|
test "allows to sign in with different users" do
|
148
148
|
first_user = create_user
|
149
|
-
first_user.confirm
|
149
|
+
first_user.confirm
|
150
150
|
|
151
151
|
sign_in first_user
|
152
152
|
get :index
|
@@ -154,10 +154,25 @@ class TestHelpersTest < ActionController::TestCase
|
|
154
154
|
sign_out first_user
|
155
155
|
|
156
156
|
second_user = create_user
|
157
|
-
second_user.confirm
|
157
|
+
second_user.confirm
|
158
158
|
|
159
159
|
sign_in second_user
|
160
160
|
get :index
|
161
161
|
assert_match /User ##{second_user.id}/, @response.body
|
162
162
|
end
|
163
|
+
|
164
|
+
test "creates a new warden proxy if the request object has changed" do
|
165
|
+
old_warden_proxy = warden
|
166
|
+
@request = ActionController::TestRequest.new
|
167
|
+
new_warden_proxy = warden
|
168
|
+
|
169
|
+
assert_not_equal old_warden_proxy, new_warden_proxy
|
170
|
+
end
|
171
|
+
|
172
|
+
test "doesn't create a new warden proxy if the request object hasn't changed" do
|
173
|
+
old_warden_proxy = warden
|
174
|
+
new_warden_proxy = warden
|
175
|
+
|
176
|
+
assert_equal old_warden_proxy, new_warden_proxy
|
177
|
+
end
|
163
178
|
end
|
data/test/test_models.rb
CHANGED
@@ -20,8 +20,8 @@ class UserWithCustomEncryption < User
|
|
20
20
|
end
|
21
21
|
|
22
22
|
class UserWithVirtualAttributes < User
|
23
|
-
devise case_insensitive_keys: [
|
24
|
-
validates :email, presence: true, confirmation: {on: :create}
|
23
|
+
devise case_insensitive_keys: [:email, :email_confirmation]
|
24
|
+
validates :email, presence: true, confirmation: { on: :create }
|
25
25
|
end
|
26
26
|
|
27
27
|
class Several < Admin
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# A copy of Rails time helpers. With this file we can support the `travel_to`
|
2
|
+
# helper for Rails versions prior 4.1.
|
3
|
+
# File origin: https://github.com/rails/rails/blob/52ce6ece8c8f74064bb64e0a0b1ddd83092718e1/activesupport/lib/active_support/testing/time_helpers.rb
|
4
|
+
module ActiveSupport
|
5
|
+
module Testing
|
6
|
+
class SimpleStubs # :nodoc:
|
7
|
+
Stub = Struct.new(:object, :method_name, :original_method)
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@stubs = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def stub_object(object, method_name, return_value)
|
14
|
+
key = [object.object_id, method_name]
|
15
|
+
|
16
|
+
if stub = @stubs[key]
|
17
|
+
unstub_object(stub)
|
18
|
+
end
|
19
|
+
|
20
|
+
new_name = "__simple_stub__#{method_name}"
|
21
|
+
|
22
|
+
@stubs[key] = Stub.new(object, method_name, new_name)
|
23
|
+
|
24
|
+
object.singleton_class.send :alias_method, new_name, method_name
|
25
|
+
object.define_singleton_method(method_name) { return_value }
|
26
|
+
end
|
27
|
+
|
28
|
+
def unstub_all!
|
29
|
+
@stubs.each_value do |stub|
|
30
|
+
unstub_object(stub)
|
31
|
+
end
|
32
|
+
@stubs = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def unstub_object(stub)
|
38
|
+
singleton_class = stub.object.singleton_class
|
39
|
+
singleton_class.send :undef_method, stub.method_name
|
40
|
+
singleton_class.send :alias_method, stub.method_name, stub.original_method
|
41
|
+
singleton_class.send :undef_method, stub.original_method
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Contains helpers that help you test passage of time.
|
46
|
+
module TimeHelpers
|
47
|
+
# Changes current time to the time in the future or in the past by a given time difference by
|
48
|
+
# stubbing +Time.now+, +Date.today+, and +DateTime.now+.
|
49
|
+
#
|
50
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
51
|
+
# travel 1.day
|
52
|
+
# Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
|
53
|
+
# Date.current # => Sun, 10 Nov 2013
|
54
|
+
# DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500
|
55
|
+
#
|
56
|
+
# This method also accepts a block, which will return the current time back to its original
|
57
|
+
# state at the end of the block:
|
58
|
+
#
|
59
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
60
|
+
# travel 1.day do
|
61
|
+
# User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
|
62
|
+
# end
|
63
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
64
|
+
def travel(duration, &block)
|
65
|
+
travel_to Time.now + duration, &block
|
66
|
+
end
|
67
|
+
|
68
|
+
# Changes current time to the given time by stubbing +Time.now+,
|
69
|
+
# +Date.today+, and +DateTime.now+ to return the time or date passed into this method.
|
70
|
+
#
|
71
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
72
|
+
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
|
73
|
+
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
|
74
|
+
# Date.current # => Wed, 24 Nov 2004
|
75
|
+
# DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500
|
76
|
+
#
|
77
|
+
# Dates are taken as their timestamp at the beginning of the day in the
|
78
|
+
# application time zone. <tt>Time.current</tt> returns said timestamp,
|
79
|
+
# and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
|
80
|
+
# <tt>Date.current</tt> returns a date equal to the argument, and
|
81
|
+
# <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
|
82
|
+
# be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
|
83
|
+
# or <tt>Date.today</tt>, in order to honor the application time zone
|
84
|
+
# please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
|
85
|
+
#
|
86
|
+
# Note that the usec for the time passed will be set to 0 to prevent rounding
|
87
|
+
# errors with external services, like MySQL (which will round instead of floor,
|
88
|
+
# leading to off-by-one-second errors).
|
89
|
+
#
|
90
|
+
# This method also accepts a block, which will return the current time back to its original
|
91
|
+
# state at the end of the block:
|
92
|
+
#
|
93
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
94
|
+
# travel_to Time.new(2004, 11, 24, 01, 04, 44) do
|
95
|
+
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
|
96
|
+
# end
|
97
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
98
|
+
def travel_to(date_or_time)
|
99
|
+
if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
|
100
|
+
now = date_or_time.midnight.to_time
|
101
|
+
else
|
102
|
+
now = date_or_time.to_time.change(usec: 0)
|
103
|
+
end
|
104
|
+
|
105
|
+
simple_stubs.stub_object(Time, :now, now)
|
106
|
+
simple_stubs.stub_object(Date, :today, now.to_date)
|
107
|
+
simple_stubs.stub_object(DateTime, :now, now.to_datetime)
|
108
|
+
|
109
|
+
if block_given?
|
110
|
+
begin
|
111
|
+
yield
|
112
|
+
ensure
|
113
|
+
travel_back
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns the current time back to its original state, by removing the stubs added by
|
119
|
+
# `travel` and `travel_to`.
|
120
|
+
#
|
121
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
122
|
+
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
|
123
|
+
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
|
124
|
+
# travel_back
|
125
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
126
|
+
def travel_back
|
127
|
+
simple_stubs.unstub_all!
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def simple_stubs
|
133
|
+
@simple_stubs ||= SimpleStubs.new
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|