CanCan, how to test an rspec with a modeless, controller? - ruby-on-rails

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.

Related

Rails: how to write an anonymous spec controller to test authorisation

I have a controller, AdminController, which sets the various authorisation levels for the rest of the CMS. Because there are no controller actions, just methods, I began to research ways to test these against controllers.
The conclusion I came to was that they needed to be tested independently of the controllers they are used in (I want to to steer clear of integration testing if possible, like capybara etc).
I found some articles like this one to help me along.
So far I have written this spec which is failing with the errors below. I am not sure about it to be honest and wanted to here what SO community had to say on what I am trying to achieve.
describe AdminController do
controller do
before_filter :authorize_fixture_uploader!
def index
render text: 'Hello World'
end
end
let(:admin){FactoryGirl.create(:admin)}
describe "authentication" do
before do
sign_in admin
allow(controller).to receive(:current_admin).and_return(admin)
end
describe "authorize_fixture_uploader! helper" do
context "signed in" do
before do
allow(:admin).to receive(:authorize_fixture_uploader!).and_return(false)
get :index
end
it "redirects do admin_home_path" do
expect(response).to redirect_to admin_home_path
end
end
end
end
end
and here is the controller
class AdminController < ApplicationController
before_filter :authenticate_admin!
def authorize_fixture_uploader!
unless current_admin.fixture_uploader?
return redirect_to(admin_home_path)
end
end
end
This test is giving me the error
1) AdminController authentication authorize_fixture_uploader! helper signed in redirects do admin_home_path
Failure/Error: allow(:admin).to receive(:authorize_fixture_uploader?).and_return(false)
TypeError:
can't define singleton
I am worried its because my whole approach to this is wrong. Help would most certainly be appreciated.
Updated thanks to #blelump's answer.
I had a type which was causing the first issue. But Now I am getting error
undefined method `authorize_fixture_uploader?' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1::Nested_1::Nested_1:0x007f9357857108>
The logic behind this i throwing me a bit. How am I to test these methods independent of the controllers they are used?
You have a typo:
allow(:admin).to receive(:authorize_fixture_uploader!).and_return(false)
Now you're trying to add authorize_fixture_uploader! to Symbol. Just start with controller variable:
allow(controller).to receive(:authorize_fixture_uploader!).and_return(false)
Aside from the poor attention detail highlight by blelump above, the real flaw in my approach was the lack of routes. I found a very useful article from pivotal labs http://pivotallabs.com/adding-routes-for-tests-specs-with-rails-3/ which saved the day.
Read the article, but it essentially boils down to this.
require 'spec_helper'
class InheritsFromAdminController < AdminController
def show
render :text => "foo"
end
end
describe InheritsFromAdminController do
before do
Rails.application.routes.draw do
# add the route that you need in order to test
match '/foo' => "inherits_from_admin#show"
# re-drawing routes means that you lose any routes you defined in routes.rb
# so you have to add those back here if your controller references them
match '/login' => "sessions/new", :as => login
end
end
after do
# be sure to reload routes after the tests run, otherwise all your
# other controller specs will fail
Rails.application.reload_routes!
end
it "requires logged-in users" do
get :show
response.should redirect_to("/login")
end
end

How should I mock my session?

I've set up a controller I'm testing so that most requests to its actions redirect to the sign_in page if a session doesn't exist.
Because of this, I need to use the sign_in method when testing controllers in a unit test. I also need to create the user in the database so they can sign in.
This is all easily achieved like this:
describe MyController, do
let(:user) { FactoryGirl.create(:user)} # The 'create' creates in the database as well as in memory
context "with session" do
before {
sign_in user
}
context ".index" do
assigns(:example).should == "Just an example"
end
end
end
However, this isn't a good unit test because it's depending on active record and the database, as well as Devise's test helper methods.
So how can I use a mock (or something) to stop my controller redirecting me when I'm trying to test it?
My controller:
class MyController < ApplicationController
before_filter :authenticate_user!, only: [:index]
def index
#example = "Just an example"
end
end
Stub user authentication as advised here in the Devise Wiki.

Rails4 + Authlogic + rspec

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

How do I test this with rspec?

I'm using the Sorcery gem for user signup/login.
One feature of this gem is the require_login before_filter on any controller you want to authenticate.
I have created a dashboard namespace for my app after they've logged in. For example /dashboard/reports or /dashboard/employees, etc.
Routes file:
# Dashboard
namespace :dashboard do
# Recent Activity
get '' => redirect('/dashboard/recent-activity')
get 'recent-activity' => 'activities#index', :as => 'root'
# Other dashboard controllers and actions
end
I extracted out the before_filter into it's own controller called:
"app/controllers/dashboard/base_controller.rb"
class Dashboard::BaseController < ApplicationController
before_filter :require_login
end
What I'd like to do is make 100% sure in some kind of test that ANY new controller I create within the dashboard folder (or dashboard namespace), inherits from Dashboard::BaseController
Such as my activities controller for example:
class Dashboard::ActivitiesController < Dashboard::BaseController
I dont want to go creating controllers in a few months and accidentally make it inherit from ApplicationController which would still would but wouldnt have login functionality.
I'm using RSpec
Can't quite believe my own eyes that I solved this on my own....
require 'spec_helper'
describe Dashboard::BaseController do
it "is the superclass of every dashboard namespaced controller" do
Rails.application.eager_load!
ApplicationController.descendants.each do |controller|
if controller.to_s.include?("Dashboard::") && controller.to_s != "Dashboard::BaseController"
expect(controller.superclass.to_s).to eq("Dashboard::BaseController")
end
end
end
end

Rails admin with Sorcery

I'm trying to install the Rails Admin Gem using Sorcery for authentication instead of Devise.
Rails admin does provide a hook that you can use to attach your own authentication method. Here is the example they provide in their docs (using warden):
config.authenticate_with do
warden.authenticate! :scope => :admin
end
config.current_user_method { current_admin }
I'm guessing that inside the block I need to reference the before_filter that Sorcery uses to authenticate users, which would be require_login.
However, when I try that and I try to visit /admin when logged out, I get a routing error:
No route matches {:action=>"new", :controller=>"sessions"}
This probably happens because I am being redirected within the engine rather than in the main app.
How can I set this up correctly?
# config/initializers/rails_admin.rb
RailsAdmin.config do |config|
config.authenticate_with do
# Use sorcery's before filter to auth users
require_login
end
end
# app/controllers/application_controller.rb
class ApplicationController
# Overwrite the method sorcery calls when it
# detects a non-authenticated request.
def not_authenticated
# Make sure that we reference the route from the main app.
redirect_to main_app.login_path
end
end
#config/initializers/rails_admin.rb
RailsAdmin.config do |config|
...
config.parent_controller = 'ApplicationController'
end
If you use Sorcery with Cancancan gem, you should also add config.current_user_method(&:current_user) in your config/initializers/rails_admin.rb file, or you'll get the error: You are not authorized.

Resources