Controller test fails for rails user authentication - ruby-on-rails

--preface: ignore if you want.
I'm new to rails, and working on a project that will require user authentication.
I found this tutorial and have been trying to go through it and understand what's happening. Of course, it's not exactly what I need as-is, so I've been modifying as I go along. The tutorial is also out of date in some areas, so of course I've had to update my code. So part of my problem is that I'm not sure if the bug is in my modifications, or some function that's been deprecated, or what.
--the question
This is the (simplest) test that fails. (" expected to not be nil" on the first assert statement.)
def test_authentication
#check we can log in
post :login, :user => { :username => "bob", :password => "test" }
assert_not_nil session[:user_id]
assert_equal users(:bob).id, session[:user_id]
assert_response :redirect
assert_redirected_to :action => 'welcome'
end
It calls the user_controller action login:
def login
if request.post?
if session[:user_id] = User.authenticate(params[:user][:username], params[:user][:password])
flash[:message] = "Login succeeded!"
redirect_to_stored
else
flash[:warning] = "Login failed."
end
end
end
which calls the User method authenticate. I know that authenticate works properly, however, because I have a single test that does pass:
def test_registration
#check that we can register and are logged in automatically
post :register, :user => { :username => "newuser", :password => "pass", :password_confirmation => "pass", :email => "newuser#web.com" }
assert_response :redirect
assert_not_nil session[:user_id]
assert_redirected_to :action => 'welcome'
end
which calls the user_controller action register
def register
#user = User.new(params[:user])
if request.post?
if #user.save
session[:user_id] = User.authenticate(#user.username, #user.password)
flash[:message] = "Registration succeeded"
redirect_to :action => 'welcome'
end
else
flash[:warning] = "Registration failed"
end
end
which successfully calls authenticate.
the users fixture has one relevant record:
bob:
username: bob
email: bob#mcbob.com
hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test
salt: 1000
I've tested the hashed password and salt - "test" is the correct password.
So by my analysis, the bug has got to be in one of 3 places:
how I'm sending my post request,
how I'm accessing the parameters in the login action,
or some aspect of the fixture loading.
(originally I was using the tutorial's code to load the fixture explicitly (self.use_instantiated_fixtures = true; fixtures :users), but I read that all fixtures are automatically loaded before testing, so I took it out. That didn't change a thing.)
Of course, since I can't seem to find the problem in those areas, it could just as well be anywhere else.

Is it possible that there's a filter that is preventing your action getting called? If there's a general :before_filter => 'login_required' then you might not be reaching your login functionality at all. (Though admittedly the register action would have to be excluded for that test to pass)
In cases like this it's useful to stick some logging in (or run through a debugger) to see whether you even get to the part of the method that you think is failing. If it were me I'd stick a logger.debug("...") as the first line of the login method and then another after the check for request.post? and then another after the authentication check.

Related

Difficulty debugging RSepec

I'm trying to debug my feature spec in RSpec. But I'm unable to get an exception. If I put a binding.pry before auth.save!, I'm able to break in. I then check if auth.valid? and it returns true. I also call auth.save manually with no exception being thrown. But if I put a pry after user.update_for_omniauth omniauth It doesn't get hit. The method update_from_omniauth is not actually being called because I am stubbing it. There is a rescue block at the end of my method. Placing a pry there doesn't trigger anything either. My spec is failing because I doesn't find an Authentication nor User in the database.
authentication controller
def create
user = merge_users! if current_user
auth = current_auth
user ||= auth&.user
email_user = User.has_email.where(email: omniauth.info.email).first_or_initialize
if user && email_user.persisted? && user != email_user
user.merge! email_user, auth
end
user ||= email_user
auth ||= user.authentications.build uid: omniauth.uid,
provider: omniauth.provider, image_url: omniauth.info.image
auth.token = omniauth.credentials.token
if Authentication::GOOGLE.include?(params[:provider].to_sym)
auth.token_expire = Time.at(omniauth.credentials.expires_at)
end
if omniauth.credentials.refresh_token
auth.refresh_token = omniauth.credentials.refresh_token
end
auth.refresh_facebook_token # get a longer running token
auth.save!
user.update_for_omniauth omniauth
user.save!
if params[:provider] == 'google_contacts'
params[:sync_status] = Contact.sync player, auth.token
end
sign_in_and_redirect user
rescue => e
if Rails.env.production?
Raven.capture_exception e, extra: omniauth
redirect_back fallback_location: new_user_session_path, flash: {error: e.message}
else
raise
end
spec version 1
it 'set organically login user as propertyuser' do
visit '/users/sign_in'
click_link 'Login via Facebook'
expect(Authentication.last.uid).to eq('654321')
end
spec version 2
it 'set organically login user as propertyuser' do
visit '/users/sign_in'
click_link 'Login via Facebook'
expect(User.last.email).to eq('facebookuser#mail.com')
end
more spec code
before do
setup_omniauth
application_controller_patch
allow(Facebook).to receive_message_chain(:oauth_for_app, :exchange_access_token_info).and_return('access_token' => '123', 'expires' => 500_000)
allow_any_instance_of(Authentication).to receive(:refresh_facebook_token) #.and_return(true)
allow_any_instance_of(User).to receive(:update_facebook_properties)# .and_return(true)
allow_any_instance_of(User).to receive(:update_for_omniauth)# .and_return(true)
end
def setup_omniauth
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:facebook_app_rewards] = OmniAuth::AuthHash.new(
'provider' => 'facebook',
'uid' => '654321',
'info' => {
'first_name' => 'Facebook',
'last_name' => 'User',
'email' => 'facebookuser#mail.com',
'image' => 'https://randomuser.me/api/portraits/med/men/65.jpg'
},
'credentials' => {
'token' => '123456',
'secret' => 'top_secret',
'expires_at' => 2.days.from_now
}
)
Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:facebook_app_rewards]
end
def application_controller_patch
ApplicationController.class_eval do
def omniauth
Rails.application.env_config['omniauth.auth']
end
end
end
The most likely reason your tests are failing is because actions triggered by click_link are not guaranteed to have completed when click_link returns. You can test that by adding a few second sleep after the click_link. If that fixes your test then you'll want to replace the sleep with an expectation of visual change on the page.
click_link 'Login via Facebook'
expect(page).to have_text('You have logged in!') # expectation for whatever happens on the page after a successful login
expect ... # rest of your test.
Note: Mocking/Stubbing methods on User in a feature test is generally a bad idea (mocking of anything but external services is generally a bad code smell in a feature test) as is direct DB access from feature tests. Feature tests are designed to be all about testing what a user experiences on the site, not about directly testing the implementation details.

How to test updating user's password in RSpec

I am using Rails 5.2, Ruby 2.4.1, and Rspec. I am trying to make an automation test using Rspec to update a User's password. I know the code works from manually testing.
The RSpec test does not pass the #user.update_attributes(user_params) condition in the controller and then goes to the else condition. Thus, my RSpec test says that the passwords are still equal to each other. How can I get my RSpec test to pass the condition?
Here is the method
# Creates user, saves old password, generates a url for user to go to, updates password, reloads user, and test if old password equals new password
context "with a valid token" do
it "updates the user's password and resets the token" do
test_users = User.create(first_name: 'chase', last_name: 'dougherty', email: 'chase#gmail.com', password: '1', password_confirmation: '1')
old_password = test_users.password
test_users.generate_password_reset_token!
patch :update, params: { id: test_users.password_reset_token, user: { password: 'newpassword', password_confirmation: 'newpassword' } }
test_users.reload
expect(test_users.password).to_not eq(old_password)
end
end
Here is the Controller
# Finds user, test if update_attributes is true, updates password, logs user in, redirects user, displays flash
def update
#user = User.find_by(password_reset_token: params[:id])
if #user && #user.update_attributes(user_params)
#user.update_attribute(:password_reset_token, nil)
session[:user_id] = #user.id
redirect_to '/maps'
flash[:notice] = "Password updated"
else
flash[:notice] = "Password reset failure."
render action: 'edit'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
If you use has_secure_password method in your user model, you can use this syntax for check password has been changed:
it "changes user's password" do
expect { send_request }.to change { user.reload.authenticate(password) }.from(false).to(user)
end
If the update_attributes line is failing, you probably have some validations on the User model that are firing and causing it to return false.
Also, I would advise against "creating" a user object in the test files. Especially because the first time you run the tests it will create the entry, but then every time after that it will return a User instance that is not saved to the database (because of uniqueness violations in the data) and the tests may not run as expected.
Unless you want to clean up your created user objects after the test runs. Otherwise you will want to use factory_bot and stub out most of these models and the database calls.

Rails test - checking if user is signed in with Devise

I am trying to test that someone is able to login to my site by making a POST request to my SessionsController. I've seen this way recommended in a few places:
it 'must be able to sign in a user' do
user = create(:user)
post :create, format: :js, user: {email: user.email, password: user.password, remember_me: 0}
assert_response :success
#controller.current_user.must_equal user
end
But this test is not correct. Calling #controller.current_user will attempt to authenticate the user using the posted parameters and will return user if the supplied email/password is correct. There is no guarantee that the create action is actually calling sign_in or current_user.
Even if I re-write the test to check that these methods are called, it's possible that other methods could be called e.g. sign_out.
Is there a more definitive way to ultimately check if a user is logged in, and if so, who the user is?
EDIT -
For example, the following test will pass
it 'must sign in a user' do
#controller.current_user.must_equal nil
post :create, format: :js, user: {email: #user.email, password: #user.password, remember_me: 0}
assert_response :success
#controller.current_user.must_equal #user
end
when the SessionsController#create action is:
def create
respond_to do |format|
format.js {
render nothing: true, status: 200
}
end
end
Solution with minimal changes to proposed code in the question:
You need to initialize the system before the test starts. Try prepending following code before your it 'must be able to sign in a user' do code:
before (:each) do
user = FactoryGirl.create(:user)
sign_out user
end
This should turn your test into a valid test for your post controller.
Explanation:
My assumption is, that your test above always succeeds, because the user is already signed in (by other tests run before this one). You could verify this by using byebug in the line after it and run current_user in bybug's console. If it is not nil, the user is already signed in, which is invalidating your test.
Note, that (different from what is discussed above in the comments), current_user does not change the status of the user; it is a read-only function.
Shorter/cleaner solution:
In my opinion, there is a a cleaner way to perform such a test like follows:
def sign_in_via_post(user)
post :create, format: :js, user: {email: user.email, password: user.password, remember_me: 0}
end
...
before (:each) do
user = FactoryGirl.create(:user)
sign_out user
end
it 'must be able to sign in a user' do
{ sign_in_via_post user }.should change { current_user }.from(nil).to(user)
end
With the should change from nil to user statement, you verify, that the user was logged out before the test begins and that the user is logged in, after the test has been performed.
Note, that the part
{ sign_in_via_post user }.should change { current_user }.from(nil).to(user)
is equivalent to the (maybe easier to understand) code
{ sign_in_via_post user }.should change { user_signed_in? }.from(false).to(true)
as discussed here.

Rails 3.2 reload! not working as expected

I am trying to test a create action on my users controller. The ask is to allow customers who have previously created accounts, but never actually created a subscription, to use the same email at signup but not be bound to their original password. My test is as follows:
it "updates the user password for user with no entitlement" do
user6 = Factory(:user)
user_params_without_entitlement= { :email => user6.email, :password => "mynewpassword", :password_confirmation => "mynewpassword", publisher_id: user6.publisher_id }
post :create, user_params_without_entitlement
hash = Hashie::Mash.new(JSON.parse response.body)
expect(hash[:errors].present?).to eq(false)
expect(user6.password).to eq("mynewpassword")
end
my controller looks like:
def create
if #user.save && #user.activate!
render :create, status: 200
elsif #user.errors[:email].first == "Oops! Looks like you've already created an account. Please <a href='/account'>click here to sign in.</a>"
user = User.find_by_email(params[:email])
if user.user_entitlements.empty? && user.update_attributes(password: params[:password], password_confirmation: params[:password_confirmation])
render :create, status: 200
else
render json: {errors: #user.errors}, status: 422
end
else
if render json: {errors: #user.errors}, status: 422
end
end
end
If I put a binding in below the
user.user_entitlements.empty? && user.update_attributes(password: params[:password], password_confirmation: params[:password_confirmation])
and I call user.password I get "mynewpassword" so the password is updating. in the test though the password is still showing as the original password. I tried adding user6.reload! in the test before the expectation block and I get
NoMethodError: undefined method `reload!' for #<User:0x007fa9a3342fc0>
I found this issue: https://github.com/rweng/pry-rails/issues/9 which suggests that I should modify my .pryrc file. I didn't have a .pryrc file previously. I created a .pryrc file and tried everything in this post(pry gem how to reload?) one at a time with no success. I created the .pryrc in the root of the app and at this point I am at a loss as to what to do.
it is object.reload to reload an object - without the !bang
inside the console you use reload! to reload the hole application models.
so in your case do user6.reload
just to clearout for you, how to use object.reload correctly
User has one profile which has autosave. After_save of profile, the profile is calculating the age and writes that to the user.
[69] pry(main)> a = User.last
[70] pry(main)> a.age
=> 80
[71] pry(main)> a.profile.birthday = 18.years.ago
[72] pry(main)> a.save
......
SQL (0.3ms) UPDATE "users" SET "age" = 18 WHERE "users"."id" = $1 [["id", 6]]
....
[73] pry(main)> a.age
=> 80
[74] pry(main)> a.reload
[75] pry(main)> a.age
=> 18
maybe that clears it out for you. cheers
Ok so I am not sure if I actually diagnosed the problem but here is my theory on what was happening(I am not going to mark it as the answer until I give you fine folks a chance to debunk my hypothesis).
I think that since the User object doesn't actually have a password attribute(It has crypted_password and salt attributes instead) the data was getting out of sync somehow. I noticed that even when I would find the user by email and call user.password i would get a nil value in the test, but was getting the correct value from a binding in the controller.
I started by posting to the session controller create action with the new password and that worked but felt pretty dirty. I then looked into how the session controller was actually verifying the password and voila, I fixed my test to use the authenticated? method that the session controller was using. Also don't worry I cleaned up the nested if/elses as well. So my test looks like:
context "User without entitlements" do
let(:user3) { Factory(:user) }
let(:user_params_without_entitlement) { {:email => user3.email, :password => "mynewpassword", :password_confirmation => "mynewpassword", publisher_id: user3.publisher_id} }
before do
post :create, user_params_without_entitlement
end
it "updates the user password for user with no entitlement" do
User.find_by_email(user3.email).authenticated?("mynewpassword").should be_true
end
it "returns a 200 if user password is updated" do
expect(response.code).to eq('200')
end
end

having problems with rails integration testing and session variables

using rails 3.1 here. i am having some problems with session variables in my integration test.
test "new registration" do
get "/user/sign_up?type=normal"
assert_response :success
assert_equal 'normal', session[:registration]
assert_template "user_registrations/new"
assert_difference('User.count', +1) do
post "/user", :user => {:email => "kjsdfkjksjdf#sdfsdf.com", :user_name => "lara", :password => "dfdfdfdfdfdf", :password_confirmation => "dfdfdfdfdfdf"}
end
assert_response :redirect
assert_template "user_registrations/new"
#user = User.find_by_user_name('lara')
assert_equal 'lara', #user.user_name
#user.confirm!
end
all of the assertions pass so thats good, but from what i can tell, session[:registration] is not getting passed when post to "/user".
in my create action in the user_registration controller:
if session[:registration] == "normal"
#user.role = "normal"
#user.status = "registering"
#user.save
else
#user.role = "fan"
#user.save
end
So when the user is created, #user.role = "fan", when it should be #user.role = "normal".
I cant seem to get that session variable to pass when i post. from what i understand, in the "new registration" test, all the session variables remain active as long as the test has not ended. am i wrong to assume that that session variable should be posted? any suggestions?

Resources