jwir3
jwir3

Reputation: 6209

Why is there no user object passed as params for this rails_spec test?

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_actions in Rails 5?

Upvotes: 0

Views: 203

Answers (1)

Subash
Subash

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

Related Questions