lib/pundit/matchers.rb in pundit-matchers-2.3.0 vs lib/pundit/matchers.rb in pundit-matchers-3.0.0.beta1
- old
+ new
@@ -1,413 +1,169 @@
# frozen_string_literal: true
require 'rspec/core'
+require_relative 'matchers/permit_actions_matcher'
+
+require_relative 'matchers/permit_attributes_matcher'
+
+require_relative 'matchers/forbid_all_actions_matcher'
+require_relative 'matchers/forbid_only_actions_matcher'
+
+require_relative 'matchers/permit_all_actions_matcher'
+require_relative 'matchers/permit_only_actions_matcher'
+
module Pundit
+ # Matchers module provides a set of RSpec matchers for testing Pundit policies.
module Matchers
- require_relative 'matchers/utils/policy_info'
- require_relative 'matchers/utils/all_actions/forbidden_actions_error_formatter'
- require_relative 'matchers/utils/all_actions/forbidden_actions_matcher'
- require_relative 'matchers/utils/all_actions/permitted_actions_error_formatter'
- require_relative 'matchers/utils/all_actions/permitted_actions_matcher'
+ # A Proc that negates the description of a matcher.
+ NEGATED_DESCRIPTION = ->(description) { description.gsub(/^permit/, 'forbid') }
- require_relative 'matchers/utils/only_actions/forbidden_actions_error_formatter'
- require_relative 'matchers/utils/only_actions/forbidden_actions_matcher'
- require_relative 'matchers/utils/only_actions/permitted_actions_error_formatter'
- require_relative 'matchers/utils/only_actions/permitted_actions_matcher'
-
+ # Configuration class for Pundit Matchers.
class Configuration
- attr_accessor :user_alias
+ # The default user object value
+ DEFAULT_USER_ALIAS = :user
+ # The default user object in policies.
+ # @return [Symbol|String]
+ attr_accessor :default_user_alias
+
+ # Policy-specific user objects.
+ #
+ # @example Use +:client+ as user alias for class +Post+
+ # config.user_aliases = { 'Post' => :client }
+ #
+ # @return [Hash]
+ attr_accessor :user_aliases
+
def initialize
- @user_alias = :user
+ @default_user_alias = DEFAULT_USER_ALIAS
+ @user_aliases = {}
end
+
+ # Returns the user object for the given policy.
+ #
+ # @return [Symbol]
+ def user_alias(policy)
+ user_aliases.fetch(policy.class.name, default_user_alias)
+ end
end
class << self
+ # Configures Pundit Matchers.
+ #
+ # @yieldparam [Configuration] configuration the configuration object to be modified.
def configure
yield(configuration)
end
+ # Returns the configuration object for Pundit Matchers.
+ #
+ # @return [Configuration] the configuration object.
def configuration
@configuration ||= Pundit::Matchers::Configuration.new
end
end
- end
- RSpec::Matchers.define :forbid_action do |action, *args, **kwargs|
- match do |policy|
- if args.any?
- !policy.public_send("#{action}?", *args, **kwargs)
- else
- !policy.public_send("#{action}?", **kwargs)
- end
+ # Creates a matcher that tests if the policy permits a given action.
+ #
+ # @param [Symbol] action the action to be tested.
+ # @return [PermitActionsMatcher] the matcher object.
+ def permit_action(action)
+ PermitActionsMatcher.new(action)
end
- failure_message do |policy|
- "#{policy.class} does not forbid #{action} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
+ # @!macro [attach] RSpec::Matchers.define_negated_matcher
+ # @!method $1
+ #
+ # The negated matcher of {$2}.
+ #
+ # Same as +expect(policy).not_to $2(*args)+.
+ RSpec::Matchers.define_negated_matcher :forbid_action, :permit_action, &NEGATED_DESCRIPTION
- failure_message_when_negated do |policy|
- "#{policy.class} does not permit #{action} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
+ # Creates a matcher that tests if the policy permits a set of actions.
+ #
+ # @param [Array<Symbol>] actions the actions to be tested.
+ # @return [PermitActionsMatcher] the matcher object.
+ def permit_actions(*actions)
+ PermitActionsMatcher.new(*actions)
end
- end
- RSpec::Matchers.define :forbid_actions do |*actions|
- actions.flatten!
- match do |policy|
- return false if actions.count < 1
+ RSpec::Matchers.define_negated_matcher :forbid_actions, :permit_actions, &NEGATED_DESCRIPTION
- @allowed_actions = actions.select do |action|
- policy.public_send("#{action}?")
- end
- @allowed_actions.empty?
+ # Creates a matcher that tests if the policy permits all actions.
+ #
+ # @note The negative form +not_to permit_all_actions+ is not supported
+ # since it creates ambiguity. Instead use +to forbid_all_actions+.
+ #
+ # @return [PermitAllActionsMatcher] the matcher object.
+ def permit_all_actions
+ PermitAllActionsMatcher.new
end
- attr_reader :allowed_actions
-
- zero_actions_failure_message = 'At least one action must be ' \
- 'specified when using the forbid_actions matcher.'
-
- failure_message do |policy|
- if actions.count.zero?
- zero_actions_failure_message
- else
- "#{policy.class} expected to forbid #{actions}, but permitted " \
- "#{allowed_actions} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
+ # Creates a matcher that tests if the policy forbids all actions.
+ #
+ # @note The negative form +not_to forbid_all_actions+ is not supported
+ # since it creates ambiguity. Instead use +to permit_all_actions+.
+ #
+ # @return [ForbidAllActionsMatcher] the matcher object.
+ def forbid_all_actions
+ ForbidAllActionsMatcher.new
end
- failure_message_when_negated do |policy|
- if actions.count.zero?
- zero_actions_failure_message
- else
- "#{policy.class} expected to permit #{actions}, but forbade " \
- "#{allowed_actions} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
+ # Creates a matcher that tests if the policy permits the edit and update actions.
+ #
+ # @return [PermitActionsMatcher] the matcher object.
+ def permit_edit_and_update_actions
+ PermitActionsMatcher.new(:edit, :update)
end
- end
- RSpec::Matchers.define :forbid_edit_and_update_actions do
- match do |policy|
- !policy.edit? && !policy.update?
- end
+ RSpec::Matchers.define_negated_matcher :forbid_edit_and_update_actions, :permit_edit_and_update_actions,
+ &NEGATED_DESCRIPTION
- failure_message do |policy|
- "#{policy.class} does not forbid the edit or update action for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
+ # Creates a matcher that tests if the policy permits the new and create actions.
+ #
+ # @return [PermitActionsMatcher] the matcher object.
+ def permit_new_and_create_actions
+ PermitActionsMatcher.new(:new, :create)
end
- failure_message_when_negated do |policy|
- "#{policy.class} does not permit the edit or update action for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
+ RSpec::Matchers.define_negated_matcher :forbid_new_and_create_actions, :permit_new_and_create_actions,
+ &NEGATED_DESCRIPTION
- RSpec::Matchers.define :forbid_mass_assignment_of do |attributes|
- # Map single object argument to an array, if necessary
- attributes = [attributes] unless attributes.is_a?(Array)
-
- match do |policy|
- return false if attributes.count < 1
-
- @allowed_attributes = attributes.select do |attribute|
- if defined? @action
- policy.send("permitted_attributes_for_#{@action}").include? attribute
- else
- policy.permitted_attributes.include? attribute
- end
- end
-
- @allowed_attributes.empty?
+ # Creates a matcher that tests if the policy permits only a set of actions.
+ #
+ # @note The negative form +not_to permit_only_actions+ is not supported
+ # since it creates ambiguity. Instead use +to forbid_only_actions+.
+ #
+ # @param [Array<Symbol>] actions the actions to be tested.
+ # @return [PermitOnlyActionsMatcher] the matcher object.
+ def permit_only_actions(*actions)
+ PermitOnlyActionsMatcher.new(*actions)
end
- attr_reader :allowed_attributes
-
- chain :for_action do |action|
- @action = action
+ # Creates a matcher that tests if the policy forbids only a set of actions.
+ #
+ # @note The negative form +not_to forbid_only_actions+ is not supported
+ # since it creates ambiguity. Instead use +to permit_only_actions+.
+ #
+ # @param [Array<Symbol>] actions the actions to be tested.
+ # @return [ForbidOnlyActionsMatcher] the matcher object.
+ def forbid_only_actions(*actions)
+ ForbidOnlyActionsMatcher.new(*actions)
end
- zero_attributes_failure_message = 'At least one attribute must be ' \
- 'specified when using the forbid_mass_assignment_of matcher.'
-
- failure_message do |policy|
- if attributes.count.zero?
- zero_attributes_failure_message
- elsif defined? @action
- "#{policy.class} expected to forbid the mass assignment of the " \
- "attributes #{attributes} when authorising the #{@action} action, " \
- 'but permitted the mass assignment of the attributes ' \
- "#{allowed_attributes} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- else
- "#{policy.class} expected to forbid the mass assignment of the " \
- "attributes #{attributes}, but permitted the mass assignment of " \
- "the attributes #{allowed_attributes} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
+ # Creates a matcher that tests if the policy permits mass assignment of a set of attributes.
+ #
+ # @param [Array<Symbol>] attributes the attributes to be tested.
+ # @return [PermitAttributesMatcher] the matcher object.
+ def permit_mass_assignment_of(*attributes)
+ PermitAttributesMatcher.new(*attributes)
end
- failure_message_when_negated do |policy|
- if attributes.count.zero?
- zero_attributes_failure_message
- elsif defined? @action
- "#{policy.class} expected to permit the mass assignment of the " \
- "attributes #{attributes} when authorising the #{@action} action, " \
- 'but permitted the mass assignment of the attributes ' \
- "#{allowed_attributes} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- else
- "#{policy.class} expected to permit the mass assignment of the " \
- "attributes #{attributes}, but permitted the mass assignment of " \
- "the attributes #{allowed_attributes} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
+ RSpec::Matchers.define_negated_matcher :forbid_mass_assignment_of, :permit_mass_assignment_of, &NEGATED_DESCRIPTION
end
-
- RSpec::Matchers.define :forbid_new_and_create_actions do
- match do |policy|
- !policy.new? && !policy.create?
- end
-
- failure_message do |policy|
- "#{policy.class} does not forbid the new or create action for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
-
- failure_message_when_negated do |policy|
- "#{policy.class} does not permit the new or create action for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
-
- RSpec::Matchers.define :permit_action do |action, *args, **kwargs|
- match do |policy|
- if args.any?
- policy.public_send("#{action}?", *args, **kwargs)
- else
- policy.public_send("#{action}?", **kwargs)
- end
- end
-
- failure_message do |policy|
- "#{policy.class} does not permit #{action} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
-
- failure_message_when_negated do |policy|
- "#{policy.class} does not forbid #{action} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
-
- RSpec::Matchers.define :permit_actions do |*actions|
- actions.flatten!
- match do |policy|
- return false if actions.count < 1
-
- @forbidden_actions = actions.reject do |action|
- policy.public_send("#{action}?")
- end
- @forbidden_actions.empty?
- end
-
- match_when_negated do |policy|
- ::Kernel.warn 'Using expect { }.not_to permit_actions could produce \
- confusing results. Please use `.to forbid_actions` instead. To \
- clarify, `.not_to permit_actions` will look at all of the actions and \
- checks if ANY actions fail, not if all actions fail. Therefore, you \
- could result in something like this: \
-
- it { is_expected.to permit_actions([:new, :create, :edit]) } \
- it { is_expected.not_to permit_actions([:edit, :destroy]) } \
-
- In this case, edit would be true and destroy would be false, but both \
- tests would pass.'
-
- return true if actions.count < 1
-
- @forbidden_actions = actions.reject do |action|
- policy.public_send("#{action}?")
- end
- !@forbidden_actions.empty?
- end
-
- attr_reader :forbidden_actions
-
- zero_actions_failure_message = 'At least one action must be specified ' \
- 'when using the permit_actions matcher.'
-
- failure_message do |policy|
- if actions.count.zero?
- zero_actions_failure_message
- else
- "#{policy.class} expected to permit #{actions}, but forbade " \
- "#{forbidden_actions} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
-
- failure_message_when_negated do |policy|
- if actions.count.zero?
- zero_actions_failure_message
- else
- "#{policy.class} expected to forbid #{actions}, but permitted " \
- "#{forbidden_actions} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
- end
-
- RSpec::Matchers.define :permit_edit_and_update_actions do
- match do |policy|
- policy.edit? && policy.update?
- end
-
- failure_message do |policy|
- "#{policy.class} does not permit the edit or update action for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
-
- failure_message_when_negated do |policy|
- "#{policy.class} does not forbid the edit or update action for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
-
- RSpec::Matchers.define :permit_mass_assignment_of do |attributes|
- # Map single object argument to an array, if necessary
- attributes = [attributes] unless attributes.is_a?(Array)
-
- match do |policy|
- return false if attributes.count < 1
-
- @forbidden_attributes = attributes.select do |attribute|
- if defined? @action
- !policy.send("permitted_attributes_for_#{@action}").include? attribute
- else
- !policy.permitted_attributes.include? attribute
- end
- end
-
- @forbidden_attributes.empty?
- end
-
- attr_reader :forbidden_attributes
-
- chain :for_action do |action|
- @action = action
- end
-
- zero_attributes_failure_message = 'At least one attribute must be ' \
- 'specified when using the permit_mass_assignment_of matcher.'
-
- failure_message do |policy|
- if attributes.count.zero?
- zero_attributes_failure_message
- elsif defined? @action
- "#{policy.class} expected to permit the mass assignment of the " \
- "attributes #{attributes} when authorising the #{@action} action, " \
- 'but forbade the mass assignment of the attributes ' \
- "#{forbidden_attributes} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- else
- "#{policy.class} expected to permit the mass assignment of the " \
- "attributes #{attributes}, but forbade the mass assignment of the " \
- "attributes #{forbidden_attributes} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
-
- failure_message_when_negated do |policy|
- if attributes.count.zero?
- zero_attributes_failure_message
- elsif defined? @action
- "#{policy.class} expected to forbid the mass assignment of the " \
- "attributes #{attributes} when authorising the #{@action} action, " \
- 'but forbade the mass assignment of the attributes ' \
- "#{forbidden_attributes} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- else
- "#{policy.class} expected to forbid the mass assignment of the " \
- "attributes #{attributes}, but forbade the mass assignment of the " \
- "attributes #{forbidden_attributes} for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
- end
-
- RSpec::Matchers.define :permit_new_and_create_actions do
- match do |policy|
- policy.new? && policy.create?
- end
-
- failure_message do |policy|
- "#{policy.class} does not permit the new or create action for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
-
- failure_message_when_negated do |policy|
- "#{policy.class} does not forbid the new or create action for " \
- "#{policy.public_send(Pundit::Matchers.configuration.user_alias).inspect}."
- end
- end
-
- RSpec::Matchers.define :permit_all_actions do
- match do |policy|
- @matcher = Pundit::Matchers::Utils::AllActions::PermittedActionsMatcher.new(policy)
- @matcher.match?
- end
-
- failure_message do
- formatter = Pundit::Matchers::Utils::AllActions::PermittedActionsErrorFormatter.new(@matcher)
- formatter.message
- end
- end
-
- RSpec::Matchers.define :permit_only_actions do |actions|
- match do |policy|
- @matcher = Pundit::Matchers::Utils::OnlyActions::PermittedActionsMatcher.new(policy, actions)
- @matcher.match?
- end
-
- failure_message do
- formatter = Pundit::Matchers::Utils::OnlyActions::PermittedActionsErrorFormatter.new(@matcher)
- formatter.message
- end
- end
-
- RSpec::Matchers.define :forbid_all_actions do
- match do |policy|
- @matcher = Pundit::Matchers::Utils::AllActions::ForbiddenActionsMatcher.new(policy)
- @matcher.match?
- end
-
- failure_message do
- formatter = Pundit::Matchers::Utils::AllActions::ForbiddenActionsErrorFormatter.new(@matcher)
- formatter.message
- end
- end
-
- RSpec::Matchers.define :forbid_only_actions do |actions|
- match do |policy|
- @matcher = Pundit::Matchers::Utils::OnlyActions::ForbiddenActionsMatcher.new(policy, actions)
- @matcher.match?
- end
-
- failure_message do
- formatter = Pundit::Matchers::Utils::OnlyActions::ForbiddenActionsErrorFormatter.new(@matcher)
- formatter.message
- end
- end
end
-if defined?(Pundit)
- RSpec.configure do |config|
- config.include Pundit::Matchers
- end
+RSpec.configure do |config|
+ config.include Pundit::Matchers
end