I'm doing the Michael Hartl Rails tutorial (using Rails 4 and RSpec-Rails 3.3.3) and implementing users with admin privileges, which it achieves by adding an admin boolean attribute to the User model. I've instead decided to use a spearate Admin model (User has_one Admin; Admin belongs_to User), which simply stores the user_id; if a user's id exists in the table then it is an admin (I feel this will be more efficient long-term given the anticipated ratio of admins to non-admins).
A user controller test is suggested to ensure that the permitted params do not allow the admin attribute to be edited via the web like so (user is created in the database using Factory Girl):
patch :update, id: user, user: { password: user[:password], password_confirmation: user[:password_confirmation], admin: '1' }
To test my version, I've attempted the following RSpec test:
context 'attempt to assign non-admin user as admin via update request via web' do
it 'will not update admin status' do
session[:user_id] = user.id # user is logged in as required to make patch request
expect { patch :update, id: user, user: { name: user.name, email: user.email, admin: { user_id: user.id } } }.to change { Admin.count }.by 0
end
end
This tests passes, but not by merit of the permitted params being correctly restricted. To check that the inverse works, I want to test that allowing the admin params in the User model will actually make the change to the Admin table.
I have updated user_controller:
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation, admin: :user_id)
end
And users_controller_spec (checks Admin count changes by 1):
context 'attempt to assign non-admin user as admin via update web request' do
it 'will not update admin status' do
session[:user_id] = user.id
expect { patch :update, id: user, user: { name: user.name, email: user.email, admin: { user_id: user.id } } }.to change { Admin.count }.by 1
end
end
Which gives this error (docs say it is raised when an object assigned to an association has an incorrect type):
Failure/Error: expect { patch :update, id: user, user: { name: user.name, email: user.email, admin: { user_id: user.id } } }.to change { Admin.count }.by 1
ActiveRecord::AssociationTypeMismatch:
Admin(#70320649341140) expected, got ActionController::Parameters(#70320662053980)
I have a feeling I may have this entirely the wrong way round: that the Admin model should only be updated directly rather than through the User model, and perhaps by separating Admin out into a separate model like this that such a malicious patch request to give a user admin status may not even be possible (there is no admin_controller or admin routes as I anticipate such assignation will be made at database level; only a simple Admin model which states its belongs_to association with User).
I'd really appreciate some advice on whether there is a test I should be writing given my circumstances.
Thanks in advance.
btw, does Michael Hartl not have his own support forum, and/or a specific tag on SO?
so I'm getting the error on the branch:
1) UsersController attempt to assign non-admin user as admin via update web request will update admin status
Failure/Error: expect { patch :update, id: user, user: { name: user.name, email: user.email, admin: { user_id: user.id } } }.to change { Admin.count }.by 1
ActiveRecord::AssociationTypeMismatch:
Admin(#70235772428120) expected, got ActionController::Parameters(#70235708075860)
one general tip is just don't write controller specs ... prefer only features and unit tests
here's how I'm dissecting the problem
[tansaku@Samuels-MBP-2:~/Documents/Github/MakersAcademy/Students/April2015/AndyGout/theatrebase ((ac406cd...))]$
→ rspec ./spec/controllers/users_controller_spec.rb:89
Run options: include {:locations=>{"./spec/controllers/users_controller_spec.rb"=>[89]}}
UsersController
attempt to assign non-admin user as admin via update web request
[29, 38] in /Users/tansaku/Documents/Github/MakersAcademy/Students/April2015/AndyGout/theatrebase/app/controllers/users_controller.rb
29: @page_title = @user.name
30: end
31:
32: def update
33: require 'byebug' ; byebug
=> 34: if @user.update_attributes(user_params)
35: flash[:success] = "Profile updated successfully: #{@user.name}"
36: redirect_to @user
37: else
38: @page_title = User.find(params[:id]).name
(byebug) @user
#<User:0x007fede0b81b60>
(byebug) user_params
{"name"=>"Andy Gout", "email"=>"[email protected]", "admin"=>{"user_id"=>"1"}}
I think the problem here seems like you aren't set up to create the new admin object via a simple params hash
I see two ways round this