Rails/Rspec Make tests pass with http basic authentication - ruby-on-rails

Here my http basic authentication in the application controller file (application_controller.rb)
before_filter :authenticate
protected
def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == "username" && password == "password"
end
end
and the default test for the index action of my home controller (spec/controllers/home_controller_spec.rb)
require 'spec_helper'
describe HomeController do
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
Test doesn't run because of the authentication method. I could comment "before_filter :authenticate" to run them but I would like to know if there is way to make them worked with the method.
Thank you!

Update (2013): Matt Connolly has provided a GIST which also works for request and controller specs: http://gist.github.com/4158961
Another way of doing this if you have many tests to run and don't want to include it everytime (DRYer code):
Create a /spec/support/auth_helper.rb file:
module AuthHelper
def http_login
user = 'username'
pw = 'password'
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user,pw)
end
end
In your test spec file:
describe HomeController do
render_views
# login to http basic auth
include AuthHelper
before(:each) do
http_login
end
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
end
Credit here - Archived site

Sorry I didn't seek enough, the solution seems to be the following:
describe "GET 'index'" do
it "should be successful" do
#request.env["HTTP_AUTHORIZATION"] = "Basic " + Base64::encode64("username:password")
get 'index'
response.should be_success
end
end

Some answers suggest to set request.env which is unsafe, because request can be nil and you will end up with private method env' called for nil:NilClass, especially when run single tests with rspec -e
Correct approach will be:
def http_login
user = 'user'
password = 'passw'
{
HTTP_AUTHORIZATION: ActionController::HttpAuthentication::Basic.encode_credentials(user,password)
}
end
get 'index', nil, http_login
post 'index', {data: 'post-data'}, http_login

For me, with Rails 6, I need keyword arguments for rspec get method like .. get route, params: params, headers: headers
Auth Helper method
module AuthHelper
def headers(options = {})
user = ENV['BASIC_AUTH_USER']
pw = ENV['BASIC_AUTH_PASSWORD']
{ HTTP_AUTHORIZATION: ActionController::HttpAuthentication::Basic.encode_credentials(user,pw) }
end
def auth_get(route, params = {})
get route, params: params, headers: headers
end
end
and the rspec request test.
describe HomeController, type: :request do
include AuthHelper
describe "GET 'index'" do
it "should be successful" do
auth_get 'index'
expect(response).to be_successful
end
end
end

When using Rspec to test Grape APIs, the following syntax works
post :create, {:entry => valid_attributes}, valid_session
where valid_session is
{'HTTP_AUTHORIZATION' => credentials}
and
credentials = ActionController::HttpAuthentication::Token.encode_credentials("test_access1")

These are great solutions for controller and request specs.
For feature tests using Capybara, here is a solution to make HTTP Basic authentication work:
spec/support/when_authenticated.rb
RSpec.shared_context 'When authenticated' do
background do
authenticate
end
def authenticate
if page.driver.browser.respond_to?(:authorize)
# When headless
page.driver.browser.authorize(username, password)
else
# When javascript test
visit "http://#{username}:#{password}##{host}:#{port}/"
end
end
def username
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_username
end
def password
# Your value here. Replace with string or config location
Rails.application.secrets.http_auth_password
end
def host
Capybara.current_session.server.host
end
def port
Capybara.current_session.server.port
end
end
Then, in your spec:
feature 'User does something' do
include_context 'When authenticated'
# test examples
end

My solution:
stub_request(method, url).with(
headers: { 'Authorization' => /Basic */ }
).to_return(
status: status, body: 'stubbed response', headers: {}
)
Use gem webmock
you can tighten verification by change:
/Basic */ -> "Basic #{Base64.strict_encode64([user,pass].join(':')).chomp}"
URL - can be a regular expression

Related

How to do request spec for JWT authenticate app using RSpec

I have a Rails 5 API only app and using knock to do JWT authenticate.
After complete the model and model spec, I start to do the request spec.
But I have no idea how to complete the authentication inside the request spec in the right way,
My users controller,
module V1
class UsersController < ApplicationController
before_action :authenticate_user, except: [:create]
end
end
Application controller,
class ApplicationController < ActionController::API
include Knock::Authenticable
include ActionController::Serialization
end
My stupidest solution (call the get token request to get the JWT before the rest request),
context 'when the request contains an authentication header' do
it 'should return the user info' do
user = create(:user)
post '/user_token', params: {"auth": {"email": user.email, "password": user.password }}
body = response.body
puts body # {"jwt":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0ODgxMDgxMDYsInN1YiI6MX0.GDBHPzbivclJfwSTswXhDkV0TCFCybJFDrjBnLIfN3Q"}
# use the retrieved JWT for future requests
end
end
Any advice is appreciated.
def authenticated_header(user)
token = Knock::AuthToken.new(payload: { sub: user.id }).token
{ 'Authorization': "Bearer #{token}" }
end
describe 'GET /users?me=true' do
URL = '/v1/users?me=true'
AUTH_URL = '/user_token'
context 'when the request with NO authentication header' do
it 'should return unauth for retrieve current user info before login' do
get URL
expect(response).to have_http_status(:unauthorized)
end
end
context 'when the request contains an authentication header' do
it 'should return the user info' do
user = create(:user)
get URL, headers: authenticated_header(user)
puts response.body
end
end
end
With the help of Lorem's answer, I was able to implement something similar for my request spec. Sharing it here for others to see an alternate implementation.
# spec/requests/locations_spec.rb
require 'rails_helper'
RSpec.describe 'Locations API' do
let!(:user) { create(:user) }
let!(:locations) { create_list(:location, 10, user_id: user.id) }
describe 'GET /locations' do
it 'reponds with invalid request without JWT' do
get '/locations'
expect(response).to have_http_status(401)
expect(response.body).to match(/Invalid Request/)
end
it 'responds with JSON with JWT' do
jwt = confirm_and_login_user(user)
get '/locations', headers: { "Authorization" => "Bearer #{jwt}" }
expect(response).to have_http_status(200)
expect(json.size).to eq(10)
end
end
end
confirm_and_login_user(user) is defined in a request_spec_helper which is included as a module in rails_helper.rb:
# spec/support/request_spec_helper.rb
module RequestSpecHelper
def json
JSON.parse(response.body)
end
def confirm_and_login_user(user)
get '/users/confirm', params: {token: user.confirmation_token}
post '/users/login', params: {email: user.email, password: 'password'}
return json['auth_token']
end
end
I'm using the jwt gem for generating my tokens as described in this SitePoint tutorial (https://www.sitepoint.com/introduction-to-using-jwt-in-rails/)
Lorem's answer mostly worked for me. I got unrecognized keyword setting headers: on the get. I modified the authenticated_header method and put it in support/api_helper.rb so I could reuse it. The modification is to merge the auth token into request.headers.
# spec/support/api_helper.rb
module ApiHelper
def authenticated_header(request, user)
token = Knock::AuthToken.new(payload: { sub: user.id }).token
request.headers.merge!('Authorization': "Bearer #{token}")
end
end
In each spec file testing the api, I include api_helper.rb. And I call authenticated_header just before the get statement when testing the case of valid authentication...
# spec/controllers/api/v2/search_controller_spec.rb
RSpec.describe API::V2::SearchController, type: :controller do
include ApiHelper
...
describe '#search_by_id' do
context 'with an unauthenticated user' do
it 'returns unauthorized' do
get :search_by_id, params: { "id" : "123" }
expect(response).to be_unauthorized
end
end
context 'with an authenticated user' do
let(:user) { create(:user) }
it 'renders json listing resource with id' do
expected_result = { id: 123, title: 'Resource 123' }
authenticated_header(request, user)
get :search_by_id, params: { "id" : "123" }
expect(response).to be_successful
expect(JSON.parse(response.body)).to eq expected_result
end
end
The key lines in this second test are...
authenticated_header(request, user)
get :search_by_id, params: { "id" : "123" }

Abstracting out common setup steps in RSpec test

I'm trying to get my head around the best way to create a user token in my RSpec tests, and write them as eloquently as I can.
Below is one example for my Project class. From the spec below, you'll see I'm using DoorKeeper to keep the API endpoints secure on all actions other than show.
The problem I'm running into is how best to create that #access_token.
This works, passing all the examples, however I'm worried I'm not adhering to DRY principles. If lots of actions/contexts require an #access_token is there a way I could abstract this out somewhere to a helper of sorts?
Thanks in advance
## projects_spec.rb
require 'spec_helper'
describe "Projects API" do
describe "#index" do
FactoryGirl.create(:project)
context 'with a valid token' do
before(:each) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
#access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
it 'returns a list of projects' do
get '/api/v1/projects', access_token: #access_token.token
expect(response.status).to eq(200)
# check the JSON is as we expect
end
end
context 'without a token' do
it 'responds with 401' do
get '/api/v1/projects'
expect(response.status).to eq(401)
end
end
end
describe "#create" do
context 'with a valid token' do
before(:each) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
#access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
context 'with required params' do
project_params = {
name: "Win the lottery",
strapline: "The best feeling in the world"
}
it "creates a project and responds with 201" do
post "/api/v1/projects", :project => project_params, access_token: #access_token.token
expect(response.status).to eq(201)
# check the JSON is as we expect
end
end
context 'without required params' do
project_params = {
strapline: "Stepney City Farm's pallets, woodchips and compost",
}
it "responds with 422 and no record created" do
post "/api/v1/projects", :project => project_params, access_token: #access_token.token
expect(response.status).to eq(422)
json = JSON.parse(response.body)
expect(json['project']['errors'].length).to eq(1)
end
end
end
context 'without a token' do
it 'responds with 401' do
get '/api/v1/projects'
expect(response.status).to eq(401)
end
end
end
describe "#show" do
it 'returns a projects' do
project = FactoryGirl.create(:project, name: "A new project")
get "/api/v1/projects/#{project.id}"
expect(response.status).to eq(200)
json = JSON.parse(response.body)
expect(json['project']['name']).to eq(project.name)
expect(GroupRun.last.name).to eq(project.name)
# check the JSON is as we expect
end
end
end
I have a few techniques that I use to deal with this stuff.
The first is to use plain ruby methods over let. This is just my preference, I think it adds clarity to tests. Check this for more about that: http://robots.thoughtbot.com/lets-not
And then, I have a helper method for auth stuff. I'm using Devise for authentication, so my implementation will be different than yours, but this sets the HTTP_AUTHORIZATION header for every request made to my app after calling the helper method in the tests.
module Requests
module AuthenticationHelpers
def basic_http_auth(user)
credentials = ActionController::HttpAuthentication::Basic.encode_credentials(user.email,user.password)
Rack::MockRequest::DEFAULT_ENV['HTTP_AUTHORIZATION'] = credentials
end
end
end
And then in the actual test, I'll do something like this:
describe "GET /api/v1/messages" do
it 'returns an array of messages' do
get '/api/v1/messages'
basic_http_auth(FactoryGirl.create(:user))
expect(response).to eq(200)
end
end
So, if you're going to be using this across a lot of the API tests, move it into a support helper. And, if you're calling something multiple times in the same file, write a method (or put it in a let call) to DRY your your tests.
Building on Matthew's answer, with Doorkeeper, I ended up using the following:
module TokenMacros
def generate_access_token_for(user = nil)
user ||= FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
access_token = Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
access_token.token
end
end
which then allows me to call...
let(:user) { FactoryGirl.create(:user) }
let(:token) { generate_access_token_for(user) }
splendid
The easiest thing to do to avoid the duplication would be to define the token in the highest level describe so that it's available for all the examples in your spec.
In addition, to avoid any performance issue for the examples that don't depend on it, you can use let rather than before to define the token since let is lazily evaluated.
What you'd have then, is the following:
let(:access_token) do
user = FactoryGirl.create(:user)
authentication = FactoryGirl.create(:authentication, user: user)
application = Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "http://app.com")
Doorkeeper::AccessToken.create!(:application_id => application.id, :resource_owner_id => authentication.identity.id)
end
You'd need to also change your references to this token from #access_token to access_token, since you're defining a method and not an instance variable.

Generating RSpec Examples via Functions

I'm trying to add a function to allow for quick testing of redirects for unauthenticated users. Here's what I have so far:
def unauthenticated_redirects_to redirect_path #yeild
context "when not signed in" do
it "redirects to #{redirect_path}" do
yield
expect(response).to redirect_to redirect_path
end
end
end
describe SomeController do
describe 'GET #show' do
unauthenticated_redirects_to('/some_path') { get :show }
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #whatever' do
unauthenticated_redirects_to('/some_other_path') { get :whatever }
end
end
This doesn't work, however, since the scope and context of the primary describe block is not available to the block passed to unauthenticated_redirects_to. This reasonably leads to the error: undefined method `get' for RSpec::Core::ExampleGroup::Nested_1::Nested_2:Class.
Is there a way around this or is there a cleaner way to accomplish something similar which I should consider?
Here's an approach using shared examples which triggers the example based on shared metadata (:auth => true in this case) and which parses the example group description to pick up some key parameters.
require 'spec_helper'
class SomeController < ApplicationController
end
describe SomeController, type: :controller do
shared_examples_for :auth => true do
it "redirects when not signed in" do
metadata = example.metadata
description = metadata[:example_group][:description_args][0]
redirect_path = metadata[:failure_redirect]
http_verb = description.split[0].downcase.to_s
controller_method = description.match(/#(.*)$/)[1]
send(http_verb, controller_method)
expect(response).to redirect_to redirect_path
end
end
describe 'GET #show', :auth => true, :failure_redirect => '/some_path' do
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #whatever', :auth => true, :failure_redirect => '/some_other_path' do
end
end
For completeness, here's another shared examples approach, this time using a block parameter with a before call which avoids the original scope problem:
require 'spec_helper'
class SomeController < ApplicationController
end
describe SomeController, type: :controller do
shared_examples_for 'auth ops' do
it "redirects when not signed in" do
expect(response).to redirect_to redirect_path
end
end
describe 'GET #show' do
it_behaves_like 'auth ops' do
let(:redirect_path) {'/some_path'}
before {get :show}
end
context "when signed in" do
# One thing...
# Another thing...
end
end
describe 'GET #new' do
it_behaves_like 'auth ops' do
let(:redirect_path) {'/some_other_path'}
before {get :whatever}
end
end
end
Have a look at rspec shared example.
Using shared_examples_for seemed like overkill given that I was only concerned with a single example. Furthermore, it_behaves_like("unauthenticated redirects to", '/some_other_path', Proc.new{ get :whatever}) seems unnecessarily verbose. The trick is to use #send() to maintain the proper scope.
def unauthenticated_redirects_to path, method_action
context "when not signed in" do
it "redirects to #{path} for #{method_action}" do
send(method_action.first[0], method_action.first[1])
expect(response).to redirect_to path
end
end
end
describe 'GET #new' do
unauthenticated_redirects_to '/path', :get => :new
end

Rspec: Test redirects in Devise::OmniauthCallbacksController subclass

Following the Railscast on Devise and OmniAuth I have implemented an OmniauthCallbacksController < Devise::OmniauthCallbacksController which contains a single method to handle an OmniAuth callback:
def all
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
alias_method :facebook, :all
routes.rb:
devise_for :users, controllers: {omniauth_callbacks: "omniauth_callbacks", :sessions => "sessions" }
I would like to customise this, so I'm trying to test it using RSpec. The question is how do I test this method and the redirects?
If in the spec I put user_omniauth_callback_path(:facebook) it doesn't complain about the route not existing, but doesn't seem to actually call the method.
According to this answer "controller tests use the four HTTP verbs (GET, POST, PUT, DELETE), regardless of whether your controller is RESTful." I tried get user_... etc. but here it does complain that the route doesn't exist. And indeed if I do rake routes it shows there is no HTTP verb for this route:
user_omniauth_callback [BLANK] /users/auth/:action/callback(.:format) omniauth_callbacks#(?-mix:facebook)
Can you see what I'm missing?
EDIT
So following this question one way of calling the method is:
controller.send(:all)
However I then run into the same error that the questioner ran into:
ActionController::RackDelegation#content_type delegated to #_response.content_type, but #_response is nil
You will need to do three things to get this accomplished.
enter OmniAuth test environment
create an OmniAuth test mock
stub out your from_omniauth method to return a user
Here is a possible solution, entered in the spec itself
(spec/feature/login_spec.rb for example) . . .
let(:current_user) { FactoryGirl.create(:user) }
before do
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new({
provider: :facebook,
uid:'12345',
info: {
name: "Joe"
}
})
User.stub(:from_omniauth).and_return(current_user)
end
I adapted this from a google authentication, so facebook may require more fields, but those are the only ones required by omniauth docs. You should be able to find the correct fields by looking at your database schema and finding fields that match the documentation.
In my case, the minimum was enough to pass the request phase and move onto the stubbed out method returning my user.
This example also uses FactoryGirl.
It may not be perfect, but I hope it helps. Good luck!
-Dan
If you hit this and you are running rspec 3.4 this example should work for you:
describe Users::OmniauthCallbacksController, type: :controller do
let(:current_user) { FactoryGirl.create(:user) }
before do
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:your_oauth_provider_here] = OmniAuth::AuthHash.new(
provider: :your_oauth_provider_here,
uid: rand(5**10),
credentials: { token: ENV['CLIENT_ID'], secret: ENV['CLIENT_SECRET'] }
)
request.env['devise.mapping'] = Devise.mappings[:user]
allow(#controller).to receive(:env) { { 'omniauth.auth' => OmniAuth.config.mock_auth[:your_oauth_provider_here] } }
allow(User).to receive(:from_omniauth) { current_user }
end
describe '#your_oauth_provider_here' do
context 'new user' do
before { get :your_oauth_provider_here }
it 'authenticate user' do
expect(warden.authenticated?(:user)).to be_truthy
end
it 'set current_user' do
expect(current_user).not_to be_nil
end
it 'redirect to root_path' do
expect(response).to redirect_to(root_path)
end
end
end
end
I am experiencing problem for writhing RSpec for OmniauthCallbacksController, do some research on this and it working for me. Here is my codes, if anyone found necessary. Tests are for happy path and it should work for news version of RSpec eg. 3.x
require 'spec_helper'
describe OmniauthCallbacksController, type: :controller do
describe "#linkedin" do
let(:current_user) { Fabricate(:user) }
before(:each) do
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:linkedin] = OmniAuth::AuthHash.new({provider: :linkedin, uid: '12345', credentials: {token: 'linkedin-token', secret: 'linkedin-secret'}})
request.env["devise.mapping"] = Devise.mappings[:user]
#controller.stub!(:env).and_return({"omniauth.auth" => OmniAuth.config.mock_auth[:linkedin]})
User.stub(:from_auth).and_return(current_user)
end
describe "#linkedin" do
context "with a new linkedin user" do
before { get :linkedin }
it "authenticate user" do
expect(warden.authenticated?(:user)).to be_truthy
end
it "set current_user" do
expect(subject.current_user).not_to be_nil
end
it "redirect to root_path" do
expect(response).to redirect_to(root_path)
end
end
end
end
end

Rspec 2.7 access controller session in spec before making request

I'm testing my controllers using Rspec and I can't seem to set the session variable of the current controller under test before making the request to the path.
For example this works:
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
get controller_path
request.session[:state] = "12334"
end
end
This doesn't work (i get an error saying session is not a method of Nil class):
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
request.session[:state] = "12334"
get controller_path
end
end
Any ideas?
With new version of RSpec this is done pretty nice, look:
describe SessionController do
# routes are mapped as:
# match 'login' => 'session#create'
# get 'logout' => 'session#destroy'
describe "#create" do
context "with valid credentials" do
let :credentials do
{ :email => 'example#gmail.com', :password => 'secret' }
end
let :user do
FactoryGirl.create(:user, credentials)
end
before :each do
post '/login', credentials
end
it "creates a user session" do
session[:user_id].should == user.id
end
end
# ...
end
describe "#destroy" do
context "when user logged in" do
before :each do
get "/logout", {}, { :user_id => 123 } # the first hash is params, second is session
end
it "destroys user session" do
session[:user_id].should be_nil
end
# ...
end
end
end
You can also use simply request.session[:user_id] = 123 inside before(:each) block, but above looks pretty nicer.
Try this:
describe "GET /controller/path" do
it "if not matching CRSF should display message" do
session[:state] = "12334"
get controller_path
end
end

Resources