I'm trying to unit test RoR api controllers using rspec-rails gem.
All my controllers are implemented this ProtectedController class which intercept all request and validate the authorization header
class V1::ProtectedController < V1::BaseController
include JsonWebTokenAuthentication
attr_reader :current_user
before_action :validate_token!
# Check if authorize has been called (Pundit gem)
after_action :verify_authorized
private
def validate_token!
authorization_header = request.headers['Authorization'] || ''
puts "header: #{request.headers['Authorization']}"
jwt = JsonWebToken.new(authorization_header.gsub('Bearer ', ''))
if payload = jwt.verify
account = Account.find_by(id: payload[:user][:id])
if account.nil? || account.token_checksum == payload[:user][:token_checksum]
# If no account found still authorize because it is likely an app token
@current_user = Account.from_jwt_data(payload[:user])
end
end
return if @current_user
raise JsonWebTokenAuthentication::Unauthorized, 'invalid token'
end
end
In order to simulate this authentication system for testing I create this AuthHelper module which will return an admin JWT ( later I can create other logins for other roles or I can pass login creden to this function)
module AuthHelper
include Rack::Test::Methods
def app
Rails.application # This assumes you're using a Rails application
end
def admin_login
params = {
grant_type: 'password',
email: '[email protected]',
password: 'preci666'
}
post '/api/v1/auth/token', params
expect(last_response.status).to eq(200)
auth_response = JSON.parse(last_response.body)
@access_token = auth_response['access_token']
end
end
Now all what I need to do is to pass the JWT in all my requests. which I did in for example my workers_controller_spec as shown bellow
require 'rails_helper'
RSpec.describe V1::WorkersController, type: :request do
include AuthHelper
before {
admin_login
auth = "Bearer #{@access_token}"
}
describe "GET #index" do
it 'assigns @workers' do
worker1 = FactoryBot.create(:worker)
worker2 = FactoryBot.create(:worker)
#request.headers['Authorization'] = auth
auth = "Bearer #{@access_token}"
#request.headers['Authorization'] = auth
get v1_workers_path , headers: {
Authorization: auth
}
expect(response).to have_http_status(200)
end
end
end
For me all looks correct like this but not for rspec, the response status is always 401. and the weird part that when I debugg the request.headers['Authorization'] in my protected controller I found it empty and in request.headers.inspect I can see my Authorization header :/
When I tried to pass the header as in the commented part #request.headers['Authorization'] = auth I get this error
1) V1::WorkersController GET #index assigns @workers
Failure/Error: request.headers['Authorization'] = auth
ArgumentError:
wrong number of arguments (given 0, expected 1..2)
puts "header: #{request.headers['Authorization']}" in my protected controller returns
header:
puts "request: #{request.inspect}" returns
header: request: #<ActionDispatch::Request GET
"http://example.org/api/v1/workers?
headers[Authorization]=Bearer+eyJhbGciOiJSUzI1NiJ9.eyJp..." for
127.0.0.1>
ruby version : 2.7.7 rails version: 7.0.4.2 rspec-rails version: 6.0.1
Any one can help me to find a solution? or is there more proper way to simuate my authentication system and return the current_user?