Posts tagged ‘cancan’

sometimes rspec is easier and faster than cucumber

I’ve been writing a lot of cucumber scenarios for this project I’m working on. I like the syntax, and I like how easy it is to automate tests against the rails stack. I just ran into a simple case where rspec is much easier, quicker to write and quicker to run.

I’m using Authlogic and CanCan to authenticate and authorize requests to my application. I started doing the silly thing of double checking my controller authentication in cucumber.

This makes some sort of sense, from the point of view that it’s loading an entire Rails stack. Unfortunately it would be the source of a hell of a lot of repetition in my tests, as well as unnecessary slowness as the project increases in size. I realized this morning that actually, this can be written as a unit test for my controllers.

1
2
3
4
def authenticate_request(*args)
  self.send(args.shift, args.shift, args.shift)
  response.should redirect_to(login_url)
end

Now I can do this:

1
2
3
4
5
6
7
8
it "authenticates for its actions" do
    authenticate_request(:get, :index)
    authenticate_request(:get, :new)
    authenticate_request(:get, :show, :id => mock_model(MyModel))
    authenticate_request(:post, :create)
    authenticate_request(:delete, :destroy, :id => mock_model(MyModel))
    authenticate_request(:put, :update, :id => mock_model(MyModel))
end

I should actually abstract that it its own method, so I can quickly unit test other controllers without repetition.

This isn’t to say that I couldn’t refactor this better in cucumber. I think I may move most of my authentication/authorization testing into unit tests, however, based on the difference in complexity.

Edit: yeah, so this was covered in a Railscast almost a year ago. Still, its a big revelation to me that this should be in my specs rather than my features.

cancan

I’ve started adding authorization code to the artist portfolio cms I’m working on, using a combination of Authlogic for authentication and CanCan for authorization. I have to say I really like CanCan.

I think what I appreciate most is how accessible it is. Admittedly, I didn’t look too hard at any of the other myriad gems or plugins out there, but my initial impressions were confused ones. CanCan is extremely easy to get going.

Like with anything, however, there are little tricks. My biggest problems were in getting Authlogic to play nicely with Cucumber. Most of those were fixed with the following code in my setup.rb (in the feature/support directory, so it’s not overwritten by changes to env.rb)

1
2
3
4
5
6
7
8
require 'authlogic/test_case'
include Authlogic::TestCase
 
ApplicationController.send(:public, :current_user, :current_user_session)
 
Before do
  activate_authlogic
end

I was trying awfully hard to abstract the session creation and login code by hooking into the same sort of code I use in my controllers–mostly because I suspect that the tests and features are going to start adding up. If I can cut out the cost of calling the stack to login through a webrat request, I think it’s probably worth it in the long run, especially since that way I would only need one feature to test the login form. Then, I would have the choice in my scenario of logging in through a web request, or just setting up a login and then going straight to the real test. Alas, I was denied this simple pleasure.

Most of my trouble with testing CanCan is in figuring out when the issue is with CanCan and when it’s somewhere else. So far, the issues have all be elsewhere. There were a few stumbling blocks that have taken time to work through, however.

I’m only allowing non-admin users to edit their own user record. In my UsersController, I have this:

1
    unauthorized! if cannot? :manage, @user

As per the CanCan documentation, I have a rescue_from in my ApplicationController so that I can do something with the exception rather than throw a giant error in front of the user.

1
2
3
4
5
6
  @allow-rescue
  Scenario: edit user fails when other random user
    When I am logged in as 'user' with password 'testpass'
    And I go to the edit user page for 'user2'
    Then I should be on the home page
    And I should see "Access denied"

It took me a couple hours, I admit, before I bothered to check the Cucumber documentation and notice the @allow-rescue tag. You can set this as a system-wide variable in env.rb–that I knew. Being able to tag individual scenarios this way allows you to specify exactly which tests allow Rails to rescue_from exceptions. Without this, you see the exception, rather than the rescue.

My next issue was with my login controller. If my user has the right access, then upon login they should be redirected to an admin controller, rather than going back to the home page. Pretty standard:

1
2
3
4
5
6
7
8
9
10
11
class UserSessionsController < ApplicationController
  def create
    @user_session = UserSession.new(params[:user_session])
    if @user_session.save
      flash[:notice] = "Logged in"
      redirect_back_or_default can?(:update, Page) ? admin_path : root_url
    else
      render :action => :new
    end
  end
end

I couldn’t understand why this was not redirecting to the right place. I was logging in with an admin user, but kept being redirected to the root.

I’m still not sure why this is happening, but at a certain point I realized that it’s an issue with my Authlogic helpers. CanCan assumes a method called current_user available to the controller. This should be lazy loading the user based on the current session, which should be lazy loaded using UserSession.find. All pretty standard. For some reason, however, current_user is coming up nil right when the session is logged in.

My solution right now is this:

1
2
3
4
5
  private
 
  def current_ability
    Ability.new(current_user || @user_session.try(:record))
  end

This will probably be refactored when I know more of what’s going on, but it works for now.