Reputation: 6209
I have the following rails_spec test:
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
fixtures :users, :clients
include AuthHelper
before do
@request.headers["Content-Type"] = 'application/json'
@request.headers["Accept"] = 'application/json'
end
describe '#create' do
context 'for a user with a valid authorization token' do
context 'having admin privileges' do
before do
init_headers_for_admin
end
context 'submitted with a valid set of parameters' do
it 'should create a new user having the submitted parameters' do
post :create, params: {
:name => 'Roland Deschain',
:email => '[email protected]',
:role => 'user',
:username => 'roland'
}
expect(response).to have_http_status(:ok)
user = json
expect(user).to_not be_nil
expect(user.name).to eq('Roland Deschain')
expect(user.email).to eq('[email protected]')
expect(user.role).to eq('user')
expect(user.username).to eq('roland')
expect(user.created_at).to eq(user.updated_at)
end
end
I also have the following controller definition:
def create
# get_required_fields.each { |field|
# unless params.has_key? field
# render :json => { :error => 'A ' + field.to_s + ' must be specified in order to create a new User'}, :status => :unprocessable_entity and return
# end
# }
username = params[:username]
unless User.find_by_username(params[:username]) == nil
render :json => { :error => I18n.t('error_username_conflict') }, :status => :conflict and return
end
unless User.find_by_email(params[:email]) == nil
render :json => { :error => I18n.t('error_email_conflict') }, :status => :conflict and return
end
end
However, when I run the test, the params
variable is always an empty hash. If I uncomment the code that detects whether I have the required parameters, it always returns an UNPROCESSABLE_ENTITY
, rather than the NO_CONTENT
I would expect if a username
, name
, and email
are present. I'm not quite sure why, but I can't seem to get it to pass actual data via the post
method. I've been looking at this for a while, and trying to debug it, but I can't seem to get it to pass the parameters correctly.
I'm using Rails 5 and rspec_rails 3.8.1.
EDIT:
Something is really wrong here, because if I simplify create
to be:
def create
unless params.has_key?('name') && params.has_key?('username') && params.has_key?('email')
render :json => {:error => I18n.t('error_must_specify_username_email_and_name')}, :status => :unprocessable_entity and return
end
end
And execute the same spec as before (obviously expecting it to return no_content
, and not unprocessable_entity
), I end up with the same thing happening - in debug mode, it looks like the params
variable upon entering this method is:
{"controller"=>"users", "action"=>"create", "user"=>{}}
So, first off, it's setting the user
parameter, which isn't exactly what I intended, and moreover, it's setting it to an empty hash. I'm not sure what's causing this. I feel like it's something I'm forgetting to do from Rails 5.0, because this pattern seems to work fine in Rails 4.0.
EDIT 2:
I modified the test to be:
it 'should create a new user record' do
post :create, params: { user: @user }, as: :json
expect(response).to have_http_status(:ok)
expect(json.name).to eq(@user.name)
expect(json.email).to eq(@user.email)
expect(json.username).to eq(@user.username)
expect(json.role).to eq(@user.role)
end
And modified the UsersController
class to look like:
class UsersController < ApplicationController
include AuthHelper
before_action :validate_api_key_for_auth, only: :login
before_action :authenticate_as_admin, only: [:create]
def create
p user_params
unless user_params.has_key?('name') && user_params.has_key?('username') && user_params.has_key?('email')
render :json => {:error => I18n.t('error_must_specify_username_email_and_name')}, :status => :unprocessable_entity and return
end
end
def user_params
params[:user].permit([:username, :name, :email, :role]).to_h
end
end
It is printing out: {}
for the users
hash from user_params
, but I don't understand why this is the case.
EDIT 3:
authenticate_as_admin
is screwing up the parameters somehow. If I change the definition of authenticate_as_admin
to true
, it shows the correct hash and returns no_content
as expected. The definition of the authenticate_as_admin
function is below (defined in ApplicationController
):
def authenticate_as_admin
authenticate_as :admin
end
def authenticate_as(role)
# First, get the headers for the request
authorization_header = get_authorization_header
unless authorization_header
render :json => {:error => I18n.t('error_must_be_logged_in')}, :status => :forbidden and return false
end
auth_token = authorization_header.split[1]
decoded_token = decode_authorization_header authorization_header
unless decoded_token
render :json => {:error => I18n.t('error_invalid_token')}, :status => :forbidden and return false
end
payload = decoded_token[0]
user_id = payload['user_id']
auth_token_expiration_date = payload['expiration_date']
@authenticated_user = User.find(user_id) rescue nil
unless @authenticated_user and @authenticated_user.auth_token == auth_token
render :json => {:error => I18n.t('error_must_be_logged_in')}, :status => :forbidden and return false
end
if auth_token_expiration_date < DateTime.now
render :json => {:error => I18n.t('error_auth_token_expired')}, :status => :forbidden and return false
end
unless @authenticated_user.role_at_least? role
render :json => {:error => I18n.t('error_insufficient_permission')}, :status => :forbidden and return false
end
true
end
I can't seem to see why this would muck up the params, though, especially considering this worked fine in Rails 4.2. Anyone see anything I'm missing?
EDIT 4:
I feel like I'm on the right track. It seems as though if the authenticate_as()
function returns false
, I get this behavior. If a before_action
returns false
, what should be the expected result of a controller action? I expected that the method called in the before_action
would perform the appropriate render ...
call, and the before_action
would silently fail, never calling the function that would have been called. I'm still not quite sure why the function is failing, but I'm trying to understand the behavior that should happen. Perhaps authentication checking isn't a great use-case for before_action
s in Rails 5?
Upvotes: 0
Views: 203
Reputation: 3168
Update Not valid for rails 5
Try this,
post :create, {
:name => 'Roland Deschain',
:email => '[email protected]',
:role => 'user',
:username => 'roland'
}
when you have the params: {}
it gets sent as a nested hash, { params: {...} }
EDIT 1
Instead of passing params
to post :create
try stubbing out params
call in controller, something like
allow(subject).to receive(:params).and_return(params)
and inside describe block,
let(:params) { ActionController::Parameters.new({ :name => '', :email => '' ... }) }
or just a regular hash if you don't need ActionController::Parameters
and call the :create
without any parameters,
post :create
see if this helps, I am using this pattern in our specs in another project which is on route to rails 5.
Upvotes: 1