Posts tagged ‘authlogic’

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.

cucumber, authlogic and factory_girl

I’m diving back into a Rails project that I started last summer, before I got sidetracked with… you know, having a job and crap. I’m surprised by how many cucumber features I’d written. I’m even more surprised by how little implementation is there. Why did I write so many features without making any of them work?? I have to assume that I must have lost something in transferring between computers, or I reverted a bunch of code without committing it… yeah… that’s it.

Anyways, I figured I’d use authlogic for my authentication, factory_girl for my factories and finally give formtastic a try. Here are really helpful links:

http://www.claytonlz.com/index.php/2009/11/cucumber-table-transformations-with-factory-girl-sequences/
http://sentia.com.au/2009/06/adding-your-own-login-method-to-authlogic/

A handy thing about table transformations is that you can build your objects without really having to care about the underlying table… the point of Cucumber, I guess. With authlogic, though, it allows you to do this:

1
2
3
4
5
Transform /^table:Login,Email,Password$/ do |table|
  table.hashes.map do |hash|
    {:username => hash[:Login], :email => hash[:Email], :password => hash[:Password], :password_confirmation => hash[:Password]}
  end
end

So, I just have to pass the password in once, but behind the scenes it gets transformed into both :password and :password_confirmation. Then using formtastic, my login view becomes this:

1
2
3
4
5
6
7
<%- semantic_form_for @user_session, :url => user_session_url do |f| -%>
  <%= f.input :username, :hint => 'login in with username or email.' %>
  <%= f.input :password, :as => :password %>
  <%- f.buttons do -%>
    <%= f.commit_button 'Login' %>
  <%- end -%>
<%- end -%>

My UserSession and User models then work it out that :username can be equal to either User#username or User#email.

1
2
3
4
5
6
class User < ActiveRecord::Base
  acts_as_authentic
  def self.find_by_username_or_email(login)
    find_by_email(login) || find_by_username(login)
  end
end

I had started to build some role-based authorization, but now that CanCan is more developed it’s probably time to look at that. My initial idea was to use Ruby metaprogramming to define new user methods based on the roles assigned to a user. I guess that’s still a valid approach, and theoretically would work with CanCan… the gem may make my approach redundant, however. I can definitely foresee errors based on Role objects overlapping the ActiveRecord namespace. I think I’d have to build in validations to check the User namespace before saving new Roles. weirdness.