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