lib/airborne/request_expectations.rb in airborne-0.1.20 vs lib/airborne/request_expectations.rb in airborne-0.2.0

- old
+ new

@@ -65,81 +65,109 @@ expect(header.downcase).to include(content.downcase) else expect(header.downcase).to eq(content.downcase) end else - fail RSpec::Expectations::ExpectationNotMetError, "Header #{key} not present in HTTP response" + fail RSpec::Expectations::ExpectationNotMetError, "Header #{key} not present in the HTTP response" end end - def call_with_path(args) - if args.length == 2 - get_by_path(args[0], json_body) do|json_chunk| - yield(args[1], json_chunk) - end - else - yield(args[0], json_body) - end - end + def expect_json_impl(expected, actual) + return if nil_optional_hash?(expected, actual) - def get_mapper - base_mapper = { - integer: [Fixnum, Bignum], - array_of_integers: [Fixnum, Bignum], - int: [Fixnum, Bignum], - array_of_ints: [Fixnum, Bignum], - float: [Float, Fixnum, Bignum], - array_of_floats: [Float, Fixnum, Bignum], - string: [String], - array_of_strings: [String], - boolean: [TrueClass, FalseClass], - array_of_booleans: [TrueClass, FalseClass], - bool: [TrueClass, FalseClass], - array_of_bools: [TrueClass, FalseClass], - object: [Hash], - array_of_objects: [Hash], - array: [Array], - array_of_arrays: [Array], - date: [DateTime], - null: [NilClass] - } + actual = actual.to_s if expected.class == Regexp - mapper = base_mapper.clone - base_mapper.each do |key, value| - mapper[(key.to_s + '_or_null').to_sym] = value + [NilClass] + return expect(actual).to match(expected) if property?(expected) + + keys = [] + + keys << expected.keys if match_expected? + keys << actual.keys if match_actual? + keys = expected.keys & actual.keys if match_none? + + keys.flatten.uniq.each do |prop| + expected_value = extract_expected(expected, prop) + actual_value = extract_actual(actual, prop) + + next expect_json_impl(expected_value, actual_value) if hash?(expected_value) + next expected_value.call(actual_value) if expected_value.is_a?(Proc) + next expect(actual_value.to_s).to match(expected_value) if expected_value.is_a?(Regexp) + + expect(actual_value).to eq(expected_value) end - mapper end - def expect_json_types_impl(expectations, hash_or_value) - return if nil_optional_hash?(expectations, hash_or_value) + def expect_json_types_impl(expected, actual) + return if nil_optional_hash?(expected, actual) @mapper ||= get_mapper - hash_or_value = convert_to_date(hash_or_value) if expectations == :date + actual = convert_to_date(actual) if expected == :date - return expect_type(expectations, hash_or_value.class) if expectations.class == Symbol - return expectations.call(hash_or_value) if expectations.class == Proc + return expect_type(expected, actual.class) if expected.is_a?(Symbol) + return expected.call(actual) if expected.is_a?(Proc) - expectations.each do |prop_name, expected_type| - value = ensure_hash_contains_prop(prop_name, hash_or_value) do - expected_type == :date ? convert_to_date(hash_or_value[prop_name]) : hash_or_value[prop_name] - end - expected_class = expected_type.class - value_class = value.class + keys = [] - next expect_json_types_impl(expected_type, value) if hash?(expected_class) - next expected_type.call(value) if expected_class == Proc + keys << expected.keys if match_expected? + keys << actual.keys if match_actual? + keys = expected.keys & actual.keys if match_none? - if expected_type.to_s.include?('array_of') - check_array_types(value, value_class, prop_name, expected_type) + keys.flatten.uniq.each do |prop| + type = extract_expected(expected, prop) + value = extract_actual(actual, prop) + value = convert_to_date(value) if type == :date + + next expect_json_types_impl(type, value) if hash?(type) + next type.call(value) if type.is_a?(Proc) + + val_class = value.class + + if type.to_s.include?('array_of') + check_array_types(value, val_class, prop, type) else - expect_type(expected_type, value_class, prop_name) + expect_type(type, val_class, prop) end end end + def call_with_path(args) + if args.length == 2 + get_by_path(args[0], json_body) do |json_chunk| + yield(args[1], json_chunk) + end + else + yield(args[0], json_body) + end + end + + def extract_expected(expected, prop) + begin + type = expected[prop] + type.nil? ? raise : type + rescue + raise ExpectationError, "Expectation is expected to contain property: #{prop}" + end + end + + def extract_actual(actual, prop) + begin + value = actual[prop] + rescue + raise ExpectationError, "Expected #{actual.class} #{actual}\nto be an object with property #{prop}" + end + end + + def expect_type(expected_type, value_class, prop_name = nil) + fail ExpectationError, "Expected type #{expected_type}\nis an invalid type" if @mapper[expected_type].nil? + + insert = prop_name.nil? ? '' : "#{prop_name} to be of type" + message = "Expected #{insert} #{expected_type}\n got #{value_class} instead" + + expect(@mapper[expected_type].include?(value_class)).to eq(true), message + end + def convert_to_date(value) begin DateTime.parse(value) rescue end @@ -150,50 +178,22 @@ value.each do |val| expect_type(expected_type, val.class, prop_name) end end - def nil_optional_hash?(expectations, hash) - expectations.class == Airborne::OptionalHashTypeExpectations && hash.nil? + def nil_optional_hash?(expected, hash) + expected.is_a?(Airborne::OptionalHashTypeExpectations) && hash.nil? end - def expect_type(expected_type, value_class, prop_name = nil) - insert = prop_name.nil? ? '' : "#{prop_name} to be of type" - msg = "Expected #{insert} #{expected_type}\n got #{value_class} instead" - fail ExpectationError, "Expected type #{expected_type}\nis an invalid type" if @mapper[expected_type].nil? - expect(@mapper[expected_type].include?(value_class)).to eq(true), msg + def hash?(hash) + hash.is_a?(Hash) || hash.is_a?(Airborne::OptionalHashTypeExpectations) end - def hash?(expected_class) - expected_class == Hash || expected_class == Airborne::OptionalHashTypeExpectations - end - def expect_array(value_class, prop_name, expected_type) expect(value_class).to eq(Array), "Expected #{prop_name}\n to be of type #{expected_type}\n got #{value_class} instead" end - def expect_json_impl(expectations, hash) - hash = hash.to_s if expectations.class == Regexp - return expect(hash).to match(expectations) if property?(expectations) - expectations.each do |prop_name, expected_value| - actual_value = ensure_hash_contains_prop(prop_name, hash) { hash[prop_name] } - expected_class = expected_value.class - next expect(actual_value).to match(expected_value) if expected_class == Hash - next expected_value.call(actual_value) if expected_class == Proc - next expect(actual_value.to_s).to match(expected_value) if expected_class == Regexp - expect(actual_value).to eq(expected_value) - end - end - - def ensure_hash_contains_prop(prop_name, hash) - begin - yield - rescue - raise ExpectationError, "Expected #{hash.class} #{hash}\nto be an object with property #{prop_name}" - end - end - def convert_expectations_for_json_sizes(old_expectations) unless old_expectations.is_a?(Hash) return convert_expectation_for_json_sizes(old_expectations) end @@ -209,19 +209,68 @@ def convert_expectation_for_json_sizes(expected_size) ->(data) { expect(data.size).to eq(expected_size) } end + def ensure_hash_contains_prop(prop_name, hash) + begin + yield + rescue + raise ExpectationError, "Expected #{hash.class} #{hash}\nto be an object with property #{prop_name}" + end + end + def property?(expectations) [String, Regexp, Float, Fixnum, Bignum, TrueClass, FalseClass, NilClass].include?(expectations.class) end + def get_mapper + base_mapper = { + integer: [Fixnum, Bignum], + array_of_integers: [Fixnum, Bignum], + int: [Fixnum, Bignum], + array_of_ints: [Fixnum, Bignum], + float: [Float, Fixnum, Bignum], + array_of_floats: [Float, Fixnum, Bignum], + string: [String], + array_of_strings: [String], + boolean: [TrueClass, FalseClass], + array_of_booleans: [TrueClass, FalseClass], + bool: [TrueClass, FalseClass], + array_of_bools: [TrueClass, FalseClass], + object: [Hash], + array_of_objects: [Hash], + array: [Array], + array_of_arrays: [Array], + date: [DateTime], + null: [NilClass] + } + + mapper = base_mapper.clone + base_mapper.each do |key, value| + mapper[(key.to_s + '_or_null').to_sym] = value + [NilClass] + end + mapper + end + def resolve_status(candidate, authority) candidate = Rack::Utils::SYMBOL_TO_STATUS_CODE[candidate] if candidate.is_a?(Symbol) case authority when String then candidate.to_s when Fixnum then candidate.to_i else candidate end + end + + def match_none? + !match_actual? && !match_expected? + end + + def match_actual? + Airborne.configuration.match_actual? + end + + def match_expected? + Airborne.configuration.match_expected? end end end