I have such tests:
class LockableFlowTest < ActionDispatch::IntegrationTest
context 'A user' do
setup do
#organization = Factory(:organization)
#user = Factory(:user, :organization => #organization)
end
should "login after 4 attempts" do
4.times do
post_via_redirect '/users/sign_in', 'user[username]' => #user.username, 'user[password]' => "bad_password"
assert_equal '/users/sign_in', path
assert_equal 'Invalid email or password.', flash[:alert]
end
post_via_redirect '/users/sign_in', 'user[username]' => #user.username, 'user[password]' => "password"
assert_equal "/registrations/#{#user.id}/edit", path
assert_nil flash[:alert]
end
It doesn't work but the application is okay. I would like to test attempts to login into the application. After 4 attempts it should be possible to login.
The code controller:
class SessionsController < Devise::SessionsController
after_filter :log_failed_login, :only => :new
private
def log_failed_login
if request.filtered_parameters["user"]
user = User.find_by_username(request.filtered_parameters["user"]["username"])
if user
if user.first_failed_attempt.nil?
user.first_failed_attempt = Time.now
else
if user.first_failed_attempt + 15.minutes <= Time.now
user.failed_attempts = 1
user.first_failed_attempt = Time.now
end
end
user.save
end
end
end
def failed_login?
(options = env["warden.options"]) && options[:action] == "unauthenticated"
end
end
What's the counting mechanism to let the application know that it already executed four tries?
Cookies? Cookies do not persist in Rspec on rails 3.1
Related
I'm attempting to write a test for the creation of a session with the omniauth-google-oauth2 gem. Do I need to pass the env["omniauth.auth"] variable with the post :create? Perhaps when I was attempting to do that I was doing it incorrectly. The error I'm getting is shown below...
Rake Test Error
1) Error:
SessionsControllerTest#test_should_get_create:
NoMethodError: undefined method `provider' for nil:NilClass
app/models/user.rb:6:in `from_omniauth'
app/controllers/sessions_controller.rb:4:in `create'
test/controllers/sessions_controller_test.rb:13:in `block in <class:SessionsControllerTest>'
The following is my attempt at writing the test...
SessionsControllerTest
require 'test_helper'
class SessionsControllerTest < ActionController::TestCase
setup :prepare_omniauth
test "should get create" do
post :create
redirect_to root_path, notice: "Signed in!"
end
test "should get destroy" do
get :destroy
assert session[:user_id].blank?, "user_id should no longer exist"
assert_redirected_to root_path, notice: "Signed out!"
end
private
def prepare_omniauth
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:google] = OmniAuth::AuthHash.new({
:provider => 'google',
:uid => '123545'
})
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:google]
end
end
Sessions Controller
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_path
end
def destroy
session[:user_id] = nil unless session[:user_id].blank?
redirect_to root_path
end
end
User Model
class User < ActiveRecord::Base
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
end
I believe this blog has the answer to your question: http://natashatherobot.com/rails-test-omniauth-sessions-controller/
Basically you need to edit your rails_helper/spec_helper to set Omniauth's test mode to true and create an omniauth_hash to be used in your tests:
OmniAuth.config.test_mode = true
omniauth_hash = { 'provider' => 'github',
'uid' => '12345',
'info' => {
'name' => 'natasha',
'email' => 'hi#natashatherobot.com',
'nickname' => 'NatashaTheRobot'
},
'extra' => {'raw_info' =>
{ 'location' => 'San Francisco',
'gravatar_id' => '123456789'
}
}
}
OmniAuth.config.add_mock(:github, omniauth_hash)
Then you require it before any tests:
before do
request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:github]
end
Now you should be able to create users using Omniauth like in this example:
describe SessionsController do
it "should successfully create a user" do
expect {
post :create, provider: :github
}.to change{ User.count }.by(1)
end
end
All credits to Natasha from http://natashatherobot.com for posting this answer in her blog.
I test password recovery, but there are errors.
Rspec study recently.
code (User Controller)
def forgot
if request.post?
user = User.find_by_email(params[:user][:email])
if user
user.create_reset_code
end
flash[:notice] = t('helpers.notice_email')
render :template => "sessions/new"
end
end
rspec test
it "POST 'reset page'" do
User.should_receive(:find_by_email).with({:email => #user.email})
post :forgot, :user => {"email" => #user.email}
end
What am I doing wrong in the test?
User.should_receive(:find_by_email).with(#user.email)
I am following Lesson 9 for sign-in, yet the signin test just won't pass! I followed the code to the letter and it still wont pass.
when testing with the browser - the sign in works with no errors..
Failures:
1) SessionsController POST 'create' with valid email and password should sign the user in
Failure/Error: controller.current_user.should == #user
expected: #
got: nil (using ==)
# ./spec/controllers/sessions_controller_spec.rb:55:in `block (4 levels) in '
2) SessionsController POST 'create' with valid email and password should redirect to the user show page
Failure/Error: response.should redirect_to(user_path(#user))
Expected block to return true value.
# ./spec/controllers/sessions_controller_spec.rb:61:in `block (4 levels) in '
Finished in 5.12 seconds
7 examples, 2 failures
Failed examples:
rspec ./spec/controllers/sessions_controller_spec.rb:53 # SessionsController POST 'create' with valid email and password should sign the user in
rspec ./spec/controllers/sessions_controller_spec.rb:59 # SessionsController POST 'create' with valid email and password should redirect to the user show page
As you can see, the test controller.current_user.should == #user returns nil for some reason.
Please help me understand this..
Thank you
SessionHelper
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
self.current_user= user
end
def signed_in?
!self.current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
private
def user_from_remember_token
User.authenticate_with_salt(*remember_token)
end
def remember_token
cookies.signed[:remember_token] || [nil, nil]
end
end
Session controller
class SessionsController < ApplicationController
def new
#title = "Sign In"
end
def create
user = User.authenticate(params[:session][:email], params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password"
#title = "Sign In (right this time)"
render 'new'
else
#sign in the user
sign_in #user
redirect_to user
end
end
def destroy
end
end
User Model
require 'digest'
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => {:minimum => 2, :maximum => 50}
validates :email, :presence => true, :format => {:with => email_regex}, :uniqueness => {:case_sensitive => false }
#Automatically creates the virtual password confimration attribute
validates :password, :presence => true,
:confirmation => true,
:length => {:within => 6..40}
before_save :encrypt_password
def has_password?(submitted_password)
self.salt = make_salt if new_record?
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return nil if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
session controller spec
require 'spec_helper'
describe SessionsController do
render_views
describe "GET 'new'" do
it "should be successful" do
get 'new'
response.should be_success
end
it "should have the right title" do
get :new
response.should have_selector("title", :content => "Sign In")
end
end
describe "POST 'create'" do
describe "invalid login" do
before(:each) do
#attr = {:email => "email#example.com", :password => "invalid"}
end
it "should re-render the new page" do
post :create, :session => #attr
response.should render_template('new')
end
it "should have the right title" do
post :create, :session => #attr
response.should have_selector("title", :content => "Sign In (right this time)")
end
it "should have flash.now message" do
post :create, :session => #attr
flash.now[:error].should =~ /invalid/i
end
end
describe "success" do
before(:each) do
#user = Factory(:user)
#attr = { :email => #user.email, :password => #user.password }
end
it "should sign the user in" do
post :create, :session => #attr
controller.current_user.should == #user
controller.should be_signed_in
end
it "should redirect to the user show page" do
post :create, :session => #attr
response.should redirect_to(user_path(#user))
end
end
end
end
I'm assuming you've completed past Section 9.3.3 and added the authenticate class method to your user model.
If you check your authenticate (9.17) class method it has user = find_by_email(email). You should check that your factory is in fact creating a user in your test database first.
Update
Do you have this in SessionsHelper
def signed_in?
!current_user.nil?
end
Update 2
Remove the following in your SessionsHelper!!
def current_user=(user)
#current_user = user
end
Hartl is quite specific as to why this writer is removed
If we did this, we would effectively replicate the functionality of
attr_accessor, first seen in Section 4.4.5 and used to make the
virtual password attribute in Section 7.1.1.7 The problem is that it
utterly fails to solve our problem: with the code in Listing 9.15, the
user’s signin status would be forgotten: as soon as the user went to
another page—poof!—the session would end and the user would be
automatically signed out.
To avoid this problem, we can find the session user corresponding to
the cookie created by the code in Listing 9.12, as shown in Listing
9.16.
It is instead replaced by
def current_user
#current_user ||= user_from_remember_token
end
First, Listing 9.16 uses the common but initially obscure ||= (“or
equals”) assignment operator (Box 9.4). Its effect is to set the
#current_user instance variable to the user corresponding to the
remember token, but only if #current_user is undefined.8 In other
words, the construction calls the user_from_remember_token method the
first time current_user is called, but on subsequent invocations
returns #current_user without calling user_from_remember_token.
I had the same issue. I understood also that it should be replaced. But it should not both methods are needed.
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
Took me forever to track this one down...
The rspec error message:
1) SessionsController POST 'create' with valid email and password should sign the user in
Failure/Error: controller.current_user.should == #user
NoMethodError:
undefined method `current_user' for #<SessionsController:0x00000005426eb8>
# ./spec/controllers/sessions_controller_spec.rb:51:in `block (4 levels) in <top (required)>'
You want to edit app/helpers/sessions_helper.rb :
WRONG:
def current_user=(user)
#current_user = user ||= user_from_remember_token
end
RIGHT:
def current_user
#current_user ||= user_from_remember_token
end
I clearly wasn't paying close enough attention when I was following along and unfortunately the error message from rspec wasn't as helpful as it normally is. Hope this helps!
I followed the rails cast of omniauth to create authentication for twitter ( http://railscasts.com/episodes/235-omniauth-part-1?view=comments ). It works fine in development but I can't get rspec to detect that I have created the authentication. Here is my snippet for create function in my Authentication controller:
def create
begin
auth_hash = request.env["omniauth.auth"]
#auth = current_user.authentications.build( :provider => auth_hash['provider'],
:uid => auth_hash['uid'],
:name => auth_hash['user_info']['name'],
:nickname => auth_hash['user_info']['nickname'],
:image => auth_hash['user_info']['image']
)
if #auth.provider.downcase == "twitter"
#auth.auth_token = auth_hash['credentials']['token']
#auth.secret_token = auth_hash['credentials']['secret']
#auth.site = auth_hash['user_info']['urls']['Twitter']
elsif #auth.provider == "Facebook"
end
rescue
redirect_to current_user, :flash => { :error => "Missing oauth data!! Check with administrator!"}
else
if #auth.save
msg = "Authentication success"
else
msg = "Already have authentication"
end
redirect_to current_user, :notice => msg
end
end
included in my routes:
match '/auth/:provider/callback' => 'authentications#create'
I have the following setup in my rspec_helper:
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:twitter, { :provider => "twitter",
:uid => "1234",
:user_info => { :name => "Bob hope",
:nickname => "bobby",
:urls => {:Twitter => "www.twitter.com/bobster"}},
:credentials => { :auth_token => "lk2j3lkjasldkjflk3ljsdf"} })
Here is my rspec code that is not working:
describe "Post 'create'" do
before(:each) do
#user = Factory(:user)
sign_in #user
end
describe "success" do
before(:each) do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
it "should create authentication" do
lambda do
post :create, :provider => "twitter"
response.should redirect_to(#user)
end.should change(Authentication, :count).by(1)
end
end
end
error i get is:
1) AuthenticationsController Post 'create' success should create authentication
Failure/Error: lambda do
count should have been changed by 1, but was changed by 0
# ./spec/controllers/authentications_controller_spec.rb:57
I've checked everything and cannot figure what I am doing wrong. Can anyone help?
i finally figured what was wrong. in my mock :auth_token is suppose to be :token. That was causing the failed validation.
Shouldn't that be a get request?
describe "success" do
before(:each) do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
it "should create authentication" do
lambda do
get :create, :provider => "twitter"
response.should redirect_to(#user)
end.should change(Authentication, :count).by(1)
end
end
i have a #user instance that i use for logging a devise user. It's a macro as Devise wiki proposes :
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = :user
#user = Factory(:user)
sign_in #user
end
end
end
Generally, i do different things to stub methods, but i don't understand why this stub does not work : (note that #user is indeed logged in, i use it successfully for testing attributes and stuff)
#user.stub(:has_tavern_quest?).and_return(true)
It seems that the stub works(i checked with #user.has_tavern_quest should true), but i just get back :
Failure/Error: flash[:error].should == I18n.t('error.has_quest')
expected: "You already have a quest to finish !"
got: nil (using ==)
The whole controller method is :
quest_type = params[:quest_type]
#quest_minutes = TavernQuest.get_quest_minutes_from_quest_type(quest_type)
flash[:error] = I18n.t('error.invalid_post') and redirect_to tavern_url and return unless [1,2,3,4].include? quest_type.to_i
flash[:error] = I18n.t('error.has_quest') and redirect_to tavern_url and return if current_user.has_tavern_quest?
flash[:error] = I18n.t('error.no_adv_points') and redirect_to tavern_url and return if current_user.adventure_points < #quest_minutes
current_user.reduce_adventure_points(#quest_minutes.to_i)
TavernQuest.create(:user_id => current_user.id, :start_time => Time.now, :end_time => Time.now + #quest_minutes.minutes,
:duration => #quest_minutes, :quest_type => quest_type)
redirect_to tavern_url
end
And the whole spec is :
it "should redirect to '/tavern' with an error if user already has a tavern quest" do
#user.stub(:has_tavern_quest?).and_return(true)
post :create, :quest_type => 3
flash[:error].should == I18n.t('error.has_quest')
response.should redirect_to tavern_url
end
Anybody knows why and what is the way to make it work ?
Solved :
def login_user
before(:each) do
#user = Factory(:user)
controller.stub(:current_user) { #user }
sign_in #user
end
end