In my controller, I have a method called update_password, this method updates the user password after validating token. But, my tests are failing.What might be the problem?
In my controller,
def update_password
user = User.find_by(email: params[:email])
if user.nil?
render json: { error: 'Could not update' }, status: 422
else
user.update(user_params)
render json: { message: 'Successfully Updated' }, status: 200
end
end
def user_params
params.permit(:password, :password_confirmation, :current_password, :email)
end
Test:
describe 'update password for valid token' do
it'should update the password' do
user_params = {
password: 'newpassword',
password_confirmation: 'newpassword',
email: user.email
}
put '/api/v1/update_password', params: user_params
expect(user.password).to eq 'newpassword'
expect(user.reload.password_confirmation).to eq 'newpassword'
expect(user.reload.password).to eq(user.reload.password_confirmation)
json_response = JSON.parse(response.body)
expect(json_response['message']).to eq('Successfully Updated')
end
end
Factories:
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}#example.com" }
password 'testcase'
username 'testcase'
password_confirmation 'testcase'
first_name 'testname'
last_name 'test'
end
end
Error I have got:
1) UsersRequests update password for valid token should update the password
Failure/Error: expect(user.password).to eq 'newpassword'
expected: "newpassword"
got: "testcase"
(compared using ==)
# ./spec/requests/users_requests_spec.rb:105:in `block (3 levels) in <top (required)>'
Finished in 0.35031 seconds (files took 5.69 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/requests/users_requests_spec.rb:98 # UsersRequests update password for valid token should update the password
Your controller action is fundamentally broken. It returns the wrong response code when a record is not found (422 not 401) and returns 200 no matter if the record is updated or not. You are also letting the user update the email attribute!
It should look something like this:
def update_password
# this will raise ActiveRecord::NotFound if the record cannot be found
# this avoids duplication by relying on rescue_from to return 401 - not found
user = User.find_by!(email: params[:email])
# You need to check the return value to see if the record was updated!
if user.update(update_password_params)
# consider omitting the JSON payload as clients can just
# use the status code to determine if it was a success
render json: { message: 'Successfully Updated' }, status: 200
else
render json: { error: 'Could not update' }, status: 422
end
end
private
# this should be a subset of the params and not allow email!
def update_password_params
params.permit(:password, :password_confirmation, :current_password)
end
You can also do much better in the spec by using RSpec's change matchers:
describe 'update password for valid token' do
let!(:user) { create(:user) }
let(:user_params) do
{
password: 'newpassword',
password_confirmation: 'newpassword',
email: user.email
}
end
# don't start with 'should'
it 'updates the password' do
expect do
put '/api/v1/update_password', params: user_params
user.reload
end.to change(user, :password).to('newpassword')
end
it 'is successful' do
put '/api/v1/update_password', params: user_params
expect(response).to have_http_status 200
end
end
Your spec should simply test the intendended behavior - that the records password is updated.
Testing the password_confirmation is not possible since its a virtual attribute - and not needed here. You instead need to test in a separate spec that the password is not updated if they do not match:
describe 'update password with invalid attributes' do
let!(:user) { create(:user) }
let(:user_params) do
{
password: 'newpassword',
password_confirmation: 'newpasswordxx',
email: user.email
}
end
it 'does not update the password' do
expect do
put '/api/v1/update_password', params: user_params
user.reload
end.to_not change(user, :password)
end
it 'reponds with 422' do
put '/api/v1/update_password', params: user_params
expect(response).to have_http_status 422
end
end
Related
I have a user controller that when a PATCH request is sent to, it will try to authenticate the user and update the user's password. Everything works in the browser (I get 200 when the "current_password" is correct, and 401 when it's incorrect).
But when I try to run the specs, expect(response.status).to eq(401) is always failing because I always get 200 as the response, can anyone explain what's going on here?
users_controller.rb:
def update
if params[:user].has_key?("current_password") && !(#user.authenticate(params[:user][:current_password]))
render_response(:not_authorized, { description_detailed: "Unable to authenticate current password" })
else
#user.update_attributes!(accessible_params)
render_response(:ok, { data: UserSerializer.new(#user, root: false)})
end
end
users_controller_spec.rb:
context "when current password field is passed in" do
let(:name) { Faker::HarryPotter.character.split(' ') }
let!(:custom_user) do
create(:user,
first_name: name[0],
last_name: name[0],
email: "test#reset.com",
password: "correct123"
)
end
context "when current password is correct" do
before :example do
patch :update, params: { id: custom_user.id, user: user_params }
end
let(:user_params) do
{ current_password: "correct123", password: "newpassword123" }
end
it "updates user's password with new password" do
expect(custom_user.authenticate('correct123')).to eq(custom_user)
expect(response.status).to eq(200)
end
end
context "when current password is incorrect" do
let(:user_params) do
{ user: {
current_password: "incorrect1235",
password: "newpassword123"
}}
end
before :example do
patch :update, params: { id: custom_user.id, user: user_params }
end
it "does not update user's password" do
expect(custom_user.authenticate('incorrect1234')).not_to eq(custom_user)
expect(response.status).to eq(401)
end
end
end
This is because you are testing for the hash keys in string form in the controller...
if params[:user].has_key?("current_password")
but passing them in symbol form in the test...
user: {
current_password: "incorrect1235",
If you look at the parameters in controller in this case, you will find the key is:
:current_password
This also points out a dangerous flaw in your code -- a user's password can be updated if "current_password" is not supplied in the update parameters. In this instance the else block is always called and the user is never authenticated.
Suggest testing for the presence the current_password and returning an error if it is not supplied.
I'm attempting to create some controller specs in my Rails 5 app using rspec, but the code keeps throwing the following error:
1) SessionsController Log in and log out logs in with valid user
Failure/Error: user = User.find_by(email: params[:session][:email].downcase)
NoMethodError:
undefined method `[]' for nil:NilClass
My spec is pretty straightforward. The user instance variable uses factory-girl to create a user with the email "user#example.com" and password as "password." When I call puts on these variables, I can see that they are set correctly.:
require 'rails_helper'
RSpec.describe SessionsController, type: :controller do
before :each do
#user = create(:user)
end
describe "Log in and log out" do
before :each do
post :create, { session: { email: #user.email,
password: #user.password }}
end
it "logs in with valid user" do
puts #user.email + " " + #user.password
expect(is_logged_in?).to be_truthy
end
end
end
Finally, the code from the sessions controller is below:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
remember user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
end
Am I misunderstanding the way params are sent to the controller in rspec? Is there any other reason for this error to be returned?
Changes were made in Rails 5 to the way you send params in controller tests.
Instead of:
before :each do
post :create, { session: { email: #user.email,
password: #user.password }}
end
You need to provide the params key in post request attribute hash. Like so...
before :each do
post :create, params: { session: { email: #user.email,
password: #user.password }}
end
It's subtle, but it's different. Let me know if that works.
I am trying to validate that the current_user's organization matches that of the organization they are trying to view.
Here's the part of the controller that's failing this test (#organization is being defined in an earlier method):
if current_user.organization != #organization
redirect_to root_path, notice: "Not authorized to edit this organization"
end
Here's the failing test:
require 'rails_helper'
RSpec.describe Admin::PagesController, :type => :controller do
describe 'GET #home' do
login_user
before do
#organization = FactoryGirl.create(:organization)
end
context "valid params" do
it "renders the home template and returns http 200" do
get :home, name: #organization.name
expect(response).to render_template("home")
expect(response.status).to eq(200)
end
end
end
Here's my factory:
factory :user do
email { Faker::Internet.email }
organization_id 1
password "foobarfoobar"
password_confirmation { |u| u.password }
end
...And here's where login_user is being defined:
module ControllerMacros
def login_user
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
sign_in user
end
end
Stacktrace:
1) Admin::PagesController GET #home valid params renders the home template and returns http 200
Failure/Error: it "renders the home template and returns http 200" do
expecting <"home"> but rendering with <[]>
# ./spec/controllers/admin/pages_controller_spec.rb:15:in `block (4 levels) in <top (required)>'
However:
[2] pry(#<RSpec::ExampleGroups::AdminPagesController::GETHome::ValidParams>)> subject.current_user.organization == #organization
=> true
Not sure what is going wrong here, seems like pretty standard stuff. Any ideas?
Turns out the issue was that I was sending in the wrong parameter - should have been sending #organization.subdomain, not #organization.name. :(
I have a controller for my users:
class API::V1::UsersController < ApplicationController
respond_to :json
def create
#user = User.create(user_params)
respond_with(:api, :v1, #user)
end
end`
I'm using the responders gem.
When I run my RSpec tests, I get a 422 error. Here is the failing test:
describe "POST create" do
it "should create a user" do
post :create, user: FactoryGirl.build(:user).attributes, format: :json
expect(response.status).to eq(201)
end
end
I'm using has_secure_password on my user model.
I have password and password_confirmation in my user_params (strong parameters)
My factory has matching password and password_confirmation
FactoryGirl.define do
factory :user do
first_name Faker::Name.first_name
last_name Faker::Name.last_name
email Faker::Internet.safe_email
password "123456789"
password_confirmation "123456789"
date_of_birth 25.years.ago
end
end
Anything I'm missing or should look for?
You probably need to set devise mappings in spec/support/rails_helper.rb
#request.env["devise.mapping"] = Devise.mappings[:admin]
See more at devise wiki
I have an app that uses Sorcery (or tries to use it),
and I am writing the specs for it:
context "successfull attempts to log in" do
let(:attr) { attributes_for(:credentials) }
before(:each) do
#user = create(:user, attr)
end
it "should log the user in" do
post :create, attr.merge(remember_me: false)
controller.should be_logged_in
end
end
Here is the FactoryGirl factory:
FactoryGirl.define do
factory :user do
email Faker::Internet.safe_email
password "password"
password_confirmation { |u| u.password }
client_id 1
end
factory :credentials, class: User do
email "user#example.com"
password "password"
end
end
And here is the controller action:
class SessionsController < ApplicationController
# ...
def create
login(params[:email], params[:email], params[:remember_me])
flash.now[:error] = "Invalid email/password combination"
render :new
end
end
The error message is the following:
1) SessionsController POST 'create' successfull attempts to log in should log the user in
Failure/Error: controller.should be_logged_in
expected logged_in? to return true, got false
# ./spec/controllers/sessions_controller_spec.rb:54:in `block (4 levels) in <top (required)>'
The spec keeps failing for some reason. Could anyone explain to me why please?
Your controller uses the email as both the username and password, which looks like a copy and paste error. Is that correct?