class Stannum::RSpec::ValidateParameterMatcher
Asserts that the command validates the given method parameter.
Attributes
@return [Stannum::Constraints::Base, nil] the constraint used to generate
the expected error(s).
@return [String, Symbol] the name of the method with validated parameters.
@return [String, Symbol] the name of the validated method parameter.
@return [Object] the invalid value for the validated parameter.
@return [Hash] the configured parameters to match.
Public Class Methods
@private
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 23 def add_parameter_mapping(map:, match:) raise ArgumentError, 'map must be a Proc' unless map.is_a?(Proc) raise ArgumentError, 'match must be a Proc' unless match.is_a?(Proc) parameter_mappings << { match: match, map: map } end
@private
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 31 def map_parameters(actual:, method_name:) parameter_mappings.each do |keywords| match = keywords.fetch(:match) map = keywords.fetch(:map) next unless match.call(actual: actual, method_name: method_name) return map.call(actual: actual, method_name: method_name) end unwrapped_method(actual: actual, method_name: method_name).parameters end
@param method_name
[String, Symbol] The name of the method with validated
parameters.
@param parameter_name
[String, Symbol] The name of the validated method
parameter.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 79 def initialize(method_name:, parameter_name:) @method_name = method_name.intern @parameter_name = parameter_name.intern end
Private Class Methods
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 46 def default_parameter_mappings [ { match: lambda do |actual:, method_name:, **_| actual.is_a?(Class) && method_name == :new end, map: lambda do |actual:, **_| actual.instance_method(:initialize).parameters end } ] end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 59 def parameter_mappings @parameter_mappings ||= default_parameter_mappings end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 63 def unwrapped_method(actual:, method_name:) method = actual.method(method_name) validations = Stannum::ParameterValidation::MethodValidations until method.nil? return method unless method.owner.is_a?(validations) method = method.super_method end end
Public Instance Methods
@return [String] a short description of the matcher and expected
properties.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 102 def description "validate the #{parameter_name.inspect} #{parameter_type || 'parameter'}" end
Asserts that the object does not validate the specified method parameter.
@param actual [Object] The object to match.
@return [true, false] false if the object validates the parameter,
otherwise true.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 112 def does_not_match?(actual) disallow_fluent_options! @actual = actual @failure_reason = nil return true unless supports_parameter_validation? return false unless responds_to_method? return true unless validates_method? return false unless method_has_parameter? !validates_method_parameter? end
@return [String] a summary message describing a failed expectation.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 127 def failure_message # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength message = "expected ##{method_name} to #{description}" reason = case @failure_reason when :does_not_respond_to_method "the object does not respond to ##{method_name}" when :does_not_support_parameter_validation 'the object does not implement parameter validation' when :does_not_validate_method "the object does not validate the parameters of ##{method_name}" when :errors_do_not_match "the errors do not match:\n\n#{equality_matcher_failure_message}" when :method_does_not_have_parameter "##{method_name} does not have a #{parameter_name.inspect} parameter" when :parameter_not_validated "##{method_name} does not expect a #{parameter_name.inspect}" \ " #{parameter_type}" when :valid_parameter_value "#{valid_value.inspect} is a valid value for the" \ " #{parameter_name.inspect} #{parameter_type}" end [message, reason].compact.join(', but ') end
@return [String] a summary message describing a failed negated
expectation.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 154 def failure_message_when_negated message = "expected ##{method_name} not to #{description}" reason = case @failure_reason when :does_not_respond_to_method "the object does not respond to ##{method_name}" when :method_does_not_have_parameter "##{method_name} does not have a #{parameter_name.inspect} parameter" end [message, reason].compact.join(', but ') end
Asserts that the object validates the specified method parameter.
@param actual [Object] The object to match.
@return [true, false] true if the object validates the parameter,
otherwise false.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 173 def matches?(actual) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity @actual = actual @failure_reason = nil return false unless supports_parameter_validation? return false unless responds_to_method? return false unless validates_method? return false unless method_has_parameter? if @expected_constraint.nil? && @parameters.nil? && @parameter_value.nil? return validates_method_parameter? end call_validated_method return false if extra_parameter? return false if valid_parameter? matches_expected_error? end
Specifies a constraint or type used to validate the parameter.
@param constraint [Stannum::Constraints::Base, Class, Module] The
constraint or type.
@return [Stannum::RSpec::ValidateParameterMatcher] the matcher.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 200 def using_constraint(constraint, **options) @expected_constraint = Stannum::Support::Coercion.type_constraint( constraint, as: 'constraint', **options ) self end
Specifies custom parameters to test.
The matcher will pass if and only if the method fails validation with the specified parameters.
@param arguments [Array] A list of arguments to test. @param keywords [Hash] A hash of keywords to test.
@return [Stannum::RSpec::ValidateParameterMatcher] the matcher.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 219 def with_parameters(*arguments, **keywords, &block) if @parameter_value raise 'cannot use both #with_parameters and #with_value' end @parameters = [ arguments, keywords, block ] self end
Specifies an invalid value for the parameter.
@param parameter_value
[Object] The invalid value for the validated
parameter.
@return [Stannum::RSpec::ValidateParameterMatcher] the matcher.
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 239 def with_value(parameter_value) raise 'cannot use both #with_parameters and #with_value' if @parameters @parameter_value = parameter_value self end
Private Instance Methods
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 255 def build_parameters case parameter_type when :argument @parameter_index = find_parameter_index(method_parameters) [[*Array.new(parameter_index, nil), parameter_value], {}, nil] when :keyword [[], { parameter_name => parameter_value }, nil] when :block [[], {}, parameter_value] end end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 268 def call_validated_method arguments, keywords, block = @parameters || build_parameters mock_validation_handler do actual.send(method_name, *arguments, **keywords, &block) rescue InvalidParameterHandledError # Do nothing. end rescue ArgumentError # Do nothing. end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 280 def disallow_fluent_options! # rubocop:disable Metrics/MethodLength unless @expected_constraint.nil? raise RuntimeError, '#does_not_match? with #using_constraint is not supported', caller[1..-1] end unless @parameters.nil? raise RuntimeError, '#does_not_match? with #with_parameters is not supported', caller[1..-1] end return if @parameter_value.nil? raise RuntimeError, '#does_not_match? with #with_value is not supported', caller[1..-1] end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 300 def equality_matcher RSpec::SleepingKingStudios::Matchers::Core::DeepMatcher .new(@expected_errors.to_a) rescue NameError # :nocov: RSpec::Matchers::BuiltIn::Eq.new(@expected_errors.to_a) # :nocov: end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 309 def equality_matcher_failure_message equality_matcher .tap { |matcher| matcher.matches?(scoped_errors.to_a) } .failure_message end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 315 def extra_parameter? extra_arguments_type = Stannum::Constraints::Parameters::ExtraArguments::TYPE extra_keywords_type = Stannum::Constraints::Parameters::ExtraKeywords::TYPE return false unless scoped_errors(indexed: true).any? do |error| error[:type] == extra_arguments_type || error[:type] == extra_keywords_type end @failure_reason = :parameter_not_validated true end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 331 def find_parameter_index(parameters) parameters.index { |_, name| name == parameter_name } end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 335 def find_parameter_type parameters = method_parameters type, _ = parameters.find { |_, name| name == parameter_name } case type when :req, :opt :argument when :keyreq, :key :keyword when :block :block end end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 349 def matches_expected_error? return true unless expected_constraint @expected_errors = expected_constraint.errors_for(parameter_value) if @expected_errors.all? { |error| scoped_errors.include?(error) } return true end @failure_reason = :errors_do_not_match false end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 363 def method_has_parameter? @parameter_type = find_parameter_type return true unless parameter_type.nil? @failure_reason = :method_does_not_have_parameter false end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 373 def method_parameters @method_parameters ||= self.class.map_parameters(actual: actual, method_name: method_name) end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 378 def mock_validation_handler @validation_handler_called = false @validation_errors = nil allow(actual).to receive(:handle_invalid_parameters) do |keywords| @validation_handler_called = true @validation_errors = keywords[:errors] raise InvalidParameterHandledError end yield allow(actual).to receive(:handle_invalid_parameters).and_call_original end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 394 def responds_to_method? return true if actual.respond_to?(method_name) @failure_reason = :does_not_respond_to_method false end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 402 def scoped_errors(indexed: false) # rubocop:disable Metrics/MethodLength return [] if @validation_errors.nil? case parameter_type when :argument @parameter_index ||= find_parameter_index(method_parameters) parameter_key = indexed ? parameter_index : parameter_name @validation_errors[:arguments][parameter_key] when :keyword @validation_errors[:keywords][parameter_name] when :block @validation_errors[:block] end end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 418 def supports_parameter_validation? return true if actual.is_a?(Stannum::ParameterValidation) @failure_reason = :does_not_support_parameter_validation false end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 426 def valid_parameter? return false unless scoped_errors.empty? @failure_reason = :valid_parameter_value true end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 434 def valid_value # rubocop:disable Metrics/MethodLength return @parameter_value if @parameter_value return nil unless @parameters case parameter_type when :argument @parameter_index ||= find_parameter_index(method_parameters) parameters[0][parameter_index] when :keyword parameters[1][parameter_name] when :block parameters[2] end end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 451 def validates_method? return true if validation_contracts.include?(method_name) @failure_reason = :does_not_validate_method false end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 459 def validates_method_argument? contract = validation_contracts.fetch(method_name) contract.send(:arguments_contract).each_constraint.any? do |definition| definition.property_name == parameter_name end end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 467 def validates_method_block? contract = validation_contracts.fetch(method_name) !contract.send(:block_constraint).nil? end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 473 def validates_method_keyword? contract = validation_contracts.fetch(method_name) contract.send(:keywords_contract).each_constraint.any? do |definition| definition.property_name == parameter_name end end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 481 def validates_method_parameter? # rubocop:disable Metrics/MethodLength validates_parameter = case parameter_type when :argument validates_method_argument? when :keyword validates_method_keyword? when :block validates_method_block? end return true if validates_parameter @failure_reason = :parameter_not_validated false end
# File lib/stannum/rspec/validate_parameter_matcher.rb, line 499 def validation_contracts if actual.is_a?(Module) actual.singleton_class::MethodValidations.contracts else actual.class::MethodValidations.contracts end end