I have configured Warden in my Rails/Grape API app in config/initializers/warden.rb:
Rails.application.config.middleware.use Warden::Manager do |manager|
manager.default_strategies :password
end
(I'd share the password strategy code, but it really doesn't apply to this question. I have it stored at config/initializers/warden/strategies/password.rb.)
When I run an RSpec rquest spec with invalid login credentials:
describe 'session_tokens', type: :request do
let!(:user) { FactoryGirl.create(:user) }
let(:ip_address) { Faker::Internet.ip_v4_address }
describe 'POST /' do
context 'with invalid password' do
before do
post '/api/v1/session_tokens', email: user.email, password: "#{user.password}.", ip: ip_address
end
it 'is unsuccessful' do
expect(response.code).to eql '401'
end
it 'has an "X-Error-Detail" header' do
expect(response.header['X-Error-Detail']).to eql 'Invalid email or password.'
end
end
end
end
It gives me this error:
Failure/Error: post '/api/v1/session_tokens', email: user.email, password: "#{user.password}.", ip: ip_address
RuntimeError:
No Failure App provided
For the life of me, I cannot get Warden to work correctly with Grape after reviewing examples online (for example, dblock/grape_warden). Most examples are simple and set the Grape app itself as the failure app.
When I do pass a module within my Grape app as the failure_app:
Rails.application.config.middleware.use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = -> (env) { API::V1::SessionTokens }
end
I get this error, even though I have a post :unauthenticated block in that module:
Failure/Error: post '/api/v1/session_tokens', email: user.email, password: "#{user.password}.", ip: ip_address
NoMethodError:
undefined method `unauthenticated' for API::V1::SessionTokens:Class
The same happens when I move the unauthenticated definition to the root of my Grape app.
The solution was to configure Warden within the Grape app itself instead of within a Rails initializer.
I removed config/initializers/warden.rb and put its contents within my Grape app as such:
module API
module V1
class Base < Grape::API
mount API::V1::SessionTokens
use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = API::V1::SessionTokens
end
end
end
end
Works perfectly now!
Kudos to Jean Bahnik for this file on GitHub in particular. I found this beautiful piece of code after almost giving up.
Related
I'm looking for the way to do this but in request specs. I need to log in and log out a double or instance_double to Devise instead of an actual ActiveModel/ActiveRecord.
By using the code in the wiki page:
module RequestSpecHelpers
def sign_in(user = double('user'))
if user.nil?
allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
allow(controller).to receive(:current_user).and_return(nil)
else
allow(request.env['warden']).to receive(:authenticate!).and_return(user)
allow(controller).to receive(:current_user).and_return(user)
end
end
end
I get this error: undefined method 'env' for nil:NilClass
I saw this question and this wiki, but if I want to use doubles of the user those two don't work. I was using the last one, works fine with a real user but with a double it doesn't log it in.
The tests:
RSpec.describe 'new shipment', type: :request do
describe 'authenticated as user' do
before do
#user = double(:user, id: 1, email: 'user#gmail.com', password: 'password',
id_card: '4163649-1', first_name: 'Jane', last_name: 'Doe')
sign_in #user
end
end
end
If I include:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :requests
end
I get this error:
Failure/Error: #request.env['action_controller.instance'] = #controller
NoMethodError:
undefined method `env' for nil:NilClass
# /root/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:40:in `setup_controller_for_warden'
Problem with Frederick Cheung answer
If I do that the login_asmethod doesn't fail but it doesn't really log the user in. So when I try to access a path that has a before_action :authenticate_user! callback it fails.
Here is my code based on his answer:
require 'rails_helper'
RSpec.describe 'new shipment', type: :request do
describe 'authenticated as user' do
include Warden::Test::Helpers
before(:each) do
Warden.test_mode!
#stub more methods as needed by the pages you are testing
user = instance_double(User, to_key: 1, authenticatable_salt: 'example')
login_as(user, scope: 'user')
end
it 'returns 200 Ok' do
get new_shipment_path
expect(response).to have_http_status(:ok)
end
end
end
And this is the response when running rspec:
1) new shipment authenticated as user returns 200 Ok
Failure/Error: expect(response).to have_http_status(:ok)
expected the response to have status code :ok (200) but it was :found (302)
# ./spec/requests/shipments_requests_spec.rb:41:in `block (3 levels) in <top (required)>'
As you can see instead of allowing me to access the path it redirects me, this is the usual behavior when the user is not allowed to access the path.
It I change the instance_double for a real User saved in the database this approach works correctly:
# only changed this line in the before hook
user = User.create(email: 'user#gmail.com', password: 'password',id_card: '4163649-1', first_name: 'Jane', last_name: 'Doe')
Result:
Finished in 3.23 seconds (files took 33.47 seconds to load)
1 example, 0 failures
It sounds like you're using Devise 3.x ( since Devise::TestHelpers was renamed in devise 4), Devise::TestHelpers is only designed to work with controller specs.
If you can upgrade to devise 4, it has separate helpers for request specs and controller tests. This is just a very thin wrapper around what warden provides, which hides all the messing around with env.
There are some extra complications when using a double - you need to stub out various methods devise calls that you might not realise.
The following worked for me
describe 'example' do
include Warden::Test::Helpers
before(:each) do
Warden.test_mode!
#stub more methods as needed by the pages you are testing
user = instance_double(User, to_key: 1, authenticatable_salt: 'example')
login_as(user, scope: 'user')
end
end
I recently installed Devise in my app to handle auth, replacing the auth system from Michael Hartl's tutorial. It's working fine in the app itself, but I can't get my tests to auth properly, so they're pretty much all failing, which makes me sad. I'm using Rails 4 with Minitest.
Here's an example of one of my controller tests that fails:
learning_resources_controller_test.rb
require 'test_helper'
class LearningResourcesControllerTest < ActionController::TestCase
def setup
#user = users(:testuser1)
end
test "user can submit new resource" do
sign_in #user # Devise helper
post :create, {:learning_resource => {:name => "My resource"}}
resource = assigns(:learning_resource)
assert_redirected_to topic_path(#topic1, :learning_resource_created => "true")
end
end
test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
fixtures :all
# Return true is test user is signed in
def is_signed_in?
!session[:user_id].nil?
end
def sign_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
# Sign in by posting to the sessions path
post signin_path, session: { email: user.email,
password: password,
remember_me: remember_me }
else
# Sign in using the session
session[:user_id] = user.id
end
end
private
def integration_test?
defined?(post_via_redirect)
end
end
class ActionController::TestCase
include Devise::TestHelpers
end
fixtures/users.yml
testuser1:
name: Test User 1
email: testuser1#mydumbdomain.com
password_digest: <%= User.digest('password') %>
The assert_redirected_to in the test always fails as the redirect is the sign in page instead of the topic page. All of my other tests fail in similar ways, indicating the user isn't signed in. I have scoured the Devise wiki and docs, but most of them cover testing with Rspec, not Minitest.
I tried using byebug within the test after the sign_in to check out the session, and I get this:
(byebug) session.inspect
{"warden.user.user.key"=>[[336453508], ""]}
If I try to call the :create, I get this error:
DEPRECATION WARNING: ActionDispatch::Response#to_ary no longer
performs implicit conversion to an array. Please use response.to_a
instead, or a splat like status, headers, body = *response. (called
from puts at
/Users/me/.rbenv/versions/2.2.2/lib/ruby/2.2.0/forwardable.rb:183)
302 {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1;
mode=block", "X-Content-Type-Options"=>"nosniff",
"Location"=>"http://test.host/signup",
"Set-Cookie"=>"request_method=POST; path=/",
"Content-Type"=>"text/html; charset=utf-8"}
Any ideas what I'm missing here?
The error is with hash
post :create, {:learning_resource => {:name => "My resource"}}
Try
post :create, :learning_resource => {:name => "My resource"}
I've spent far too long messing with this before asking for help. I can't seem to get RSpec and Sorcery to play together nicely. I've read through the docs on Integration testing with Sorcery and can post the login action properly, but my tests still doesn't think the user is logged in.
# spec/controllers/user_controller_spec
describe 'user access' do
let (:user) { create(:user) }
before :each do
login_user(user[:email], user[:password])
end
it "should log in the user" do
controller.should be_logged_in
end
end
And my login_user method
# spec/support/sorcery_login
module Sorcery
module TestHelpers
module Rails
def login_user email, password
page.driver.post(sessions_path, { email: email , password: password, remember_me: false })
end
end
end
end
The sessions controller handles the pages properly when I use them on the generated pages just fine. I tried outputting the results of the login_user method and it appears to properly post the data. How do I persist this logged in user through the tests? Does a before :each block not work for this? I'm just not sure where it could be running wrong and I'm pretty new to testing/RSpec so I may be missing something obvious. I'd appreciate any help.
Here's the output of the failed tests:
1) UsersController user access should log in the user
Failure/Error: controller.should be_logged_in
expected logged_in? to return true, got false
I just went through this yesterday. Here's what I did, if it helps.
Sorcery provides a test helper login_user that relies on a #controller object being available. This works great in controller specs, but doesn't work in integration tests. So the workaround in integration tests is to write another method (like the one you have above) to simulate actually logging in via an HTTP request (essentially simulating submitting a form).
So my first thought is that you should try renaming your method to login_user_post or something else that doesn't collide with the built-in test helper.
Another potential gotcha is that it looks to me like the Sorcery helper assumes that your user's password is 'secret'.
Here's a link to the built-in helper so you can see what I'm talking about:
https://github.com/NoamB/sorcery/blob/master/lib/sorcery/test_helpers/rails.rb
Good luck - I really like this gem except for this part. It is really only fully explained by patching together SO posts. Here's the code I use:
Integration Helper
module Sorcery
module TestHelpers
module Rails
def login_user_post(user, password)
page.driver.post(sessions_url, { username: user, password: password})
end
def logout_user_get
page.driver.get(logout_url)
end
end
end
end
Integration Spec (where user needs to be logged in to do stuff)
before(:each) do
#user = create(:user)
login_user_post(#user.username, 'secret')
end
Controller Spec (where the regular login_user helper works fine)
before(:each) do
#user = create(:user)
login_user
end
Note that login_user doesn't need any arguments if you have an #user object with the password 'secret'.
Did you try adding to spec/spec_helpers.
RSpec.configure do |config|
# ...
config.include Sorcery::TestHelpers::Rails::Controller
end
Nota that you need to include Sorcery::TestHelpers::Rails::Controller, not just Sorcery::TestHelpers::Rails.
Then you will be able to login_user from any controller specs like:
describe CategoriesController do
before do
#user = FactoryGirl::create(:user)
end
describe "GET 'index'" do
it "returns http success" do
login_user
get 'index'
expect(response).to be_success
end
end
end
The way you pass a password is probably wrong. It may be encrypted at this point. In provided example I will try to do this at first:
describe 'user access' do
let (:user) { create(:user, password: 'secret') }
before :each do
login_user(user[:email], 'secret')
end
it "should log in the user" do
controller.should be_logged_in
end
end
This seems to be very poorly documented. The above solutions did not work for me. Here's how I got it to work:
Check your sessions_url. Make sure it is correct. Also, check what params are necessary to log in. It may be email, username, etc.
module Sorcery
module TestHelpers
module Rails
def login_user_post(email, password)
page.driver.post(sessions_url, { email:email, password: password })
end
end
end
end
RSpec config:
config.include Sorcery::TestHelpers::Rails
Spec helper:
def app
Capybara.app
end
spec/controllers/protected_resource_spec.rb:
describe UsersController do
before do
# Create user
# Login
response = login_user_post( user.email, :admin_password )
expect( response.headers[ 'location' ]).to eq 'http://test.host/'
# I test for login success here. Failure redirects to /sign_in.
#cookie = response.headers[ 'Set-Cookie' ]
end
specify 'Gets protected resource' do
get protected_resource, {}, { cookie:#cookie }
expect( last_response.status ).to eq 200
end
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
I'm using rack/test and rspec on Rails 3 to authenticate a users api key through devise. Any request I make returns a status of 302 and a response of: "You are being redirected.". Can't seem to figure out how to authenticate.
require 'spec_helper'
describe Api::V1::CollectController do
def app
Rails.application
end
context 'user' do
subject do
FactoryGirl.create :user
end
it 'allow valid api credentials' do
post '/api/collect', {}, { 'Authentication' => subject.authentication_token }
p last_response.body
end
end
end
I've found a working solution. But I'm not satisfied with why the original approach didn't work. I eliminated rack/test and changed the way I interface with the controller. The following worked. I'm mostly just curious now...
require 'spec_helper'
describe Api::V1::CollectController do
context 'user' do
subject do
FactoryGirl.create :user
end
it 'allow valid api credentials' do
post :create, {}, { 'Authentication' => subject.authentication_token }
p response.body
end
end
end