-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Testing protected controllers
Few things you should be aware of when testing controllers protected by doorkeeper.
If you're using RSpec, the recommendation from their release notes is to use request specs, not controller specs:
The official recommendation of the Rails team and the RSpec core team is to write request specs instead.
Here's one example of an RSpec request test:
describe 'Credentials', type: :request do
context 'when unauthorized' do
it 'fails with HTTP 401' do
get '/me'
expect(response).to have_http_status(:unauthorized)
end
end
context 'when authorized' do
let(:application) { FactoryBot.create(:application) }
let(:user) { FactoryBot.create(:user) }
let(:token) { FactoryBot.create(:access_token, application: application, resource_owner_id: user.id) }
it 'succeeds' do
get '/me', params: {}, headers: { 'Authorization': 'Bearer ' + token.token }
expect(response).to be_successful
expect(response.body).to eq(user.to_json)
end
end
end
If you have an action that requires a specific scope, you can specify the scopes in your factory, for example:
FactoryBot.define do
factory :access_token, class: 'Doorkeeper::AccessToken' do
application
expires_in { 2.hours }
scopes { 'public' }
end
end
in the majority of cases, you'll only need to stub the doorkeeper_token
method in your controller:
describe Api::V1::ProfilesController do
describe 'GET #index' do
let(:token) { instance_double('Doorkeeper::AccessToken', :acceptable? => true) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
# controller.stub(:doorkeeper_token) { token } # => RSpec 2
end
it 'responds with 200' do
get :index, :format => :json
response.status.should eq(200)
end
end
end
Stubbing
-
:acceptable? => true
will bypass the doorkeeper filter, since the token is valid -
:acceptable? => false
will force the response status to be401 unauthorized
You can also stub the resource_owner_id
to have the stubben token authenticate a specific user, e.g.:
let(:token) { instance_double('Doorkeeper::AccessToken', acceptable?: true, resource_owner_id: user.id) }
If you have an action that requires a specific scope, you will need to stub the token scope:
# controllers/api/v1/profiles_controller.rb
class Api::V1::ProfilesController < ApiController
before_action :only => [:create] do
doorkeeper_authorize! :write
end
# ...
def create
respond_with 'api_v1', Profile.create!(params[:profile])
end
end
# spec/controllers/api/v1/profiles_controller_spec.rb
describe 'POST #create (with scopes)' do
let(:token) do
stub :acceptable? => true, :scopes => [:write]
end
before do
allow(controller).to receive(:doorkeeper_token) { token }
# controller.stub(:doorkeeper_token) { token } # => RSpec 2
end
it 'creates the profile' do
Profile.should_receive(:create!) { stub_model(Profile) }
post :create, :format => :json
response.status.should eq(201)
end
end
If you need to test the controller fully integrated with your app, you'll need to create the necessary models:
describe Api::V1::CredentialsController do
describe 'GET #me (integrated)' do
let!(:application) { Factory :application } # OAuth application
let!(:user) { Factory :user }
let!(:token) { Factory :access_token, :application => application, :resource_owner_id => user.id }
it 'responds with 200' do
get :me, :format => :json, :access_token => token.token
response.status.should eq(200)
end
it 'returns the user as json' do
get :me, :format => :json, :access_token => token.token
response.body.should == user.to_json
end
end
end
You can accomplish the same test above with MiniTest (no Rspec) by creating a mock Token and stubbing the acceptable?
method:
describe API::V1::ProfilesController do
describe 'unauthorized' do
it "should return unauthorized" do
get :index
assert_response :unauthorized
end
end
describe 'authorized' do
let(:user) { users(:one) }
let(:token) { Minitest::Mock.new }
setup do
token.expect(:acceptable?, true, [Doorkeeper::OAuth::Scopes])
end
it "should return success" do
@controller.stub :doorkeeper_token, token do
get :index
assert_response :success
end
end
end
end
For more examples, check the doorkeeper provider app on GitHub here.