0

I have a rails-api application that I'm testing using Rspec. The Application uses devise_token_auth gem for authentication and cancancan gem for authorization.

devise_token_auth requires that the client include these authentication headers in every request: access-token, client, expiry, uid. These headers are available in a response after successful authentication using email and password.

I have decided to use a solution provided in this answer to set these headers during testing.

In ability.rb I have this:

## models/ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
   if user.role? :registered
     can :create, Post, user_id: user.id
     can :update, Post, user_id: user.id
     can :destroy, Post, user_id: user.id
     can :read, Post
   end
  end
end 

posts#show action in PostsController looks like this:

## controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :authenticate_user!
  load_and_authorize_resource

  def show
    render json: @post
  end
end

I have rescued CanCan::AccessDenied error to render a json message and a status of 403 forbidden in ApplicationController

rescue_from CanCan::AccessDenied do |exception|
  render json: {"message" => "unauthorized"}.to_json, :status => 403
end

I have this in spec/support/session_helper.rb

module SessionHelper
 def retrieve_access_headers

 ##I have a user with these credentials and has a "registered" role. in the the test db.

   post "/auth/sign_in", params: {:email => "registered_user@gmail.com", :password => "g00dP@ssword"}, headers: {'HTTP_ACCEPT' => "application/json"}

##These two pass
expect(response.response_code).to eq 200
expect(response.body).to match(/"email": "registered_user@gmail.com"/)

     access_headers = {"access-token" => response.headers["access-token"],
     "client" => response.headers["client"],
     "expiry" => response.headers["expiry"],
     "uid" => response.headers["uid"],
     "token-type" => response.headers["token-type"],
     'HTTP_ACCEPT' => "application/json"
   }

  return access_headers
 end

end

I have this in spec/support/requests_helper.rb

module RequestsHelper
  def get_with_token(path, params={}, headers={})
   headers.merge!(retrieve_access_headers)
   get path, params: params, headers: headers

   #### this outputs the expected headers on a json string and they seem fine ####
   puts "headers: "+headers.to_json
  end
end

I have included the two helpers in rails_helper.rb as shown below:

Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }

RSpec.configure do |config|
   config.include SessionHelper, type: :request
   config.include RequestsHelper, type: :request
end

Finally I have a request spec in spec/request/posts/show_spec.rb

require 'rspec/its'
require 'spec_helper'
require 'rails_helper'

RSpec.describe 'GET /posts/:id', :type => :request do

    let(:post) {create(:post)}
    let(:id) {post.id}
    let(:request_url) {"/posts/#{id}"}

  context 'with a registered user' do
   it 'has a status code of 200' do
     get_with_token request_url
     expect(response).to have_http_status(:success)
   end
  end
end

I expect this to pass but it fails with message:

Failure/Error: expect(response).to have_http_status(:success)
   expected the response to have a success status code (2xx) but it was 403

The application works as expected on a browser.

What I'm a doing wrong?

Community
  • 1
  • 1
Optimus Pette
  • 3,250
  • 3
  • 29
  • 50
  • There are two things that stand out: a) you are not adding `before_filter :authenticate_user!`(Rails 3) or `before_action :authenticate_user!` to your controller (unless you have added it directly to the `ApplicationController`class). b) if you are using `load_and_authorize_resource` you should not initialize `@post` in your controller action (you are effectively by-passing the cancancan framework this way) – Marco Sandrini Jan 12 '16 at 19:58
  • @MarcoSandrini Thank you for that observation, i've made the edits in my application and the original question as well, but the spec is still failing as before. – Optimus Pette Jan 12 '16 at 20:29
  • Next thing: are you linking the user and the post in your factory? In other words, does `create(:post)` set the correct user_id in the post object? Another thing worth checking is to dump the `current_user` to the console in the `show` method to see if the error is whether in authenticating the user or in authorizing the resource... – Marco Sandrini Jan 12 '16 at 20:48
  • @MarcoSandrini This is the first spec i'm writing for the application, i wanted to move a step at a time to avoid being overwhelmed, I have not linked the user yet since the `ability` does not require the `user` be linked to a `post` to `read` any `post`. I will link them when i start writing the spec for `create`, `update` and `destroy` which require that the user performing those actions be the owner of the resource. – Optimus Pette Jan 12 '16 at 20:53
  • You are right, I read the ability file to quickly.... I just now noticed that you invoke get like `get path, params: params, headers: headers`, while I think it should be like `get path, params, headers`.... – Marco Sandrini Jan 12 '16 at 21:16
  • @MarcoSandrini I'm on rails 5.0 beta, I think that's the new way of doing it, I got a deprecation warning when I did it the way you propose: `DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept only the following keyword arguments in future Rails versions: params, headers, env, xhr Examples: get '/profile', params: { id: 1 }, headers: { 'X-Extra-Header' => '123' }, env: { 'action_dispatch.custom' => 'custom' }, xhr: true ` – Optimus Pette Jan 12 '16 at 21:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100519/discussion-between-marco-sandrini-and-optimus-pette). – Marco Sandrini Jan 12 '16 at 21:26

0 Answers0