Back to Repositories

Testing Policy Resolution and Scope Handling in Pundit

This test suite validates the PolicyFinder class functionality in the Pundit authorization library. It thoroughly examines policy resolution, scoping mechanisms, and parameter key handling for various object types and namespaces.

Test Coverage Overview

The test suite provides comprehensive coverage of PolicyFinder’s core functionality:
  • Policy resolution for instances, classes, and namespaced objects
  • Scope handling and retrieval
  • Parameter key generation for different object types
  • Edge cases including nil objects and missing policies

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with context-specific test organization. It leverages double objects for user simulation and implements nested describe blocks for logical grouping of related test scenarios.
  • Extensive use of let blocks for test setup
  • Context-based test organization
  • Expectation matching for policy resolution

Technical Details

Testing infrastructure includes:
  • RSpec as the testing framework
  • Mock objects and doubles for dependency isolation
  • Shared example groups for common behavior
  • Custom class definitions for testing different scenarios

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Clear test organization and naming
  • Proper isolation of test cases
  • Comprehensive coverage of edge cases
  • DRY principle through shared setup
  • Explicit expectation statements

varvet/pundit

spec/policy_finder_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

RSpec.describe Pundit::PolicyFinder do
  let(:user) { double }
  let(:post) { Post.new(user) }
  let(:comment) { CommentFourFiveSix.new }
  let(:article) { Article.new }

  describe "SUFFIX" do
    specify { expect(described_class::SUFFIX).to eq "Policy" }
    specify { expect(Pundit::SUFFIX).to eq(described_class::SUFFIX) }
  end

  describe "#scope" do
    subject { described_class.new(post) }

    it "returns a policy scope" do
      expect(subject.scope).to eq PostPolicy::Scope
    end

    context "policy is nil" do
      it "returns nil" do
        allow(subject).to receive(:policy).and_return nil
        expect(subject.scope).to eq nil
      end
    end
  end

  describe "#policy" do
    context "with an instance" do
      it "returns the associated policy" do
        object = described_class.new(post)

        expect(object.policy).to eq PostPolicy
      end
    end

    context "with an array of symbols" do
      it "returns the associated namespaced policy" do
        object = described_class.new(%i[project post])

        expect(object.policy).to eq Project::PostPolicy
      end
    end

    context "with an array of a symbol and an instance" do
      it "returns the associated namespaced policy" do
        object = described_class.new([:project, post])

        expect(object.policy).to eq Project::PostPolicy
      end
    end

    context "with an array of a symbol and a class with a specified policy class" do
      it "returns the associated namespaced policy" do
        object = described_class.new([:project, Customer::Post])

        expect(object.policy).to eq Project::PostPolicy
      end
    end

    context "with an array of a symbol and a class with a specified model name" do
      it "returns the associated namespaced policy" do
        object = described_class.new([:project, CommentsRelation])

        expect(object.policy).to eq Project::CommentPolicy
      end
    end

    context "with a class" do
      it "returns the associated policy" do
        object = described_class.new(Post)

        expect(object.policy).to eq PostPolicy
      end
    end

    context "with a class which has a specified policy class" do
      it "returns the associated policy" do
        object = described_class.new(Customer::Post)

        expect(object.policy).to eq PostPolicy
      end
    end

    context "with an instance which has a specified policy class" do
      it "returns the associated policy" do
        object = described_class.new(Customer::Post.new(user))

        expect(object.policy).to eq PostPolicy
      end
    end

    context "with a class which has a specified model name" do
      it "returns the associated policy" do
        object = described_class.new(CommentsRelation)

        expect(object.policy).to eq CommentPolicy
      end
    end

    context "with an instance which has a specified policy class" do
      it "returns the associated policy" do
        object = described_class.new(CommentsRelation.new)

        expect(object.policy).to eq CommentPolicy
      end
    end

    context "with nil" do
      it "returns a NilClassPolicy" do
        object = described_class.new(nil)

        expect(object.policy).to eq NilClassPolicy
      end
    end

    context "with a class that doesn't have an associated policy" do
      it "returns nil" do
        object = described_class.new(Foo)

        expect(object.policy).to eq nil
      end
    end
  end

  describe "#scope!" do
    context "@object is nil" do
      subject { described_class.new(nil) }

      it "returns the NilClass policy's scope class" do
        expect(subject.scope!).to eq NilClassPolicy::Scope
      end
    end

    context "@object is defined" do
      subject { described_class.new(post) }

      it "returns the scope" do
        expect(subject.scope!).to eq PostPolicy::Scope
      end
    end
  end

  describe "#param_key" do
    context "object responds to model_name" do
      subject { described_class.new(comment) }

      it "returns the param_key" do
        expect(subject.object).to respond_to(:model_name)
        expect(subject.param_key).to eq "comment_four_five_six"
      end
    end

    context "object is a class" do
      subject { described_class.new(Article) }

      it "returns the param_key" do
        expect(subject.object).not_to respond_to(:model_name)
        expect(subject.object).to be_a Class
        expect(subject.param_key).to eq "article"
      end
    end

    context "object is an instance of a class" do
      subject { described_class.new(article) }

      it "returns the param_key" do
        expect(subject.object).not_to respond_to(:model_name)
        expect(subject.object).not_to be_a Class
        expect(subject.object).to be_an_instance_of Article

        expect(subject.param_key).to eq "article"
      end
    end

    context "object is an array" do
      subject { described_class.new([:project, article]) }

      it "returns the param_key for the last element of the array" do
        expect(subject.object).not_to respond_to(:model_name)
        expect(subject.object).not_to be_a Class
        expect(subject.object).to be_an_instance_of Array

        expect(subject.param_key).to eq "article"
      end
    end
  end
end