Testing Pundit Policies with RSpec

March 27, 2013

talking_heads

The fine folks over at Elabs recently released Pundit: a new authorization gem that uses “regular Ruby classes and object oriented design patterns to build a simple, robust and scaleable authorization system”. It competes squarely with cancan, but aims at being simpler, more predictable and easier to understand.

We’re going to look at a way of making your pundit specs as readable as possible, but first…

A Quick Pundit Overview

With pundit, you create regular ruby policy classes that follow well defined conventions. Here’s an example of a policy file that would control access to an Article class. This goes in the app/policies/article_policy.rb file:

class ArticlePolicy < ApplicationPolicy
  def show?
    true
  end

  def create?
    user
  end

  def update?
    user
  end

  def destroy?
    user
  end

  ...
end

While ArticlePolicy descends from ApplicationPolicy, that doesn’t make it special. In fact, ApplicationPolicyis simply another Ruby class:

class ApplicationPolicy < Struct.new(:user, :record)
  def show?
    true
  end

  def create?
    false
  end

  ...
end

Note that we’re descending from Struct, which gives us #user and #record accessors for free. Pundit then helps you make use of these classes inside your views and controllers.

Now, let’s look at how you’d test drive these with the help of…

A Custom Matcher for Pundit

While it’s entirely reasonable to test your pundit policies using stock rspec, it’s always nice to be able to increase the readability of your specs with a well written custom matcher.

Here’s what we’d like the specs to look like. This is the spec/policies/article_policy_spec.rb spec file:

require 'spec_helper'

describe ArticlePolicy do
  subject { ArticlePolicy.new(user, article) }

  let(:article) { FactoryGirl.create(:article) }

  context "for a visitor" do
    let(:user) { nil }

    it { should     permit(:show)    }

    it { should_not permit(:create)  }
    it { should_not permit(:new)     }
    it { should_not permit(:update)  }
    it { should_not permit(:edit)    }
    it { should_not permit(:destroy) }
  end

  context "for a user" do
    let(:user) { FactoryGirl.create(:user) }

    it { should permit(:show)    }
    it { should permit(:create)  }
    it { should permit(:new)     }
    it { should permit(:update)  }
    it { should permit(:edit)    }
    it { should permit(:destroy) }
  end
end

Note two things:

  1. The use of late-binding let statements for user and article. This lets us change those values when we create the ArticlePolicy.new(user, article) subject inside each of the context blocks.
  2. The should permit(:action) syntax, which is enabled through this custom matcher:
RSpec::Matchers.define :permit do |action|
  match do |policy|
    policy.public_send("#{action}?")
  end

  failure_message_for_should do |policy|
    "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}."
  end

  failure_message_for_should_not do |policy|
    "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}."
  end
end

Throw that in a file under spec/support (we named ours spec/support/pundit_matcher.rb), and make sure yourspec/spec_helper.rb includes it:

Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

For Security’s Sake

Readable code and easily understandable specs are important in every project and every context, but they’re absolutely paramount when dealing with authorization rules. Between Pundit and the power of custom RSpec matchers, we can now spot holes in our auth policies with ease.