1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

[tests] Changes to format testing helper

* Adds and documents Shindo::Tests#data_matches_schema
* Deprecates Shindo::Tests#format method
* Strict mode is replace by expandable options
* Uses Fog::Logger instead of p for debug output
* Shindo::Tests#formats_kernel removed (was private)
* Shindo::Tests#confirm_data_matches_schema added
* #confirm_data_matches_schema uses yield

Related to issue #1477

Shindo::Tests#formats incorrectly treated missing keys as valid if their
value is allowed to be "nullable" or NilClass. These keys are not
optional. They are required but can be the value nil or the Nullable
class.

Unfortunately due to lack of documentation and a bug in the code the
current Nullable classes can be interpreted as optional and work as such.

This replaces the old format checker with a new version that supports
options to control the matching behaviour related to extra keys in
either the data or the schema.

This corrects the behaviour so extra keys in either the schema or the
supplied data fail the check unless non strict has been used.

A legacy version of the #formats test is in place that passes the
options so it behaves as the old version does.

Premptive patches have been added to fix those tests that were already
broken but passed the original checks.
This commit is contained in:
Paul Thornthwaite 2013-01-16 15:44:56 +00:00
parent 3863cd38c8
commit 0793a42a64
2 changed files with 162 additions and 67 deletions

View file

@ -25,62 +25,144 @@ end
module Shindo module Shindo
class Tests class Tests
def formats(format, strict=true) # Generates a Shindo test that compares a hash schema to the result
raise ArgumentError, 'format is nil' unless format # of the passed in block returning true if they match.
#
# The schema that is passed in is a Hash or Array of hashes that
# have Classes in place of values. When checking the schema the
# value should match the Class.
#
# Strict mode will fail if the data has additional keys. Setting
# +strict+ to +false+ will allow additional keys to appear.
#
# @param [Hash] schmea A Hash schema
# @param [Hash] options Options to change validation rules
# @option options [Boolean] :allow_extra_keys
# If +true+ deoes not fail when keys are in the data that are
# not specified in the schema. This allows new values to
# appear in API output without breaking the check.
# @option options [Boolean] :allow_optional_rules
# If +true+ does not fail if extra keys are in the schema
# that do not match the data. Not recommended!
# @yield Data to check with schema
#
# @example Using in a test
# Shindo.tests("data matches schema") do
# data = {:string => "Hello" }
# data_matches_schema(:string => String) { data }
# end
#
# data matches schema
# + has proper format
#
# @example Example schema
# {
# "id" => String,
# "ram" => Integer,
# "disks" => [
# "size" => Float
# ],
# "dns_name" => Fog::Nullable::String,
# "active" => Fog::Boolean,
# "created" => DateTime
# }
#
# @return [Boolean]
def data_matches_schema(schema, options = {})
test('data matches schema') do
confirm_data_matches_schema(yield, schema, options)
end
end
# @deprecation #formats is deprecated. Use #data_matches_schema instead
def formats(format, strict = true)
test('has proper format') do test('has proper format') do
formats_kernel(instance_eval(&Proc.new), format, true, strict) if strict
options = {:allow_extra_keys => false, :allow_optional_rules => true}
else
options = {:allow_extra_keys => true, :allow_optional_rules => true}
end
confirm_data_matches_schema(yield, format, options)
end end
end end
private private
def formats_kernel(original_data, original_format, original = true, strict = true) # Checks if the data structure matches the schema passed in and
valid = true # returns true if it fits.
data = original_data.dup #
format = original_format.dup # @param [Object] data Hash or Array to check
if format.is_a?(Array) # @param [Object] schema Schema pattern to check against
data = {:element => data} # @param [Boolean] options
format = {:element => format} # @option options [Boolean] :allow_extra_keys
end # If +true+ does not fail if extra keys are in the data
for key, value in format # that are not in the schema. Allows
datum = data.delete(key) # @option options [Boolean] :allow_optional_rules
format.delete(key) # If +true+ does not fail if extra keys are in the schema
case value # that do not match the data. Not recommended!
when Array #
p("#{key.inspect} not Array: #{datum.inspect}") unless datum.is_a?(Array) # @return [Boolean] Did the data fit the schema?
valid &&= datum.is_a?(Array) def confirm_data_matches_schema(data, schema, options = {})
if datum.is_a?(Array) && !value.empty? # Clear message passed to the Shindo tests
for element in datum @message = nil
type = value.first
if type.is_a?(Hash) valid = validate_value(schema, data, options)
valid &&= formats_kernel({:element => element}, {:element => type}, false, strict)
else unless valid
valid &&= element.is_a?(type) @message = "#{data.inspect} does not match #{schema.inspect}"
end
end
end
when Hash
p("#{key.inspect} not Hash: #{datum.inspect}") unless datum.is_a?(Hash)
valid &&= datum.is_a?(Hash)
valid &&= formats_kernel(datum, value, false, strict)
else
p "#{key.inspect} not #{value.inspect}: #{datum.inspect}" unless datum.is_a?(value)
valid &&= datum.is_a?(value)
end
end
p data unless data.empty?
p format unless format.empty?
if strict
valid &&= data.empty? && format.empty?
else
valid &&= format.empty?
end
if !valid && original
@message = "#{original_data.inspect} does not match #{original_format.inspect}"
end end
valid valid
end end
# This contains a slightly modified version of the Hashidator gem
# but unfortunately the gem does not cope with Array schemas.
#
# @see https://github.com/vangberg/hashidator/blob/master/lib/hashidator.rb
#
def validate_value(validator, value, options)
Fog::Logger.write :debug, "[yellow][DEBUG] #{value.inspect} against #{validator.inspect}[/]\n"
# When being strict values not specified in the schema are fails
unless options[:allow_extra_keys]
if validator.respond_to?(:empty?) && value.respond_to?(:empty?)
# Validator is empty but values are not
return false if !value.empty? && validator.empty?
end
end
unless options[:allow_optional_rules]
if validator.respond_to?(:empty?) && value.respond_to?(:empty?)
# Validator has rules left but no more values
return false if value.empty? && !validator.empty?
end
end
case validator
when Array
return false if value.is_a?(Hash)
value.respond_to?(:all?) && value.all? {|x| validate_value(validator[0], x, options)}
when Symbol
value.respond_to? validator
when Hash
return false if value.is_a?(Array)
validator.all? do |key, sub_validator|
Fog::Logger.write :debug, "[blue][DEBUG] #{key.inspect} against #{sub_validator.inspect}[/]\n"
validate_value(sub_validator, value[key], options)
end
else
result = validator == value
result = validator === value unless result
# Repeat unless we have a Boolean already
unless (result.is_a?(TrueClass) || result.is_a?(FalseClass))
result = validate_value(result, value, options)
end
if result
Fog::Logger.write :debug, "[green][DEBUG] Validation passed: #{value.inspect} against #{validator.inspect}[/]\n"
else
Fog::Logger.write :debug, "[red][DEBUG] Validation failed: #{value.inspect} against #{validator.inspect}[/]\n"
end
result
end
end
end end
end end

View file

@ -52,56 +52,61 @@ Shindo.tests('test_helper', 'meta') do
end end
tests('#formats_kernel') do tests('data matches schema') do
data = {:welcome => "Hello" }
data_matches_schema(:welcome => String) { data }
end
tests('#confirm_data_matches_schema') do
tests('returns true') do tests('returns true') do
returns(true, 'when value matches schema expectation') do returns(true, 'when value matches schema expectation') do
formats_kernel({"key" => "Value"}, {"key" => String}) confirm_data_matches_schema({"key" => "Value"}, {"key" => String})
end end
returns(true, 'when values within an array all match schema expectation') do returns(true, 'when values within an array all match schema expectation') do
formats_kernel({"key" => [1, 2]}, {"key" => [Integer]}) confirm_data_matches_schema({"key" => [1, 2]}, {"key" => [Integer]})
end end
returns(true, 'when nested values match schema expectation') do returns(true, 'when nested values match schema expectation') do
formats_kernel({"key" => {:nested_key => "Value"}}, {"key" => {:nested_key => String}}) confirm_data_matches_schema({"key" => {:nested_key => "Value"}}, {"key" => {:nested_key => String}})
end end
returns(true, 'when collection of values all match schema expectation') do returns(true, 'when collection of values all match schema expectation') do
formats_kernel([{"key" => "Value"}, {"key" => "Value"}], [{"key" => String}]) confirm_data_matches_schema([{"key" => "Value"}, {"key" => "Value"}], [{"key" => String}])
end end
returns(true, 'when collection is empty although schema covers optional members') do returns(true, 'when collection is empty although schema covers optional members') do
formats_kernel([], [{"key" => String}]) confirm_data_matches_schema([], [{"key" => String}], {:allow_optional_rules => true})
end end
returns(true, 'when additional keys are passed and not strict') do returns(true, 'when additional keys are passed and not strict') do
formats_kernel({"key" => "Value", :extra => "Bonus"}, {"key" => String}, true, false) confirm_data_matches_schema({"key" => "Value", :extra => "Bonus"}, {"key" => String}, {:allow_extra_keys => true})
end end
returns(true, 'when value is nil and schema expects NilClass') do returns(true, 'when value is nil and schema expects NilClass') do
formats_kernel({"key" => nil}, {"key" => NilClass}) confirm_data_matches_schema({"key" => nil}, {"key" => NilClass})
end end
returns(true, 'when value and schema match as hashes') do returns(true, 'when value and schema match as hashes') do
formats_kernel({}, {}) confirm_data_matches_schema({}, {})
end end
returns(true, 'when value and schema match as arrays') do returns(true, 'when value and schema match as arrays') do
formats_kernel([], []) confirm_data_matches_schema([], [])
end end
returns(true, 'when value is a Time') do returns(true, 'when value is a Time') do
formats_kernel({"time" => Time.now}, {"time" => Time}) confirm_data_matches_schema({"time" => Time.now}, {"time" => Time})
end end
returns(true, 'when key is missing but value should be NilClass (#1477)') do returns(true, 'when key is missing but value should be NilClass (#1477)') do
formats_kernel({}, {"key" => NilClass}) confirm_data_matches_schema({}, {"key" => NilClass}, {:allow_optional_rules => true})
end end
returns(true, 'when key is missing but value is nullable (#1477)') do returns(true, 'when key is missing but value is nullable (#1477)') do
formats_kernel({}, {"key" => Fog::Nullable::String}) confirm_data_matches_schema({}, {"key" => Fog::Nullable::String}, {:allow_optional_rules => true})
end end
end end
@ -109,35 +114,43 @@ Shindo.tests('test_helper', 'meta') do
tests('returns false') do tests('returns false') do
returns(false, 'when value does not match schema expectation') do returns(false, 'when value does not match schema expectation') do
formats_kernel({"key" => nil}, {"key" => String}) confirm_data_matches_schema({"key" => nil}, {"key" => String})
end end
returns(false, 'when key formats do not match') do returns(false, 'when key formats do not match') do
formats_kernel({"key" => "Value"}, {:key => String}) confirm_data_matches_schema({"key" => "Value"}, {:key => String})
end end
returns(false, 'when additional keys are passed and strict') do returns(false, 'when additional keys are passed and strict') do
formats_kernel({"key" => "Missing"}, {}) confirm_data_matches_schema({"key" => "Missing"}, {})
end end
returns(false, 'when some keys do not appear') do returns(false, 'when some keys do not appear') do
formats_kernel({}, {"key" => String}) confirm_data_matches_schema({}, {"key" => String})
end end
returns(false, 'when collection contains a member that does not match schema') do returns(false, 'when collection contains a member that does not match schema') do
formats_kernel([{"key" => "Value"}, {"key" => 5}], [{"key" => String}]) confirm_data_matches_schema([{"key" => "Value"}, {"key" => 5}], [{"key" => String}])
end end
returns(false, 'when hash and array are compared') do returns(false, 'when hash and array are compared') do
formats_kernel({}, []) confirm_data_matches_schema({}, [])
end end
returns(false, 'when array and hash are compared') do returns(false, 'when array and hash are compared') do
formats_kernel([], {}) confirm_data_matches_schema([], {})
end end
returns(false, 'when a hash is expected but another data type is found') do returns(false, 'when a hash is expected but another data type is found') do
formats_kernel({"key" => {:nested_key => []}}, {"key" => {:nested_key => {}}}) confirm_data_matches_schema({"key" => {:nested_key => []}}, {"key" => {:nested_key => {}}})
end
returns(false, 'when key is missing but value should be NilClass (#1477)') do
confirm_data_matches_schema({}, {"key" => NilClass}, {:allow_optional_rules => false})
end
returns(false, 'when key is missing but value is nullable (#1477)') do
confirm_data_matches_schema({}, {"key" => Fog::Nullable::String}, {:allow_optional_rules => false})
end end
end end