Как проверить политику Pundit с помощью Minitest?

Gemfile

gem 'pundit', '~> 0.2.1'

приложение/контроллеры/application_controller.rb

class ApplicationController < ActionController::Base

  include Pundit
  ...

приложение/политика/application_policy.rb

class ApplicationPolicy < Struct.new(:user, :record)
  def index?  ; false;                              end
  def show?   ; scope.where(id: record.id).exists?; end
  def create? ; false;                              end
  def new?    ; create?;                            end
  def update? ; false;                              end
  def edit?   ; update?;                            end
  def destroy?; false;                              end
  def scope
    Pundit.policy_scope!(user, record.class)
  end
end

приложение/политика/book_policy.rb

class BookPolicy < ApplicationPolicy

  def create?
    record.new_record?
  end

  def new?
    create?      end

  def show?
    record.published? || user == record.user || user.is?(:admin)
  end

end

приложение/контроллеры/books_controller.rb

class BooksController < ApplicationController
  before_action :set_book, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [:show]

  after_action :verify_authorized, except: :index
  after_action :verify_policy_scoped, only: :index

  # GET /books/1
  def show
    authorize(@book)
  end

  # GET /books/new
  def new
    @book = Book.new
    authorize(@book)
  end

  # POST /books
  def create
    @book = current_user.books.build(book_params)
    authorize(@book)

    if @book.save
      redirect_to @book, notice: 'Your book was successfully created.'
    else
      render action: 'new'
    end
  end

private
    def set_book
      @book = Book.find(params[:id])
    end

    def book_params
      params.require(:book).permit(:title, :description)
    end
end

<сильные > тест/фабрики/factories.rb

FactoryGirl.define do

  factory :user do
    sequence(:email) { |n| "email#{n}@x.com" }
    password  '12345678'
    password_confirmation  '12345678'
  end

  factory :book do
    title  'xx'
    user
  end

end

Ответ 1

Первая попытка

В соответствии с документами Minitest я попробовал < Minitest::Test, но получил gems/minitest-4.7.5/lib/minitest/unit.rb:19:in 'const_missing': uninitialized constant MiniTest::Test (NameError), что привело меня к выводу, что документы в master для Minitest 5. Поэтому я преследовал документы Minitest перед фиксацией версии 5 и обнаружил, что мы должны быть подклассифицированы MiniTest::Unit::TestCase.

тест/политика/book_policy_test.rb

require 'test_helper'
class BookPolicyTest < Minitest::Test
  ...
end

Вторая попытка (правильный суперкласс)

тест/политика/book_policy_test.rb

require 'test_helper'

class BookPolicyTest < Minitest::Unit::TestCase

  def test_new
    user = FactoryGirl.create(:user)
    book_policy = BookPolicy.new(user, Book.new)
    assert book_policy.new?
  end

  def test_create
    book = FactoryGirl.create(:book)
    book_policy = BookPolicy.new(book.user, book)
    assert !book_policy.create?
  end

end

Рефакторинг один (создайте метод "разрешение" )

тест/политика/book_policy_test.rb

require 'test_helper'

class BookPolicyTest < Minitest::Unit::TestCase

  def test_new
    user = FactoryGirl.create(:user)
    assert permit(user, Book.new, :new)
  end

  def test_create
    book = FactoryGirl.create(:book)
    assert !permit(book.user, book, :create)
  end

private

    def permit(current_user, record, action)
      self.class.to_s.gsub(/Test/, '').constantize.new(current_user, record).public_send("#{action.to_s}?")
    end

end

Refactor two (создать класс PolicyTest)

Тест /test _helper.rb

class PolicyTest < Minitest::Unit::TestCase

  def permit(current_user, record, action)
    self.class.to_s.gsub(/Test/, '').constantize.new(current_user, record).public_send("#{action.to_s}?")
  end

end

тест/политика/book_policy_test.rb

require 'test_helper'

class BookPolicyTest < PolicyTest

  def test_new
    user = FactoryGirl.create(:user)
    assert permit(user, Book.new, :new)
  end

  def test_create
    book = FactoryGirl.create(:book)
    assert !permit(book.user, book, :create)
  end

end

Рефактор три (создайте метод "запретить" )

Тест /test _helper.rb

class PolicyTest < Minitest::Unit::TestCase

  def permit(current_user, record, action)
    self.class.to_s.gsub(/Test/, '').constantize.new(current_user, record).public_send("#{action.to_s}?")
  end

  def forbid(current_user, record, action)
    !permit(current_user, record, action)
  end

end

тест/политика/book_policy_test.rb

require 'test_helper'

class BookPolicyTest < PolicyTest

  def test_new
    user = FactoryGirl.create(:user)
    assert permit(user, Book.new, :new)
  end

  def test_create
    book = FactoryGirl.create(:book)
    assert forbid(book.user, book, :create)
  end

end

Добавить полный набор тестов политики

require 'test_helper'

class BookPolicyTest < PolicyTest

  def test_new
    assert permit(User.new, Book.new, :new)

    book = FactoryGirl.create(:book)
    assert forbid(book.user, book, :new)
  end

  def test_create
    assert permit(User.new, Book.new, :create)

    book = FactoryGirl.create(:book)
    assert forbid(book.user, book, :create)
  end

  def test_show
    # a stranger should be able to see a published book
    stranger = FactoryGirl.build(:user)
    book = FactoryGirl.create(:book, published: true)
    refute_equal stranger, book.user
    assert permit(stranger, book, :show)

    # but not if it NOT published
    book.published = false
    assert forbid(stranger, book, :show)

    # but the book owner still should
    assert permit(book.user, book, :show)

    # and so should the admin
    admin = FactoryGirl.build(:admin)
    assert permit(admin, book, :show)
  end

end

Ответ 2

Я создал жемчужину для тестирования pundit с minitest под названием policy-assertions. Вот как выглядит ваш тест.

class ArticlePolicyTest < PolicyAssertions::Test
  def test_index_and_show
    assert_permit nil, Article
  end

  def test_new_and_create
    assert_permit users(:staff), Article
  end

  def test_destroy
    refute_permit users(:regular), articles(:instructions)
  end
end

Ответ 3

Теперь Pundit предоставляет Pundit#authorize (https://github.com/elabs/pundit/pull/227). Таким образом, метод разрешения user664833 может быть обновлен следующим образом:

def permit(current_context, record, action)
  Pundit.authorize(current_context, record, action)
rescue Pundit::NotAuthorizedError
  false
end

Ответ 4

Основываясь на user664833 Ответ Я использую следующее, чтобы проверить Pundit policy_scope:

def permit_index(user, record)
  (record.class.to_s + 'Policy::Scope').constantize.new(user, record.class).resolve.include?(record)
end

Например:

assert permit_index(@book.user, @book)