Rails4 + Authlogic + rspec - ruby-on-rails

Using Rails 4, I am having issues getting Authlogic to see my faked UserSession.
I have set up pages#whoami to render the current user's email address, as a simplistic test.
class PagesController < ApplicationController
# before_filter :require_user
def whoami
render :text => current_user.try(:email) || 'anonymous'
end
end
in spec/spec_helper.rb:
require "authlogic/test_case"
include Authlogic::TestCase
and my rspec test:
require 'spec_helper'
describe '/whoami' do
setup :activate_authlogic
it "should tell me who I am" do
user = FactoryGirl.create(:user)
user.should be_valid
session = UserSession.create(user)
session.should be_valid
get '/whoami'
response.body.should == user.email
end
end
I updated my application controller to show the current session:
def require_user
unless current_user
raise "Current User Session is: #{ current_user_session.inspect}"
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to new_user_session_url
return false
end
end
With before_filter :require_user commented, I correctly get "anonymous". When I uncomment it, I see that my user session is nil. I tried looking through the authlogic code but got lost in Authlogic::Session:Persistence::InstanceMethods#persisting?
I'm trying to debug. Here's where I am so far.
Here, we try to set Authlogic::Session::Base.controller to the test's mock controller:
https://github.com/binarylogic/authlogic/blob/master/lib/authlogic/test_case.rb#L109
in my spec, I see that #controller is a Authlogic::TestCase::MockController
and in my spec, I see that Authlogic::Session::Base.controller is set to that Mock Controller.
However, I then check this:
class ApplicationController < ActionController::Base
...
def current_user_session
raise Authlogic::Session::Base.controller.inspect
...
end
end
and I see Authlogic::ControllerAdapters::RailsAdapter ... so somehow the controller is being set but isn't persisting. I'm wondering whether this has to do with the switch from Rails3 to Rails4?
Any insight into this would be appreciated.
Gem versions for those who are interested:
gem rspec-core (2.14.5)
gem authlogic (3.3.0)
gem rails (4.0.0)

Per https://stackoverflow.com/a/5803121, a request spec is just a thin wrapper around ActionDispatch::IntegrationTest. As such, there is no direct access to the session, unlike a normal controller spec.
Due to this, it isn't directly possible to log a user in directly with AuthLogic, which does rely on the session and cookies:
It first authenticates, then it sets up the proper session values and cookies to persist the session.
For request/integration/api/feature specs, a request directly to the login path will be necessary to set the proper session / cookies behind the scenes. The integration session will then be sent back (just like a normal web request) with the proper values.
To make life easier you can add a helper method, which you can include for request/integration/api/feature specs:
# spec/support/auth_logic_helpers.rb
module Authlogic
module TestHelper
# You can call this anything you want, I chose this name as it was similar
# to how AuthLogic calls it's objects and methods
def create_user_session(user)
# Assuming you have this defined in your routes, otherwise just use:
# '/your_login_path'
post user_session_path, login: user.login, password: user.password
end
end
end
# Make this available to just the request and feature specs
RSpec.configure do |config|
config.include Authlogic::TestHelper, type: :request
config.include Authlogic::TestHelper, type: :feature
end

Related

Stubbing Grape helper

I have Rails app with Grape API.
The interface is done with Backbone and Grape API provides it all data.
All it returns is user-specific stuff, so i need reference to currently logged in user.
Simplified version looks like this:
API initialization:
module MyAPI
class API < Grape::API
format :json
helpers MyAPI::APIHelpers
mount MyAPI::Endpoints::Notes
end
end
Endpoint:
module MyAPI
module Endpoints
class Notes < Grape::API
before do
authenticate!
end
# (...) Api methods
end
end
end
API helper:
module MyAPI::APIHelpers
# #return [User]
def current_user
env['warden'].user
end
def authenticate!
unless current_user
error!('401 Unauthorized', 401)
end
end
end
So, as you can see, i get the current user from Warden and it works fine. But the problem is with testing.
describe MyAPI::Endpoints::Notes do
describe 'GET /notes' do
it 'it renders all notes when no keyword is given' do
Note.expects(:all).returns(#notes)
get '/notes'
it_presents(#notes)
end
end
end
How can I stub helpers's method *current_user* with some specific user?
I tried:
setting env/request, but it doesn't exist before calling get method.
stubbing MyAPI::APIHelpers#current_user method with Mocha
stubbing MyAPI::Endpoints::Notes.any_instance.stub with Mocha
Edit:
At the moment, it's stubbed this way:
spec:
# (...)
before :all do
load 'patches/api_helpers'
#user = STUBBED_USER
end
# (...)
spec/patches/api_helpers.rb:
STUBBED_USER = FactoryGirl.create(:user)
module MyAPI::APIHelpers
def current_user
STUBBED_USER
end
end
But it's definitely not the answer :).
comments mentioned in this issue should help you, It's how even Grape tests it's helpers,
https://github.com/intridea/grape/blob/master/spec/grape/endpoint_spec.rb#L475
(If the code is not there on the same line due to changes, just do a ctrl+f & look for helpers)
Here's some code from the same file
it 'resets all instance variables (except block) between calls' do
subject.helpers do
def memoized
#memoized ||= params[:howdy]
end
end
subject.get('/hello') do
memoized
end
get '/hello?howdy=hey'
last_response.body.should == 'hey'
get '/hello?howdy=yo'
last_response.body.should == 'yo'
end
Option 1
The recommended way is to use Grape::Endpoint.before_each:
context 'when user is logged in' do
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:current_user).and_return(user)
end
end
after { Grape::Endpoint.before_each nil }
end
But this is quite verbose. It can live in a shared context, but you can't pass user as a parameter explicitly so you'd end up with:
let(:user) { create(:user) }
# ...
include_context 'signed in user'
Option 2
My preferred way is a more RSpec-like stubbing:
# helper
module AuthHelper
def current_user
# ...
end
end
# api
module API
module V1
class Auth < Grape::API
helpers AuthHelper
end
end
end
# spec
before do
allow_any_instance_of(AuthHelper).to receive(:current_user).and_return(user)
end
Option 3
You can also define helpers:
API::V1::User.helpers do
def current_user
user
end
end

Testing devise views with rspec

I have generated Devise's views running rails g devise:views and would now like to test them.
This is what I have come up with:
require 'spec_helper'
describe "devise/sessions/new" do
before do
render
end
it "renders the form to log in" do
rendered.should have_selector("form", action: user_session_path, method: :post) do |form|
end
end
end
For the render statement it gives me undefined local variable or method 'resource'. After googling around I found that I should add
#user.should_receive(:resource).and_return(User.new)
before the render statement - but it still gives me the same error, and I am not really sure how to use it.
What am I doing wrong? Thanks for your help.
Just another thought incase anyone runs into the same issue. I wasn't a huge fan of adding code to my helpers just to make it so tests can pass so I ended up adding this code in a before block in my tests:
before do
view.stub(:resource).and_return(User.new)
view.stub(:resource_name).and_return(:user)
view.stub(:devise_mapping).and_return(Devise.mappings[:user])
end
And whaddya know? I found this answer where someone had a similar problem. The solution is to include the following code in your application helper:
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
This is necessary because devise is using certain helper methods in its controllers. If I access the views from my specs, however, these helper methods are not available, hence my tests fail. Putting these methods inside the application helper makes them accessible throughout the application, including my specs, and indeed the tests pass!
In rspec there's a configuration which doesn't allow you to stub out methods which are not defined. With rails 4 it's even standard behavior:
# spec/spec_helper.rb
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
Because of that the highest-voted answer doesn't work with devise helpers. Change verify_partial_doubles to false or use an actual helper.
I wanted to do #MikeFogg's solution - I do something similar in another view spec to deal with Pundit - but of course I ran up against the issue #ChrisEdwards pointed out with regards to needing mocks.verify_partial_doubles = false.
However, I was not much interested in turning that off across my whole test suite, it's a good check, "generally recommended" and default in Rspec 4+.
I actually first posted a solution here where I reconfigured RSpec in before/after blocks, but I ran across a really, really easy way to do this, and it works great:
before(:each) do
without_partial_double_verification do
allow(view).to receive(:resource).and_return(Student.new)
allow(view).to receive(:resource_name).and_return(:student)
allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:student])
end
# other before_each configuration here as needed
end
This has been around since 2016 and was originally called without_verifying_partial_doubles, but was renamed to the current without_partial_double_verification. Does not appear to be documented anywhere I can see.
Like Mike Fogg, I didn't like the idea of adding several methods to my ApplicationController just to get these view specs working.
I noticed that my rspec installation had a spec/mixins directory, so I did the following:
# spec/mixins/devise_helpers.rb
module DeviseHelpers
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
Then include in my spec:
# spec/views/devise/registrations/new.html.haml_spec.rb
require 'rails_helper'
include DeviseHelpers
describe 'devise/registrations/new.html.haml' do
it 'has a login link for existing users' do
render
expect(rendered).to have_link('Log in')
end
end
Now I can include mix those methods into any spec that needs them.
Combining #sixty4bit answer with this answer here I came up with this solution:
class DeviseHelpers < Module
def initialize(resource_name, resource_class)
#resource_name = resource_name
#resource_class = resource_class
end
def extended(base)
_resource_name = #resource_name
_resource_class = #resource_class
base.class_eval do
define_method :resource_name do
_resource_name
end
define_method :resource do
#resource ||= _resource_class.new
end
define_method :devise_mapping do
#devise_mapping ||= Devise.mappings[_resource_name]
end
end
end
end
This allows you to use it with different resources by requiring it at the top of your spec:
require 'support/devise_helpers'
and then calling it like this:
before do
view.extend(DeviseHelpers.new(:customer, Customer))
end
For RSpec 4, Rails 6 and Devise 4.7 I was unsuccessful with the other recommendations here or was wary of ramifications to my other testing or production code.
allow(view).to receive(:current_user).and_return(user)
in my view specs was what ultimately worked for me without any other configuration changes. (One has to define user first.)
It is not unlikely that there have been meaningful changes to the underlying gems/frameworks in recent years that invalidates some of the other solutions here. It is even more likely that I have misconfigured Devise, RSpec and/or related helpers.
I confirmed that current_user was populated in rspec/rails/example/view_example_group.rb, but was returning nil during render. I found this comment in view_example_group.rb and followed it:
# The instance of `ActionView::Base` that is used to render the template.
# Use this to stub methods _before_ calling `render`.
#
# describe "widgets/new.html.erb" do
# it "shows all the widgets" do
# view.stub(:foo) { "foo" }
# render
# # ...
# end
# end
def view
_view
end
https://github.com/rspec/rspec-rails/blob/main/lib/rspec/rails/example/view_example_group.rb
stub syntax is deprecated but view.stub(:current_user) { user } will also work (for now).

How to rescue OmniAuth::Strategies::OAuth2::CallbackError?

I am building a Rails application with Omniauth for log in service.To authenticate Google I am using OmniAuth Google OAuth2 Strategy.
When user clicks 'allow access' button everything works fine.But when user clicks 'no thanks' button the below error is raised.
OmniAuth::Strategies::OAuth2::CallbackError
I have tried adding the below rescue code in application controller.
class ApplicationController < ActionController::Base
rescue_from OmniAuth::Strategies::OAuth2::CallbackError, :with =>
:omniauth_callback_error_handler
protected
def omniauth_callback_error_handler
redirect_to init_sign_in_users_path
end
end
But no luck. Any idea?
You can set the on_failure proc in the omniauth initializer in an even cleaner fashion:
OmniAuth.config.on_failure = UsersController.action(:oauth_failure)
This happens because the authentication happens in a middleware so your controller is not involved in it. This is where the exception is raised and the called code is this
I think you can handle this kind of error by defining a callback in OmniAuth initializer with this kind of code
OmniAuth.config do |config|
config.on_failure do
# your handling code invoked in the context of a rack app
end
end
Otherwise there is a commit of three months ago which introduce this behavior
def redirect_to_failure
message_key = env['omniauth.error.type']
new_path = "#{env['SCRIPT_NAME']}#{OmniAuth.config.path_prefix}/failure?message=#{message_key}"
Rack::Response.new(["302 Moved"], 302, 'Location' => new_path).finish
end
which states that on errors your user is redirected to /auth/failure with an error message, so you should be able to define a route for that path and handle it in your app. Keep in mind that this won't happen in development mode so you need to try it in other envs. If this doesn't happen in production try to upgrade your omniauth gem to version 1.1.0
I have solved this problem with the Fabio's first suggestion.
OmniAuth.config.on_failure = Proc.new do |env|
UsersController.action(:omniauth_failure).call(env)
#this will invoke the omniauth_failure action in UsersController.
end
In my UsersController
class UsersController < ActionController::Base
def omniauth_failure
redirect_to init_sign_in_users_path
#redirect wherever you want.
end
end
There's a configuration to use /auth/failure instead of raising an error.
I use OmniAuth 1.2.2 and when I checking the FailureEndpoint I found the code is like this:
def call
raise_out! if OmniAuth.config.failure_raise_out_environments.include?(ENV['RACK_ENV'].to_s)
redirect_to_failure
end
And the failure_raise_out_environments is defined here:
def self.defaults
#defaults ||= {
# other configurations
:failure_raise_out_environments => ['development']
}
end
The environment can be configured so the solution is easy. I use Rails so I put below code in an initializer file:
OmniAuth.configure do |config|
# Always use /auth/failure in any environment
config.failure_raise_out_environments = []
end

Can't get an authorized user in integration test with Rails 3.1/Authlogic/ActiveRecord session store

I am trying to write a simple integration test on a Rails 3.1 application that uses Authlogic and ActiveRecord SessionStore, but I'm hitting the wall.
First, I tried a classic approach: after ensuring that require "authlogic/test_case" line is already in our test_helper.rb I then wrote a setup method that calls activate_authlogic and then uses UserSession(#user) to create a session.
This did create a session (as proved by UserSession.find), but when doing a get request on protected resource, the response would be a redirect to the login form and session was killed (ie. UserSession.find would return nil)
I tried POST-ing the email/password as well, but that seems to work only if I change the session store back to cookie_store (something I found out from this comment).
Switching session store to CookieStore just for test would be an option, but there are some tests that already depend on ActiveRecord store.
Is there any way to switch the session store just for one test? Is there any other solution to this problem that I'm missing?
require 'test_helper'
class ProtectedTest < ActionController::IntegrationTest
def setup
activate_authlogic
#user = Factory(:user, :roles => 'user')
UserSession.create(#user)
end
def test_protected
https!
get '/protected'
assert_response :success
end
end
Dear people from the future:
I figured a workaround to this by starting tests that needed authorized users in a different environment by setting ENV["RAILS_ENV"] before including 'test_helper'. Then I changed session_store in that environment to cookie_store.
ENV["RAILS_ENV"] = "test_cs"
require 'test_helper'
end
class ProtectedTest < ActionController::IntegrationTest
# ...
end

CanCan, how to test an rspec with a modeless, controller?

I have an admin controller w/o a model
class AdminController < ApplicationController
# Sign In Required - Devise Check
before_filter :authenticate_user!
# CanCan
before_filter :current_ability
# CanCan for Non RESTful Controllers - https://github.com/ryanb/cancan/wiki/Non-RESTful-Controllers
# We do this as there is no admin model/resouce
authorize_resource :class => false
I'm unable to figure out how to write a spec for the controller. I have the following:
describe 'Admin User' do
it "should be allow admin users to view admin#index" do
#ability_for_user_4.can :read, Admin
get :index
assert_template :index
end
end
This fails with: uninitialized constant Admin
Any ideas? Thanks
Does your spec start with require 'spec_helper' ? Most Ruby on Rails specs start with that and it will load the Rails environment into memory.
Otherwise, you need to create a mocked model so Rspec knows what an Admin is.

Resources