Skip to content
This repository has been archived by the owner on Jan 2, 2018. It is now read-only.

Commit

Permalink
Refactored JSONResponseVerification, adding support for top-level arrays
Browse files Browse the repository at this point in the history
JSONResponseVerification#verify replaces JSONResponseVerification#validate_hash_response (which is still available for backguard compatibility).
  • Loading branch information
raul committed Jun 2, 2013
1 parent 30e9191 commit d77510b
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 99 deletions.
187 changes: 108 additions & 79 deletions lib/json_response_verification.rb
Original file line number Diff line number Diff line change
@@ -1,109 +1,138 @@
# include this module in WeaselDiesel
# Include this module in WeaselDiesel
# to add response verification methods.
#
module JSONResponseVerification

# Validates a hash against the service's response description.
# Verifies the parsed body of a JSON response against the service's response description.
#
# @return [Array<TrueClass, FalseClass, Array<String>>] True/false and an array of errors.
def validate_hash_response(hash)
errors = []
# nodes without the arrays
response.nodes.each do |node|
if node.name
# Verify that the named node exists in the hash
unless hash.has_key?(node.name.to_s)
errors << json_response_error(node, hash)
return [false, errors]
end
end
errors += validate_hash_against_template_node(hash, node)
end

def verify(parsed_json_body)
errors = [verify_element(parsed_json_body, response.nodes.first)]
errors.flatten!
[errors.empty?, errors]
end

alias :validate_hash_response :verify # backguard compatibility

private

# Recursively validates a hash representing a json response.
# Recursively validates an element found when parsing a JSON.
#
# @param [Hash>] hash the hash to verify.
# @param [WDSL::Response::Element] node the reference element defined in the response description.
# @param [TrueClass, FalseClass] nested if the node/hash to verify is nested or not. If nested, the method expects to get the subhash
# & won't verify that the name exists since it was done a level higher.
# @param [Arrays<String>] errors the list of errors encountered while verifying.
# @param []
# @return [TrueClass, FalseClass]
def validate_hash_against_template_node(hash, node, nested=false, errors=[], array_item=false)
if hash.nil?
errors << json_response_error(node, hash)
return errors
end

if node.name && !nested
if hash.has_key?(node.name.to_s)
subhash = hash[node.name.to_s]
# @param [Hash, Array, nil] el parsed JSON to be verified.
# @param [WDSL::Response::Element] expected the reference element defined in the response description.
# @param [TrueClass, FalseClass] verify_namespace if the nesting must be verified.
# @return [Arrays<String>] errors the list of errors encountered while verifying.
def verify_element(el, expected, verify_namespace=true)
if expected.name && verify_namespace
if verified_namespace?(el, expected.name)
el = el[expected.name.to_s]
verify_namespace = false
else
errors << json_response_error(node, hash)
return something_is_missing_error(expected)
end
else
verify_namespace = true
end

subhash ||= hash
if node.is_a?(WeaselDiesel::Response::Vector) && !array_item
errors << json_response_error(node, subhash, true) unless subhash.is_a?(Array)
subhash.each do |obj|
validate_hash_against_template_node(obj, node, true, errors, true)
end
if el.nil?
something_is_missing_error(expected)
elsif el.is_a?(Array)
verify_array(el, expected, verify_namespace)
else
node.properties.each do |prop|
if !array_item && !subhash.has_key?(prop.name.to_s) && (prop.opts && prop.respond_to?(:opts) && !prop.opts[:null])
errors << json_response_error(prop, subhash)
end
errors << json_response_error(prop, subhash, true) unless valid_hash_type?(subhash, prop)
end

node.objects.each do |obj|
# recursive call
validate_hash_against_template_node(subhash[obj.name.to_s], obj, true, errors)
end if node.objects
verify_object(el, expected, verify_namespace)
end
end

errors
# Verifies hash corresponding to a JSON response against a given namespace
#
# @param [Array] array array to be verified.
# @param [WDSL::Response::Element] expected the reference element defined in the response description.
# @return [TrueClass, FalseClass] if the nesting name found is correct.
def verified_namespace?(hash, expected_name)
hash.respond_to?(:has_key?) && hash.has_key?(expected_name.to_s)
end

# Validates an array found when parsing a JSON.
#
# @param [Array] array array to be verified.
# @param [WDSL::Response::Element] expected the reference element defined in the response description.
# @return [Arrays<String>] errors the list of errors encountered while verifying.
def verify_array(array, expected, verify_nesting)
return wrong_type_error(array, expected.name, expected.type) unless expected.is_a?(WeaselDiesel::Response::Vector)
expected = expected.elements && expected.elements.any? ? expected.elements.first : expected
array.map{ |el| verify_element(el, expected, verify_nesting) }
end

def json_response_error(el_or_attr, hash, type_error=false)
if el_or_attr.is_a?(WeaselDiesel::Response::Element)
"#{el_or_attr.name || 'top level'} Node/Object/Element is missing"
elsif type_error
error = "#{el_or_attr.name || el_or_attr.inspect} was of wrong type, expected #{el_or_attr.type}"
if el_or_attr.name
error << " and the value was: #{hash[el_or_attr.name.to_s]}"
# Validates a hash corresponding to a JSON object.
#
# @param [Hash] hash hash to be verified.
# @param [WDSL::Response::Element] expected the reference element defined in the response description.
# @return [Arrays<String>] errors the list of errors encountered while verifying.
def verify_object(hash, expected, verify_nesting)
[verify_attributes(hash, expected)] + [verify_objects(hash, expected)]
end

# Validates the objects found in a hash corresponding to a JSON object.
#
# @param [Hash] hash hash representing a JSON object whose internal objects will be verified.
# @param [WDSL::Response::Element] expected the reference element defined in the response description.
# @return [Arrays<String>] errors the list of errors encountered while verifying.
def verify_objects(hash, expected)
return [] unless expected.objects
expected.objects.map do |expected|
found = hash[expected.name.to_s]
null_allowed = expected.respond_to?(:opts) && expected.opts[:null]
if found.nil?
null_allowed ? [] : something_is_missing_error(expected)
else
verify_element(found, expected, false) # don't verify nesting
end
error
else
"#{el_or_attr.name || el_or_attr.inspect} is missing in #{hash.inspect}"
end
end

def valid_hash_type?(hash, prop_template)
name = prop_template.name.to_s
attribute = hash[name]
# Validates the attributes found in a hash corresponding to a JSON object.
#
# @param [Hash] hash hash whose attributes will be verified.
# @param [WDSL::Response::Element] expected the reference element defined in the response description.
# @return [Arrays<String>] errors the list of errors encountered while verifying.
def verify_attributes(hash, expected)
return [] unless expected.attributes
expected.attributes.map{ |a| verify_attribute_value(hash[a.name.to_s], a) }
end

# Check for nullity
if attribute.nil?
return prop_template.opts[:null] == true
# Validates a value against a found in a hash corresponding to a JSON object.
#
# @param [value] value value to be verified.
# @param [WDSL::Response::Attribute] expected the reference element defined in the response description.
# @return [Arrays<String>] errors the list of errors encountered while verifying.
def verify_attribute_value(value, attribute)
null_allowed = attribute.respond_to?(:opts) && !!attribute.opts[:null]
if value.nil?
null_allowed ? [] : wrong_type_error(value, attribute.name, attribute.type)
else
type = attribute.type
return [] if type.to_sym == :string
rule = ParamsVerification.type_validations[attribute.type.to_sym]
puts "Don't know how to validate attributes of type #{type}" if rule.nil?
(rule.nil? || value.to_s =~ rule) ? [] : wrong_type_error(value, attribute.name, attribute.type)
end

type = prop_template.type
return true if type.nil?
end

rule = ParamsVerification.type_validations[type.to_sym]
if rule.nil?
puts "Don't know how to validate attributes of type #{type}" if type.to_sym != :string
return true
end
# Returns an error message reporting that an expected data hasn't been found in the JSON response.
#
# @param [WDSL::Response::Element, WDSL::Response::Attribute] expected missing data.
# @return [String] error message
def something_is_missing_error(expected)
"#{expected.name || 'top level'} Node/Object/Element is missing"
end

attribute.to_s =~ rule
# Returns an error message reporting that a value doesn't correspond to an expected data type.
#
# @param [value] value which doesn't correspond to the expected type.
# @param [data_name] data_name name of the data containing the value.
# @param [expected_type] expected type.
# @return [String] error message
def wrong_type_error(value, data_name, expected_type)
"#{data_name} was of wrong type, expected #{expected_type} and the value was #{value}"
end

end
end
36 changes: 16 additions & 20 deletions spec/json_response_verification_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,11 @@ def valid_nested_array_response
end

def valid_top_level_array_response
[ { :name => "Bob" }, { :name => "Judy" } ]
[ { "name" => "Bob" }, { "name" => "Judy" } ]
end


it "should validate the response" do
valid, errors = @service.validate_hash_response(valid_response)
valid, errors = @service.verify(valid_response)
errors.should == []
valid.should be_true
errors.should be_empty
Expand All @@ -147,15 +146,15 @@ def valid_top_level_array_response
it "should detect that the response is missing the top level object" do
response = valid_response
response.delete("user")
valid, errors = @service.validate_hash_response(response)
valid, errors = @service.verify(response)
valid.should be_false
errors.should_not be_empty
end

it "should detect that a property integer type is wrong" do
response = valid_response
response["user"]["id"] = 'test'
valid, errors = @service.validate_hash_response(response)
valid, errors = @service.verify(response)
valid.should be_false
errors.should_not be_empty
errors.first.should match(/id/)
Expand All @@ -165,7 +164,7 @@ def valid_top_level_array_response
it "should detect that an integer attribute value is nil" do
response = valid_response
response["user"]["id"] = nil
valid, errors = @service.validate_hash_response(response)
valid, errors = @service.verify(response)
valid.should be_false
errors.should_not be_empty
errors.first.should match(/id/)
Expand All @@ -175,77 +174,74 @@ def valid_top_level_array_response
it "should detect that a string attribute value is nil [bug]" do
response = valid_response
response["user"]["name"] = nil
valid, errors = @service.validate_hash_response(response)
valid, errors = @service.verify(response)
valid.should be_false
errors.should_not be_empty
errors.first.should match(/name/)
errors.first.should match(/wrong type/)
end


it "should detect that a nested object is missing" do
response = valid_response
response["user"].delete("creds")
valid, errors = @service.validate_hash_response(response)
valid, errors = @service.verify(response)
valid.should be_false
errors.first.should match(/creds/)
errors.first.should match(/missing/)
end

it "should validate non namespaced responses" do
valid, errors = @second_service.validate_hash_response(valid_response(false))
valid, errors = @second_service.verify(valid_response(false))
valid.should be_true
end

it "should validate nil attributes if marked as nullable" do
response = valid_response(false)
response["name"] = nil
valid, errors = @second_service.validate_hash_response(response)
valid, errors = @second_service.verify(response)
valid.should be_true
end


it "should validate array items" do
valid, errors = @third_service.validate_hash_response(valid_array_response)
valid, errors = @third_service.verify(valid_array_response)
valid.should be_true
errors.should be_empty
end

it "should validate an empty array" do
response = valid_array_response
response["users"] = []
valid, errors = @third_service.validate_hash_response(response)
valid, errors = @third_service.verify(response)
valid.should be_true
end

it "should catch error in an array item" do
response = valid_array_response
response["users"][1]["id"] = 'test'
valid, errors = @third_service.validate_hash_response(response)
valid, errors = @third_service.verify(response)
valid.should be_false
errors.should_not be_empty
end

it "should validate nested arrays" do
valid, errors = @forth_service.validate_hash_response(valid_nested_array_response)
valid, errors = @forth_service.verify(valid_nested_array_response)
valid.should be_true
end


it "should respect optional properties" do
valid, errors = @optional_prop_service.validate_hash_response({})
valid, errors = @optional_prop_service.verify({})
valid.should be_true
end

it "should validate the response" do
valid, errors = @service.validate_hash_response(valid_response)
valid, errors = @service.verify(valid_response)
errors.should == []
valid.should be_true
errors.should be_empty
end

it "should validated a top level array" do
valid, errors = @top_level_array_service.validate_hash_response(valid_top_level_array_response)
valid, errors = @top_level_array_service.verify(valid_top_level_array_response)
errors.should == []
valid.should be_true
errors.should be_empty
Expand Down

0 comments on commit d77510b

Please sign in to comment.