如何在 Rspec 中测试 Pundit Scopes?

Dav*_*mpy 3 ruby rspec ruby-on-rails pundit

我有一个非常简单的 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
Run Code Online (Sandbox Code Playgroud)

当我运行 Rspec 时,我得到:

NoMethodError: undefined method `sign_in' for #<RSpec::ExampleGroups::ReportPolicy::Scope:0x00007f93241c67b8>
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在我的控制器中,我这样称呼它。我已经确认这在现实世界中正常工作,管理员可以看到所有报告,而其他用户只能看到他们帐户的报告:

reports = policy_scope(Report)
Run Code Online (Sandbox Code Playgroud)

max*_*max 10

您可以使用以下命令实例化策略范围:

Pundit.policy_scope!(user, Report)
Run Code Online (Sandbox Code Playgroud)

简称:

ReportPolicy::Scope.new(user, Report).resolve
Run Code Online (Sandbox Code Playgroud)

请注意,您不需要执行任何实际的用户登录步骤。user它只是您的策略范围作为初始值设定项参数的对象。Pundit 毕竟只是普通的老式 OOP。

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

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

    def resolve
      scope.all
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

至于实际的规格,我会把它写成:

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
Run Code Online (Sandbox Code Playgroud)

避免it { expect ... }使用诸如和 use it 块来描述您正在测试的实际行为的结构,否则您最终会得到非常神秘的失败消息和难以理解的测试。在单行语法 it { is_expected.to ... }应仅用于帮助避免重复在其中在该示例中使用的镜doc字符串和匹配器彼此完全相同的情况。