Как проверить Pundit Scopes в Rspec? - PullRequest
0 голосов
/ 23 января 2019

У меня довольно простая политика Pundit с областью действия для разных ролей пользователей. Я не могу понять, как проверить это в Rspec. В частности, я не знаю, как определить область, в которую вошел пользователь, прежде чем получить доступ к области.

Вот что я пробовал:

let(:records) { policy_scope(Report) } 

context 'admin user' do
  before(:each) { sign_in(admin_user) }
  it { expect(reports.to_a).to match_array([account1_report, account2_report]) }
end

context 'client user' do
  before(:each) { sign_in(account2_user) }
  it { expect(reports.to_a).to match_array([account2_report]) }
end

Когда я запускаю Rspec, я получаю:

NoMethodError: undefined method `sign_in' for #<RSpec::ExampleGroups::ReportPolicy::Scope:0x00007f93241c67b8>

Я широко использую sign_in в тестах контроллеров, но, полагаю, это не применимо в тесте политик.

Документы Pundit говорят только:

Pundit не предоставляет DSL для тестирования областей. Просто протестируйте его как обычный класс Ruby!

Итак ... у кого-нибудь есть пример тестирования области Pundit для конкретного пользователя? Как мне определить область действия current_user?


FWIW, вот суть моей политики:

class ReportPolicy < ApplicationPolicy
  def index?
    true
  end

  class Scope < Scope
    def resolve
      if user.role == 'admin'
        scope.all
      else
        scope.where(account_id: user.account_id)
      end
    end
  end
end

В моем контроллере я называю это следующим образом. Я подтвердил, что это работает правильно в реальном мире: администраторы видят все отчеты, а другие пользователи видят отчеты только для своей учетной записи:

reports = policy_scope(Report)

Ответы [ 2 ]

0 голосов
/ 24 января 2019

Вы можете создать область действия политики с помощью:

Pundit.policy_scope!(user, Report)

Что означает:

ReportPolicy::Scope.new(user, Report).resolve

Обратите внимание, что вам не нужно выполнять какие-либо реальные шаги для подписи пользователяin. user - это просто объект, который ваша область действия политики принимает в качестве аргумента инициализатора.Пандит в конце концов просто старый ООП.

class ApplicationPolicy
  # ...
  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      scope.all
    end
  end
end

Что касается фактической спецификации, я бы написал ее так:

require 'rails_helper'
require 'pundit/rspec'

RSpec.describe ReportPolicy, type: :policy do
  let(:user) { User.new }
  let(:scope) { Pundit.policy_scope!(user, Report) } 
  # ... setup account1_report etc

  describe "Scope" do
    context 'client user' do
      it 'allows a limited subset' do
        expect(scope.to_a).to match_array([account2_report])
      end 
    end
    context 'admin user' do
      let(:user) { User.new(role: 'admin') }
      it 'allows access to all the reports' do
        expect(scope.to_a).to match_array([account1_report, account2_report]
      end
    end
  end
end

Избегайте конструкций, таких как it { expect ... }, и используйте блоки, описывающие реальное поведение, которое вы тестируете иливы получите действительно загадочные сообщения об ошибках и тесты, которые трудно понять. однострочный синтаксис it { is_expected.to ... } должен использоваться только во избежание дублирования в ситуациях, когда строка документа и сопоставление, используемые в примере, точно отражают друг друга.

0 голосов
/ 23 января 2019

Замена

let(:records) { policy_scope(Report) } 

... с этим:

let(:records) { ReportPolicy::Scope.new(user, Report).resolve }

... позволяет указать пользователя в политике. Вызова sign_in не требуется.

Вот полное решение:

let(:records) { ReportPolicy::Scope.new(user, Report).resolve }

context 'admin user' do
  let(:user) { admin_user }
  it { expect(reports.to_a).to match_array([account1_report, account2_report]) }
end

context 'client user' do
  let(:user) { account2_user }
  it { expect(reports.to_a).to match_array([account2_report]) }
end
...