Why doesn't validate_acceptance_of break my functional tests? - ruby-on-rails

Using Ruby on Rails 4.2.0.rc2 I added an 'Accept terms of service' checkbox to user registration
In the user model I added
attr_accessor :terms_of_service
validates_acceptance_of :terms_of_service, acceptance: true
In the view
<%= f.check_box :terms_of_service %>
and finally in the controller I added it to the list of parameters
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation, :terms_of_service)
end
This works as expected but since I made a change to the implementation I expected the related tests to be in the red. However, this test passes and I don't understand why:
assert_difference 'User.count', 1 do
post users_path, user: { name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password" }
end
I can re-write my tests like so
test "accept terms of service" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password",
terms_of_service: "0" }
end
assert_difference 'User.count', 1 do
post users_path, user: { name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password",
terms_of_service: "1" }
end
end
but I am curious as to why the original test fails to fail. What I've taken away from this is that validates_acceptance_of passes for nil.
Is this the intended behaviour?

In a nutshell, yes, nil is allowed. I've had the same issue before.
active_model/validations/acceptance.rb
module ActiveModel
module Validations
class AcceptanceValidator < EachValidator # :nodoc:
def initialize(options)
super({ allow_nil: true, accept: "1" }.merge!(options))
setup!(options[:class])
end
# ...
end
# ...
end
# ...
end
In the initializer, it merges allow_nil with the options, so yes, nil (or the lack of a value, I should say) is allowed for that. They mention it in the Rails Guide for acceptance, but I missed it.
This bit me a few times in my tests also - I kept getting passing validations when I was certain they should not pass. Now we know why!

Related

Rails: How do I login a Devise user in an integration test?

I am trying to test that a logged in user can access the admin page.
I have a user set up in the fixtures:
user_one:
email: user_one#test.com
encrypted_password: <%= User.new.send(:password_digest, 'password') %>
In the test, I log in and navigate to the admin page. But the test fails with a redirect.
class AdminTestLoggedIn < ActionDispatch::IntegrationTest
test "Log in" do
post user_session_path, params: {user: {
email: users(:user_one).email,
password: "password"
}}
get admin_path
assert_response :success
end
end
I can see that the redirect points back to the login page:
test "Log in" do
...
assert_redirected_to new_user_session_path
end
So, it looks like I haven't logged in. Where have I gone wrong? My guess is that I haven't handle the password encryption properly. If that's the case, how should it be done?
My initial test failed because I'd set users to :confirmable but user_one had no :confirmed_at date. Without confirmation, the user could not log in and therefore admin_path was redirecting.
The fix was to set :confirmed_at in users.yml:
user_one:
email: user_one#test.com
encrypted_password: <%= User.new.send(:password_digest, 'password') %>
confirmed_at: <%= Time.now %>

Password validation fails in two opposed scenarios

I am working through the Ruby on Rails Tutorial by Michael Hartl and have generated an interesting dilemma. I will have done something wrong, so I need your help finding the issue.
The issue surrounds the validation of a password property within a User model. The initial validation of this property was:
validates :password, presence: true,
confirmation: true,
length: { minimum: 6 }
This requires a minimum length of the password and is designed to satisfy the situation where a new user creates their instance.
I have created the following tests (and I wish I had used Rspec!) guided by the book. These tests check that the validations work:
test "password must not be blank or made up of spaces" do
#user.password = #user.password_confirmation = " "
assert_not #user.valid?
end
test "password must not be empty/nil" do
#user.password = #user.password_confirmation = ""
assert_not #user.valid?
end
So, we’re checking that the password field cannot contain either a space, or a nil entry. With the current validations, these tests pass. All is well.
I have progressed to allowing a user to edit their profile. This enables the user to change their name, email address and password/confirmation if they choose. In order to allow a user not to change their password if they don’t want to, additional validation is added to the password property of the model, adding allow_blank: true such as:
validates :password, presence: true,
confirmation: true,
length: { minimum: 6 },
allow_blank: true # added this!
So, the user can now leave the two password fields blank when they edit their profile if they don’t want to change their profile. This satisfies the test:
test "successful edit" do
log_in_as #user
get edit_user_path(#user)
assert_template 'users/edit'
name = "Foo Bar"
email = "foo#valid.co.uk"
patch user_path(#user), params: { user: { name: name,
email: email,
password: "",
password_confirmation: "" } }
assert_not flash.empty?
assert_redirected_to #user
#user.reload
assert_equal #user.name, name
assert_equal #user.email, email
end
This enables a user to edit just their name & email and, by leaving their two password fields blank, there’s no need to change, or re-enter, their password. This throws a FAIL on a long passing test, as above, such as:
test "password must not be blank or made up of spaces" do
#user.password = #user.password_confirmation = " "
assert_not #user.valid?
end
The test fails because the user is validated. The slightly different test, which tests for nil, not blank, passes:
test "password must not be empty/nil" do
#user.password = #user.password_confirmation = ""
assert_not #user.valid?
end
So a password of “” is caught but a password of “ “ works fine for creating a new user or editing an existing user.
Adding allow_blank: true to the user model validation of password seems to have caused this. So, I am stuck between two tests failing. If I omit allow_blank: true, this test fails (full test pasted above):
test "successful edit" do
.
.
patch user_path(#user), params: { user:
{ name: name,
email: email,
password: "",
password_confirmation: "" } }
.
assert_equal #user.name, name
assert_equal #user.email, email
end
Sending the blank password and password_confirmation fails the test as it isn’t allowed to be blank.
Adding allow_blank: true within the validation fails this test:
test "password must not be blank or made up of spaces" do
#user.password = #user.password_confirmation = " "
assert_not #user.valid?
end
This fail allows a user to be created with a password consisting of spaces. A nil password, i.e. no characters at all, is not allowed. That test works.
This leaves me in the position where I must decide between a user having to change/repeat their two password fields if they edit their profile, OR, allowing a scenario where a user can sign up with a password consisting of one space, or many spaces, as this test doesn’t throw the expected failure message:
test "password must not be blank or made up of spaces" do
#user.password = #user.password_confirmation = " "
assert_not #user.valid?
end
The addition of allow_blank: true bypasses this test or the validation generally. A password of any number of spaces is accepted which is against the validation in the model. How is that possible?
Any thoughts how to test better (apart from using Rspec!). I bow to your greater knowledge.
TIA.
[EDIT]
The suggested changes in the comments below made my test suite green. This was due to the suite being inadequate. To test the unsuccessful integration, the suggested code tested multiple scenarios in one go, such as:
test "unsuccessful edit with multiple errors" do
log_in_as #user
get edit_user_path(#user)
assert_template 'users/edit'
patch user_path(#user), params: { user:
{ name: "",
email: "foo#invalid",
password: "foo",
password_confirmation: "bar" } }
assert_template 'users/edit'
assert_select 'div.alert', "The form contains 3 errors."
end
The key part here is getting the number of expected errors correct so that assert_select gives the right result. I didn't. The errors should be blank name, invalid email format, password too short, pwd & confirmation don't match. The error for short password isn't showing.
I decided to pull out two more tests to demonstrate the failure of the validations of password length and presence. The point of allow_blank is to allow the password & confirmation fields to have nothing in them when editing the user profile so it isn't compulsory to enter the password every time the user profile is edited. These tests are:
test "unsuccessful edit with short password" do
log_in_as #user
get edit_user_path(#user)
assert_template 'users/edit'
patch user_path(#user), params: { user:
{ name: #user.name,
email: "foo#valid.com",
password: "foo",
password_confirmation: "foo" } }
assert_select 'div.alert', "The form contains 1 error."
end
test "unsuccessful edit with blank (spaces) password" do
log_in_as #user
get edit_user_path(#user)
assert_template 'users/edit'
patch user_path(#user), params: { user:
{ name: #user.name,
email: "foo#valid.com",
password: " ",
password_confirmation: " " } }
assert_select 'div.alert', "The form contains 1 error."
end
If the password is changed, then the validation rules should apply, i.e. the password should not be blank and must have a minimum length. That's not what's happening here either in the code the Tutorial book suggests or the amended code using on: :create and on: :edit.
I figured this out so am posting here in case others come across a similar problem.
I amended the validations to include the :update action on the User, rather than just :edit. This covered the action of saving to the database and caught the short password update validations but still allowed passwords made of spaces.
A bit of checking the documentation showed me that using allow_blank: true allows nil and strings made up of spaces. The scenario here wants a nil password to be acceptable, but not a blank one. The alternative validation of allow_nil: true is more appropriate to the scenario here.
The updated code from above looks like, in User.rb:
validates :password, presence: true,
length: { minimum: 6 },
allow_nil: true,
on: [:edit, :update]
validates :password, presence: true,
confirmation: true,
length: { minimum: 6 },
on: :create
The extended test suite is now all green.

Unable to update a user in Rspec with Devise and DeviseTokenAuth, although create and delete work fine

In my Rails 5 app I'm using these gems among others:
gem "devise", "~> 4.2.0"
gem "devise_token_auth"
My User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :rememberable, :trackable, :validatable
include DeviseTokenAuth::Concerns::User
I'm unable to create an Rspec request to update a User. Whatever I've tried, it's failed:
require "rails_helper"
RSpec.describe UsersController, type: :controller do
describe "PATCH #update" do
context "with valid params" do
it "updates the requested user" do
u1 = create(:user)
patch :update, params: { id: u1.to_param, user: attributes_for(:user, name: "name_new", email: "email_new#mail.com") },
format: :json
u1.reload
expect(u1.name).to eq("name_new")
end
end
The thing is that a test for creating and deleting a User works well. Only updating doesn't.
It's sending the new parameters during the patch request, but the returned parameters of a User are always the old ones. What the error is -- I have no idea, it just says the test fails. And again -- only one for updating.
I've tried to use PUT and POST -- still failed.
You need password and password_confirmation to update devise user by default. Try pass them in.
This route will update an existing user's account settings. The default accepted params are password and password_confirmation, but this can be customized using the devise_parameter_sanitizer system. If config.check_current_password_before_update is set to :attributes the current_password param is checked before any update, if it is set to :password the current_password param is checked only if the request updates user password.
Here is the Rspec test I had written some time back.
it 'updates a user\'s email_address and password' do
new_password = Faker::Internet.password(8)
email_address = Faker::Internet.email
request_params = {
'email' => email_address,
'name' => Faker::Name.name,
'password' => new_password,
'password_confirmation' => new_password
}
put '/myapp/v1/system/auth', request_params.to_json, subdomain_login(user.uid, password, account.subdomain)
expect(response).to have_http_status(:ok)
# Validating the password update with sign_in
request_params = {
'email' => email_address,
'password' => new_password
}
post '/myapp/v1/system/auth/sign_in', request_params.to_json,
{ 'ACCEPT' => "application/json",
'Content-Type' => "application/json" }
expect(response).to have_http_status(:ok)
end
Here is the transcript from console
Initial user
#<Myapp::V1::System::User id: "13153737-9efb-47e2-8ac9-85aa28befa2b", provider: "email", uid: "olin#schneiderbailey.name", name: nil, nickname: nil, image: nil, email: "olin#schneiderbailey.name", created_at: "2017-01-13 11:50:01", updated_at: "2017-01-13 11:50:01">
Update Params
{"email"=>"lisa#turner.info", "name"=>"Norberto Zulauf", "password"=>"OnT3Zv19YpZ", "password_confirmation"=>"OnT3Zv19YpZ"}
Response Body
"{\"status\":\"success\",\"data\":{\"id\":\"13153737-9efb-47e2-8ac9-85aa28befa2b\",\"email\":\"lisa#turner.info\",\"name\":\"Norberto Zulauf\",\"provider\":\"email\",\"uid\":\"lisa#turner.info\",\"nickname\":null,\"image\":null,\"created_at\":\"2017-01-13T11:50:01.175Z\",\"updated_at\":\"2017-01-13T11:50:03.436Z\"}}"
Please note that the registrations controller (which also updates the user) is overridden to meet a specific requirement.
Add faker gem, if needed to try the code. Refer to this thread for subdomain_login helper
RSpec: Authenticating before test with devise_auth_token

Railstutorial | Chapter 10 | Exercise 10.4.1.2 | Why does abritrary password setting work?

Description of the Exercise: Railstutorial Exercise 10.4.1.2
The exercise: 'FILL_IN' has to be replaced with the proper code, so that the test is working
test "should not allow the admin attribute to be edited via the web" do
log_in_as(#other_user)
assert_not #other_user.admin?
patch user_path(#other_user), params: {
user: { password: FILL_IN,
password_confirmation: FILL_IN,
admin: FILL_IN} }
assert_not #other_user.FILL_IN.admin?
end
My solution:
test "should not allow the admin attribute to be edited via the web" do
log_in_as(#other_user)
assert_not #other_user.admin?
patch user_path(#other_user), params: {
user: { password: 'password',
password_confirmation: 'password',
admin: true } }
assert_not #other_user.reload.admin?
end
My Question(s):
If you have set the ':admin' attribute to the list of permitted parameters in user_params (in the UsersController class) the test turns 'Red' as it is supposed to. What I don't understand though, is that you can set random passwords and the test is still working properly, like so:
test "should not allow the admin attribute to be edited via the web" do
log_in_as(#other_user)
assert_not #other_user.admin?
patch user_path(#other_user), params: {
user: { password: 'foobar',
password_confirmation: 'foobar',
admin: true } }
assert_not #other_user.reload.admin?
end
Shouldn't the only valid option be 'password' (and not 'foobar' or even '' (i.e. blank)), since the instance variable #other_user contains the values of the User 'archer' from the fixtures file 'users.yml', who has 'password' as a password_digest? Wouldn't it result in a mismatch between the two attributes password_digest(='password') and password(='foobar')? Or is the 'password_digest' attribute from 'archer' somehow updated as well? If so, how does it work?
And why does the test turn 'Green', if you type in an invalid password, like:
test "should not allow the admin attribute to be edited via the web" do
log_in_as(#other_user)
assert_not #other_user.admin?
patch user_path(#other_user), params: {
user: { password: 'foo',
password_confirmation: 'bar',
admin: true } }
assert_not #other_user.reload.admin?
end
Could it be that the 'patch' request aborts due to the incorrect password input and thus also fails to update the admin status? Is that the reason why the test is 'Green' (since admin is still 'nil')?
I already looked up the reference implementation of the sample application by Michael Hartl (https://bitbucket.org/railstutorial/sample_app_4th_ed), but unfortunately he does not provide any code relating to the exercises.
Thanks for help!
In Chapter 10.1, for the update tests, we allowed empty password for tests on the User Controller :
class User < ApplicationRecord
.
.
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
.
.
end
As mentionned in the same chapter, the password is secured with the has_secure_password helper for the online use.
Hope I helped

How do I mock models, instances and helpers in a controller spec?

require 'spec_helper'
describe UsersController do
let(:user){ double(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
before do
mock_model("User")
end
describe 'PATCH #update' do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation })#.and_return true
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "could not update user"
response.status.should == 200
end
end
codebase:
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user, flash: { success: 'succesfully updated user' }
else
flash.now[:error] = "could not update user"
render 'edit'
end
end
While the above spec passes (and passes in just 0.05 seconds!) am I doing it correctly? With the mocks above the request, and the 'normal' expectations below it? It seems a bit clumsy. Not only is it hard to read, but if one expectation fails, all of them will appear to fail.
What makes me think I'm doing it wrong is a weird error I'm getting. See the second line of the describe block, where I'm saying the instance of user (user) should have its update_attributes triggered with updated attributes? Note the and_return true method I've commented out. When it's chained on, running the above spec hits me with this stinker:
1) UsersController should_receive
Failure/Error: patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
NoMethodError:
undefined method `model_name' for RSpec::Mocks::Mock:Class
While this a over my head, I think it's because the user 'instance' isn't actually an instance, just a hash. I thought because I used mock_model and the double inherits from that mocked model, all of the active-record-y stuff such as the 'model_name' method would be set.
Anyway, how should I write this spec properly? I don't want to use FactoryGirl, I want to keep it all contained so my specs are fast and accurately report a fault.
Yes, in this case of controller testing, your basic flow is "normal", except that your innermost code needs to be within an it block (and perhaps is and is just not transcribed properly).
However, you're not making use of the mock_model call, since you're not doing anything with the result. This method doesn't make any fundamental changes to the class whose string name you pass it, it simply creates a mock object that simulates an ActiveRecord instance, which in your case you effectively discard. Like all RSpec doubles, the first parameter is just giving it a name that can be used for error messages and such.
So yes, the reason you're getting the error when you return true is that redirect_to expects #user to be an ActiveRecord instance and while it's not just a Hash as you suggest it might be, it does not have the model_name method it needs.
There are lots of ways to rewrite this, particularly if you want to support both the success and failure cases, but one way that minimizes the changes is (not fully tested):
describe UsersController do
let(:user){ mock_model(User, id: 2, name: "Jimbo", email: 'jimbo#email.com', password: 'passwordhuzzah', password_confirmation: 'passwordhuzzah') }
describe 'PATCH #update' do
it "should fail in this case" do
User.should_receive(:find).with(user.id.to_s).and_return user
user.should_receive(:update_attributes).with({ "email" => user.email, "name" => user.name, "password" => user.password, "password_confirmation" => user.password_confirmation })#.and_return true
patch :update, id: user.id, user: { email: user.email, name: user.name, password: user.password, password_confirmation: user.password_confirmation }
flash[:error].should == "could not update user"
response.status.should == 200
end
end
end

Resources