Skip to content

Commit

Permalink
Inclusion and exclusion validation specs.
Browse files Browse the repository at this point in the history
  • Loading branch information
AlphaHydrae committed Jan 26, 2015
1 parent 0f25554 commit ca351ae
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 25 deletions.
19 changes: 6 additions & 13 deletions lib/errapi/validations/clusivity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,17 @@ module Clusivity

DELIMITER_METHOD_CHECKS = %i(include? call to_sym).freeze

def check_delimiter! option_description
if DELIMITER_METHOD_CHECKS.none?{ |c| @delimiter.respond_to? c }
raise ArgumentError, "The #{option_description} option must be an object with the #include? method, a proc, a lambda or a symbol, but a #{@delimiter.class.name} was given."
def check_delimiter! option_desc
unless @delimiter.respond_to?(:include?) || callable_option_value?(@delimiter)
raise callable_option_type_error option_desc, "an object with the #include? method", @delimiter
end
end

def members source

enumerable = if @delimiter.respond_to? :call
@delimiter.call source
elsif @delimiter.respond_to? :to_sym
source.send @delimiter
else
@delimiter
end
def members option_desc, options = {}
enumerable = actual_option_value @delimiter, options

unless enumerable.respond_to? :include?
raise ArgumentError, "An enumerable must be returned from the given proc or lambda, or from calling the method corresponding to the given symbol, but a #{enumerable.class.name} was returned."
raise callable_option_value_error option_desc, "an object with the #include? method", @delimiter
end

enumerable
Expand Down
23 changes: 17 additions & 6 deletions lib/errapi/validations/exclusion.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
require File.join(File.dirname(__FILE__), 'clusivity.rb')

module Errapi::Validations
class Exclusion
class Exclusion < Base
include Clusivity

def initialize options = {}
@delimiter = options[:from] || options[:in] || options[:within]
check_delimiter! ":from (or :in or :within)"
unless key = exactly_one_option?(OPTIONS, options)
raise ArgumentError, "Either :from or :in or :within must be supplied (but only one of them)."
end

@delimiter = options[key]
check_delimiter! OPTIONS_DESCRIPTION
end

def validate value, context, options = {}
allowed_values = members options[:source]
if include? allowed_values, value
context.add_error reason: :not_included, check_value: allowed_values
excluded_values = members OPTIONS_DESCRIPTION, options
if include? excluded_values, value
context.add_error reason: :excluded, check_value: excluded_values
end
end

private

OPTIONS = %i(from in within)
OPTIONS_DESCRIPTION = ":from (or :in or :within)"
end
end
23 changes: 17 additions & 6 deletions lib/errapi/validations/inclusion.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
require File.join(File.dirname(__FILE__), 'clusivity.rb')

module Errapi::Validations
class Inclusion
class Inclusion < Base
include Clusivity

def initialize options = {}
@delimiter = options[:in] || options[:within]
check_delimiter! ":in (or :within)"
unless key = exactly_one_option?(OPTIONS, options)
raise ArgumentError, "Either :in or :within must be supplied (but not both)."
end

@delimiter = options[key]
check_delimiter! OPTIONS_DESCRIPTION
end

def validate value, context, options = {}
excluded_values = members options[:source]
unless include? excluded_values, value
context.add_error reason: :excluded, check_value: excluded_values
allowed_values = members OPTIONS_DESCRIPTION, options
unless include? allowed_values, value
context.add_error reason: :not_included, check_value: allowed_values
end
end

private

OPTIONS = %i(in within)
OPTIONS_DESCRIPTION = ":in (or :within)"
end
end
92 changes: 92 additions & 0 deletions spec/validations/exclusion_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
require 'helper'

RSpec.describe Errapi::Validations::Exclusion do
let(:context){ double add_error: nil }
let(:validation_options){ {} }
subject{ described_class.new validation_options }

it "should require at least one option to be set" do
expect{ described_class.new }.to raise_error(/either :from or :in or :within/i)
end

it "should not accept an object without the #include? method as an option" do
%i(in within).each do |option|
[ nil, true, false, Object.new ].each do |invalid_option|
expect{ described_class.new({ option => invalid_option }) }.to raise_error(/an object with the #include\? method/i)
end
end
end

describe "with a callable returning an invalid list" do
let(:validation_options){ { from: Proc.new{ :foo } } }

it "should raise an error when validating" do
expect{ validate 'foo' }.to raise_error(/must return an object with the #include\? method/i)
end
end

describe "with a symbol returning an invalid list" do
let(:validation_options){ { from: :excluded_values } }

it "should raise an error when validating" do
expect{ validate 'foo', source: OpenStruct.new(excluded_values: :foo) }.to raise_error(/must return an object with the #include\? method/i)
end
end

shared_examples_for "an exclusion validation" do
let(:runtime_options){ {} }
let(:validation_options){ { exclusion_option => %w(foo bar baz) } }

it "should not accept a value in the supplied list" do
%w(foo bar baz).each.with_index do |invalid_value,i|
validate invalid_value, runtime_options
expect(context).to have_received(:add_error).with(reason: :excluded, check_value: %w(foo bar baz)).exactly(i + 1).times
end
end

it "should accept a value not in the supplied list" do
%w(qux corge grault).each do |valid_value|
validate valid_value, runtime_options
expect(context).not_to have_received(:add_error)
end
end
end

shared_examples_for "a callable exclusion option" do
describe "as a list" do
let(:validation_options){ { from: %w(foo bar baz) } }
it_should_behave_like "an exclusion validation"
end

describe "as a callable" do
let(:validation_options){ { from: ->(source){ source.excluded_values } } }
let(:runtime_options){ { source: OpenStruct.new(excluded_values: %w(foo bar baz)) } }
it_should_behave_like "an exclusion validation"
end

describe "as a symbol" do
let(:validation_options){ { from: :excluded_values } }
let(:runtime_options){ { source: OpenStruct.new(excluded_values: %w(foo bar baz)) } }
it_should_behave_like "an exclusion validation"
end
end

describe "with the :from option" do
let(:exclusion_option){ :from }
it_should_behave_like "a callable exclusion option"
end

describe "with the :in option" do
let(:exclusion_option){ :in }
it_should_behave_like "a callable exclusion option"
end

describe "with the :within option" do
let(:exclusion_option){ :within }
it_should_behave_like "a callable exclusion option"
end

def validate value, options = {}
subject.validate value, context, options
end
end
87 changes: 87 additions & 0 deletions spec/validations/inclusion_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
require 'helper'

RSpec.describe Errapi::Validations::Inclusion do
let(:context){ double add_error: nil }
let(:validation_options){ {} }
subject{ described_class.new validation_options }

it "should require at least one option to be set" do
expect{ described_class.new }.to raise_error(/either :in or :within/i)
end

it "should not accept an object without the #include? method as an option" do
%i(in within).each do |option|
[ nil, true, false, Object.new ].each do |invalid_option|
expect{ described_class.new({ option => invalid_option }) }.to raise_error(/an object with the #include\? method/i)
end
end
end

describe "with a callable returning an invalid list" do
let(:validation_options){ { in: Proc.new{ :foo } } }

it "should raise an error when validating" do
expect{ validate 'foo' }.to raise_error(/must return an object with the #include\? method/i)
end
end

describe "with a symbol returning an invalid list" do
let(:validation_options){ { in: :allowed_values } }

it "should raise an error when validating" do
expect{ validate 'foo', source: OpenStruct.new(allowed_values: :foo) }.to raise_error(/must return an object with the #include\? method/i)
end
end

shared_examples_for "an inclusion validation" do
let(:runtime_options){ {} }
let(:validation_options){ { inclusion_option => %w(foo bar baz) } }

it "should not accept a value not in the supplied list" do
%w(qux corge grault).each.with_index do |invalid_value,i|
validate invalid_value, runtime_options
expect(context).to have_received(:add_error).with(reason: :not_included, check_value: %w(foo bar baz)).exactly(i + 1).times
end
end

it "should accept a value in the supplied list" do
%w(foo bar baz).each do |valid_value|
validate valid_value, runtime_options
expect(context).not_to have_received(:add_error)
end
end
end

shared_examples_for "a callable inclusion option" do
describe "as a list" do
let(:validation_options){ { in: %w(foo bar baz) } }
it_should_behave_like "an inclusion validation"
end

describe "as a callable" do
let(:validation_options){ { in: ->(source){ source.allowed_values } } }
let(:runtime_options){ { source: OpenStruct.new(allowed_values: %w(foo bar baz)) } }
it_should_behave_like "an inclusion validation"
end

describe "as a symbol" do
let(:validation_options){ { in: :allowed_values } }
let(:runtime_options){ { source: OpenStruct.new(allowed_values: %w(foo bar baz)) } }
it_should_behave_like "an inclusion validation"
end
end

describe "with the :in option" do
let(:inclusion_option){ :in }
it_should_behave_like "a callable inclusion option"
end

describe "with the :within option" do
let(:inclusion_option){ :within }
it_should_behave_like "a callable inclusion option"
end

def validate value, options = {}
subject.validate value, context, options
end
end

0 comments on commit ca351ae

Please sign in to comment.