Search code examples
ruby-on-railsunit-testingrspecauthorizationpundit

Rails testing: ensure authorization (Pundit) is enforced in all controllers and actions


I'm writing RSpec tests for a Rails 4.2 application which uses Pundit for authorization.

I'd like to test whether authorization is enforced in all actions of all controllers, to avoid unintentionally providing public access to sensitive data in case a developer forgets to call policy_scope (on #index actions) and authorize (on all other actions).

One possible solution is to mock these methods in all controller unit tests. Something like expect(controller).to receive(:authorize).and_return(true) and expect(controller).to receive(:policy_scope).and_call_original. However, that would lead to a lot of code repetition. This line could be placed within a custom matcher or a helper method in spec/support but calling it in every spec of every controller also seems repetitive. Any ideas on how to achieve this in a DRY way?

In case you are wondering, Pundit's policy classes are tested separately, as shown in this post.


Solution

  • I feel like you could use something like this up in spec_helper. Note that I'm assuming a naming convention where you have the word "index" in the index level answers, so that your spec might look like this:

    describe MyNewFeaturesController, :type => :controller do
    
      describe "index" do
        # all of the index tests under here have policy_scope applied
      end
    
      # and these other tests have authorize applied
      describe 'show' do
      end
    
      describe 'destroy' do
      end
    end
    

    and here is the overall configuration:

    RSpec.configure do |config|
      config.before(:each, :type => :controller) do |spec|
        # if the spec description has "index" in the name, then use policy-level authorization
        if spec.metadata[:full_description] =~ /\bindex\b/
          expect(controller).to receive(:policy_scope).and_call_original
        else 
          expect(controller).to receive(:authorize).and_call_original
        end
      end
    end