class ActionPolicy::RSpec::HaveAuthorizedScope

Implements `have_authorized_scope` matcher.

Verifies that a block of code applies authorization scoping using specific policy.

Example:

# in controller/request specs
subject { get :index }

it "has authorized scope" do
  expect { subject }
    .to have_authorized_scope(:active_record_relation)
    .with(ProductPolicy)
end

Attributes

actual_scopes[R]
name[R]
policy[R]
scope_options[R]
target_expectations[R]
type[R]

Public Class Methods

new(type) click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 26
def initialize(type)
  @type = type
  @name = :default
  @scope_options = nil
end

Public Instance Methods

actual_scopes_message() click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 100
def actual_scopes_message
  if actual_scopes.empty?
    "no scopings have been made"
  else
    "the following scopings were encountered:\n" \
    "#{formatted_scopings}"
  end
end
as(name) click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 37
def as(name)
  @name = name
  self
end
does_not_match?(*) click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 74
def does_not_match?(*)
  raise "This matcher doesn't support negation"
end
failure_message() click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 80
def failure_message
  "expected a scoping named :#{name} for type :#{type} " \
  "#{scope_options_message} " \
  "from #{policy} to have been applied, " \
  "but #{actual_scopes_message}"
end
formatted_scopings() click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 109
def formatted_scopings
  actual_scopes.map do
    " - #{_1.inspect}"
  end.join("\n")
end
match(_expected, actual) click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 52
def match(_expected, actual)
  raise "This matcher only supports block expectations" unless actual.is_a?(Proc)

  ActionPolicy::Testing::AuthorizeTracker.tracking { actual.call }

  @actual_scopes = ActionPolicy::Testing::AuthorizeTracker.scopings

  matching_scopes = actual_scopes.select { _1.matches?(policy, type, name, scope_options) }

  return false if matching_scopes.empty?

  return true unless target_expectations

  if matching_scopes.size > 1
    raise "Too many matching scopings (#{matching_scopes.size}), " \
          "you can run `.with_target` only when there is the only one match"
  end

  target_expectations.call(matching_scopes.first.target)
  true
end
scope_options_message() click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 87
def scope_options_message
  if scope_options
    if defined?(::RSpec::Matchers::Composable) &&
        scope_options.is_a?(::RSpec::Matchers::Composable)
      "with scope options #{scope_options.description}"
    else
      "with scope options #{scope_options}"
    end
  else
    "without scope options"
  end
end
supports_block_expectations?() click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 78
  def supports_block_expectations?() = true

  def failure_message
    "expected a scoping named :#{name} for type :#{type} " \
    "#{scope_options_message} " \
    "from #{policy} to have been applied, " \
    "but #{actual_scopes_message}"
  end

  def scope_options_message
    if scope_options
      if defined?(::RSpec::Matchers::Composable) &&
          scope_options.is_a?(::RSpec::Matchers::Composable)
        "with scope options #{scope_options.description}"
      else
        "with scope options #{scope_options}"
      end
    else
      "without scope options"
    end
  end

  def actual_scopes_message
    if actual_scopes.empty?
      "no scopings have been made"
    else
      "the following scopings were encountered:\n" \
      "#{formatted_scopings}"
    end
  end

  def formatted_scopings
    actual_scopes.map do
      " - #{_1.inspect}"
    end.join("\n")
  end
end
with(policy) click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 32
def with(policy)
  @policy = policy
  self
end
with_scope_options(scope_options) click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 42
def with_scope_options(scope_options)
  @scope_options = scope_options
  self
end
with_target(&block) click to toggle source
# File lib/action_policy/rspec/have_authorized_scope.rb, line 47
def with_target(&block)
  @target_expectations = block
  self
end