rspec-json_api 1.3.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitattributes +28 -0
- data/.github/workflows/main.yml +39 -14
- data/.gitignore +6 -0
- data/.rubocop.yml +29 -1
- data/CHANGELOG.md +50 -0
- data/Gemfile +6 -5
- data/Gemfile.lock +143 -174
- data/README.md +11 -11
- data/gemfiles/rails_6_1.gemfile +7 -0
- data/gemfiles/rails_7_1.gemfile +7 -0
- data/gemfiles/rails_7_2.gemfile +7 -0
- data/gemfiles/rails_8_0.gemfile +7 -0
- data/gemfiles/rails_8_1.gemfile +7 -0
- data/lib/generators/rspec/json_api/interface/interface_generator.rb +3 -1
- data/lib/generators/rspec/json_api/type/type_generator.rb +3 -1
- data/lib/rspec/json_api/constraints.rb +56 -0
- data/lib/rspec/json_api/matchers/match_json_schema.rb +14 -11
- data/lib/rspec/json_api/schema_match.rb +135 -0
- data/lib/rspec/json_api/traversal.rb +49 -0
- data/lib/rspec/json_api/types/uuid.rb +1 -1
- data/lib/rspec/json_api/version.rb +1 -1
- data/lib/rspec/json_api.rb +5 -7
- data/rspec-json_api.gemspec +8 -3
- metadata +15 -14
- data/lib/extentions/array.rb +0 -8
- data/lib/extentions/hash.rb +0 -36
- data/lib/generators/rspec/json_api/interface/templates/interface.erb +0 -9
- data/lib/rspec/json_api/compare_array.rb +0 -39
- data/lib/rspec/json_api/compare_hash.rb +0 -114
|
@@ -8,12 +8,14 @@ module Rspec
|
|
|
8
8
|
|
|
9
9
|
def copy_interface_file
|
|
10
10
|
create_file "spec/rspec/json_api/interfaces/#{file_name}.rb", <<~FILE
|
|
11
|
+
# frozen_string_literal: true
|
|
12
|
+
|
|
11
13
|
module RSpec
|
|
12
14
|
module JsonApi
|
|
13
15
|
module Interfaces
|
|
14
16
|
#{file_name.upcase} = {
|
|
15
17
|
# name: String
|
|
16
|
-
}
|
|
18
|
+
}.freeze
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -6,8 +6,10 @@ module Rspec
|
|
|
6
6
|
class TypeGenerator < Rails::Generators::NamedBase
|
|
7
7
|
source_root File.expand_path("templates", __dir__)
|
|
8
8
|
|
|
9
|
-
def
|
|
9
|
+
def copy_type_file
|
|
10
10
|
create_file "spec/rspec/json_api/types/#{file_name}.rb", <<~FILE
|
|
11
|
+
# frozen_string_literal: true
|
|
12
|
+
|
|
11
13
|
module RSpec
|
|
12
14
|
module JsonApi
|
|
13
15
|
module Types
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module JsonApi
|
|
5
|
+
# Constraints evaluates the option hash produced by a schema Proc
|
|
6
|
+
# (e.g. `-> { { type: Integer, min: 1, max: 10, allow_blank: true } }`)
|
|
7
|
+
# against an actual value.
|
|
8
|
+
#
|
|
9
|
+
# allow_blank is a modifier, not a constraint of its own: when it is true a
|
|
10
|
+
# blank value is accepted and the remaining options are skipped; otherwise
|
|
11
|
+
# the value is checked against every other option.
|
|
12
|
+
module Constraints
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
SUPPORTED_OPTIONS = %i[allow_blank type value min max inclusion regex lambda].freeze
|
|
16
|
+
|
|
17
|
+
# @param value [Object] the actual value being matched.
|
|
18
|
+
# @param options [Hash] the option hash returned by the schema Proc.
|
|
19
|
+
# @return [Boolean] true when the value satisfies the options.
|
|
20
|
+
# @raise [ArgumentError] when an option key is not supported.
|
|
21
|
+
def match(value, options)
|
|
22
|
+
validate!(options)
|
|
23
|
+
|
|
24
|
+
return true if value.blank? && options[:allow_blank]
|
|
25
|
+
|
|
26
|
+
options.except(:allow_blank).all? do |option, condition|
|
|
27
|
+
satisfies?(value, option, condition)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validate!(options)
|
|
32
|
+
unknown = options.keys - SUPPORTED_OPTIONS
|
|
33
|
+
return if unknown.empty?
|
|
34
|
+
|
|
35
|
+
raise ArgumentError, "Unsupported match option(s): #{unknown.join(", ")}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def satisfies?(value, option, condition)
|
|
39
|
+
case option
|
|
40
|
+
when :type then value.instance_of?(condition)
|
|
41
|
+
when :value then value == condition
|
|
42
|
+
when :inclusion then condition.include?(value)
|
|
43
|
+
when :regex then condition.match?(value.to_s)
|
|
44
|
+
when :lambda then condition.call(value)
|
|
45
|
+
when :min, :max then within_bound?(value, option, condition)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def within_bound?(value, option, condition)
|
|
50
|
+
return false unless value.is_a?(Numeric) && condition.is_a?(Numeric)
|
|
51
|
+
|
|
52
|
+
option == :min ? value >= condition : value <= condition
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -23,18 +23,13 @@ module RSpec
|
|
|
23
23
|
# @param actual [String] The JSON string to test against the expected schema.
|
|
24
24
|
# @return [Boolean] true if the actual JSON matches the expected schema, false otherwise.
|
|
25
25
|
def matches?(actual)
|
|
26
|
+
@diff = nil
|
|
26
27
|
@actual = JSON.parse(actual, symbolize_names: true)
|
|
27
|
-
@diff = Diffy::Diff.new(expected, @actual, context: 5)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
else
|
|
34
|
-
return false unless @actual.deep_keys.deep_sort == expected.deep_keys.deep_sort
|
|
35
|
-
|
|
36
|
-
RSpec::JsonApi::CompareHash.compare(@actual, expected)
|
|
37
|
-
end
|
|
29
|
+
RSpec::JsonApi::SchemaMatch.match(@actual, expected)
|
|
30
|
+
rescue JSON::ParserError
|
|
31
|
+
@actual = actual
|
|
32
|
+
false
|
|
38
33
|
end
|
|
39
34
|
|
|
40
35
|
# Provides a failure message for when the JSON data does not match the expected schema.
|
|
@@ -45,7 +40,7 @@ module RSpec
|
|
|
45
40
|
got: #{actual}
|
|
46
41
|
|
|
47
42
|
Diff:
|
|
48
|
-
#{
|
|
43
|
+
#{diff}
|
|
49
44
|
MSG
|
|
50
45
|
end
|
|
51
46
|
|
|
@@ -55,6 +50,14 @@ module RSpec
|
|
|
55
50
|
def failure_message_when_negated
|
|
56
51
|
"expected the JSON data not to match the provided schema, but it did."
|
|
57
52
|
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# The diff is only needed to render a failure message, so it is built
|
|
57
|
+
# lazily and memoized rather than on every matches? call.
|
|
58
|
+
def diff
|
|
59
|
+
@diff ||= Diffy::Diff.new(expected, actual, context: 5)
|
|
60
|
+
end
|
|
58
61
|
end
|
|
59
62
|
end
|
|
60
63
|
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module JsonApi
|
|
5
|
+
# SchemaMatch compares parsed JSON (a Hash, an Array, or a scalar) against an
|
|
6
|
+
# expected schema. It is the single entry point behind the match_json_schema
|
|
7
|
+
# matcher: callers hand it the actual and expected values and it dispatches on
|
|
8
|
+
# shape internally, so the matcher does not need to know whether it is looking
|
|
9
|
+
# at an object or a collection.
|
|
10
|
+
module SchemaMatch
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# Top-level comparison. Applies the shape guards (class equality and, for
|
|
14
|
+
# objects, key-set equality) before recursing.
|
|
15
|
+
def match(actual, expected)
|
|
16
|
+
return false unless actual.instance_of?(expected.class)
|
|
17
|
+
|
|
18
|
+
case expected
|
|
19
|
+
when Array
|
|
20
|
+
compare_array(actual, expected)
|
|
21
|
+
when Hash
|
|
22
|
+
return false unless same_key_structure?(actual, expected)
|
|
23
|
+
|
|
24
|
+
compare(actual, expected)
|
|
25
|
+
else
|
|
26
|
+
compare_simple_value(actual, expected)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def same_key_structure?(actual, expected)
|
|
31
|
+
Traversal.deep_sort(Traversal.deep_keys(actual)) ==
|
|
32
|
+
Traversal.deep_sort(Traversal.deep_keys(expected))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def compare(actual, expected)
|
|
36
|
+
return false if actual.blank? && expected.present?
|
|
37
|
+
|
|
38
|
+
keys = Traversal.deep_key_paths(expected) | Traversal.deep_key_paths(actual)
|
|
39
|
+
|
|
40
|
+
compare_key_paths_and_values(keys, actual, expected)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def compare_key_paths_and_values(keys, actual, expected)
|
|
44
|
+
keys.all? do |key_path|
|
|
45
|
+
actual_value = dig_path(actual, key_path)
|
|
46
|
+
expected_value = dig_path(expected, key_path)
|
|
47
|
+
|
|
48
|
+
compare_values(actual_value, expected_value)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Digs a key path without raising when an intermediate value is not a Hash.
|
|
53
|
+
# Plain Hash#dig raises TypeError if it walks into a scalar (e.g. a schema
|
|
54
|
+
# expects a nested object but the actual value is a String), so a mismatch
|
|
55
|
+
# would crash instead of failing the match.
|
|
56
|
+
def dig_path(data, key_path)
|
|
57
|
+
key_path.reduce(data) do |value, key|
|
|
58
|
+
break nil unless value.is_a?(Hash)
|
|
59
|
+
|
|
60
|
+
value[key]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def compare_values(actual_value, expected_value)
|
|
65
|
+
case expected_value
|
|
66
|
+
when Class then compare_class(actual_value, expected_value)
|
|
67
|
+
when Regexp then compare_regexp(actual_value, expected_value)
|
|
68
|
+
when Proc then compare_proc(actual_value, expected_value)
|
|
69
|
+
when Array then compare_array(actual_value, expected_value)
|
|
70
|
+
else compare_simple_value(actual_value, expected_value)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def compare_class(actual_value, expected_value)
|
|
75
|
+
actual_value.instance_of?(expected_value)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def compare_regexp(actual_value, expected_value)
|
|
79
|
+
expected_value.match?(actual_value.to_s)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def compare_proc(actual_value, expected_value)
|
|
83
|
+
Constraints.match(actual_value, expected_value.call)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def compare_array(actual_value, expected_value)
|
|
87
|
+
if simple_type?(expected_value)
|
|
88
|
+
compare_typed_array(actual_value, expected_value)
|
|
89
|
+
elsif interface?(expected_value)
|
|
90
|
+
compare_interface_array(actual_value, expected_value)
|
|
91
|
+
else
|
|
92
|
+
compare_exact_array(actual_value, expected_value)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# [SomeClass] => every element must be an instance of SomeClass.
|
|
97
|
+
def compare_typed_array(actual_value, expected_value)
|
|
98
|
+
type = expected_value[0]
|
|
99
|
+
|
|
100
|
+
actual_value.all? { |elem| compare_class(elem, type) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# [{ ...interface... }] => every element must match the single interface.
|
|
104
|
+
# Elements go through match (not compare) so each one is held to the same
|
|
105
|
+
# key-structure guard as a top-level object; otherwise an element with an
|
|
106
|
+
# extra null-valued key would slip through (nil == nil).
|
|
107
|
+
def compare_interface_array(actual_value, expected_value)
|
|
108
|
+
interface = expected_value[0]
|
|
109
|
+
|
|
110
|
+
actual_value.all? { |elem| match(elem, interface) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Any other array => element-by-element match, sizes must be equal.
|
|
114
|
+
def compare_exact_array(actual_value, expected_value)
|
|
115
|
+
return false if actual_value&.size != expected_value&.size
|
|
116
|
+
|
|
117
|
+
expected_value.each_with_index.all? do |elem, index|
|
|
118
|
+
elem.is_a?(Hash) ? compare(actual_value[index], elem) : compare_simple_value(actual_value[index], elem)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def compare_simple_value(actual_value, expected_value)
|
|
123
|
+
actual_value == expected_value
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def simple_type?(expected_value)
|
|
127
|
+
expected_value.size == 1 && expected_value[0].instance_of?(Class)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def interface?(expected_value)
|
|
131
|
+
expected_value.size == 1 && expected_value[0].is_a?(Hash)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module JsonApi
|
|
5
|
+
# Traversal holds the structural helpers used to compare JSON shapes:
|
|
6
|
+
# collecting the nested key structure of a Hash and sorting it into a
|
|
7
|
+
# canonical order. These were previously monkey-patched onto core Hash and
|
|
8
|
+
# Array; keeping them here means the gem no longer mutates those classes in
|
|
9
|
+
# the host application.
|
|
10
|
+
module Traversal
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# The nested keys of a hash, with each nested hash's keys inlined as an
|
|
14
|
+
# array, e.g. { a: 1, b: { c: 2 } } => [:a, :b, [:c]].
|
|
15
|
+
def deep_keys(hash)
|
|
16
|
+
hash.each_with_object([]) do |(key, value), keys|
|
|
17
|
+
keys << key
|
|
18
|
+
keys << deep_keys(value) if value.respond_to?(:keys)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Every leaf key path of a hash, e.g. { a: { b: 1 }, c: 2 } => [[:a, :b], [:c]].
|
|
23
|
+
def deep_key_paths(hash)
|
|
24
|
+
stack = hash.map { |key, value| [[key], value] }
|
|
25
|
+
key_map = []
|
|
26
|
+
|
|
27
|
+
until stack.empty?
|
|
28
|
+
key, value = stack.pop
|
|
29
|
+
|
|
30
|
+
key_map << key unless value.is_a?(Hash)
|
|
31
|
+
|
|
32
|
+
next unless value.is_a?(Hash)
|
|
33
|
+
|
|
34
|
+
value.each { |k, v| stack.push([key.dup << k, v]) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
key_map.reverse
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Recursively sorts an array (and any nested arrays) into a canonical order
|
|
41
|
+
# so two key structures can be compared regardless of original order.
|
|
42
|
+
def deep_sort(array)
|
|
43
|
+
array
|
|
44
|
+
.map { |element| element.is_a?(Array) ? deep_sort(element) : element }
|
|
45
|
+
.sort_by { |element| element.is_a?(Array) ? element.first.to_s : element.to_s }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/rspec/json_api.rb
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Load
|
|
3
|
+
# Load 3rd party libraries
|
|
4
4
|
require "json"
|
|
5
|
+
require "uri"
|
|
5
6
|
require "diffy"
|
|
6
7
|
require "active_support/core_ext/object/blank"
|
|
7
8
|
|
|
8
9
|
# Load the json_api parts
|
|
9
10
|
require "rspec/json_api/version"
|
|
10
|
-
require "rspec/json_api/
|
|
11
|
-
require "rspec/json_api/
|
|
12
|
-
|
|
13
|
-
# Load extentions
|
|
14
|
-
require "extentions/hash"
|
|
15
|
-
require "extentions/array"
|
|
11
|
+
require "rspec/json_api/traversal"
|
|
12
|
+
require "rspec/json_api/constraints"
|
|
13
|
+
require "rspec/json_api/schema_match"
|
|
16
14
|
|
|
17
15
|
# Load matchers
|
|
18
16
|
require "rspec/json_api/matchers"
|
data/rspec-json_api.gemspec
CHANGED
|
@@ -11,11 +11,12 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.summary = "RSpec extension to test JSON API response."
|
|
12
12
|
spec.homepage = "https://github.com/nomtek/rspec-json_api"
|
|
13
13
|
spec.license = "MIT"
|
|
14
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 3.
|
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
|
|
15
15
|
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/nomtek/rspec-json_api"
|
|
18
18
|
spec.metadata["changelog_uri"] = "https://github.com/nomtek/rspec-json_api/blob/master/CHANGELOG.md"
|
|
19
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
19
20
|
|
|
20
21
|
# Specify which files should be added to the gem when it is released.
|
|
21
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
@@ -26,10 +27,14 @@ Gem::Specification.new do |spec|
|
|
|
26
27
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
27
28
|
spec.require_paths = ["lib"]
|
|
28
29
|
|
|
29
|
-
#
|
|
30
|
+
# Runtime dependencies. The gem only needs ActiveSupport's blank?/present?
|
|
31
|
+
# core extensions and Rails::Generators (which lives in railties); depending
|
|
32
|
+
# on the full "rails" meta-gem would force ActiveRecord, ActionCable,
|
|
33
|
+
# ActionMailer, ActionMailbox, ActiveStorage, ActionText, etc. on every
|
|
34
|
+
# consumer of a JSON-matcher gem. The >= 6.1.4.1 floor is unchanged.
|
|
30
35
|
spec.add_dependency "activesupport", ">= 6.1.4.1"
|
|
31
36
|
spec.add_dependency "diffy", ">= 3.4.2"
|
|
32
|
-
spec.add_dependency "
|
|
37
|
+
spec.add_dependency "railties", ">= 6.1.4.1"
|
|
33
38
|
spec.add_dependency "rspec-rails", ">= 5.0.2"
|
|
34
39
|
|
|
35
40
|
# For more information and examples about making a new gem, checkout our
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rspec-json_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Gajowiak
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activesupport
|
|
@@ -39,7 +38,7 @@ dependencies:
|
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: 3.4.2
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
41
|
+
name: railties
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - ">="
|
|
@@ -66,13 +65,13 @@ dependencies:
|
|
|
66
65
|
- - ">="
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
67
|
version: 5.0.2
|
|
69
|
-
description:
|
|
70
68
|
email:
|
|
71
69
|
- m.gajowiak@nomtek.com
|
|
72
70
|
executables: []
|
|
73
71
|
extensions: []
|
|
74
72
|
extra_rdoc_files: []
|
|
75
73
|
files:
|
|
74
|
+
- ".gitattributes"
|
|
76
75
|
- ".github/workflows/main.yml"
|
|
77
76
|
- ".gitignore"
|
|
78
77
|
- ".rspec"
|
|
@@ -87,21 +86,24 @@ files:
|
|
|
87
86
|
- Rakefile
|
|
88
87
|
- bin/console
|
|
89
88
|
- bin/setup
|
|
90
|
-
-
|
|
91
|
-
-
|
|
89
|
+
- gemfiles/rails_6_1.gemfile
|
|
90
|
+
- gemfiles/rails_7_1.gemfile
|
|
91
|
+
- gemfiles/rails_7_2.gemfile
|
|
92
|
+
- gemfiles/rails_8_0.gemfile
|
|
93
|
+
- gemfiles/rails_8_1.gemfile
|
|
92
94
|
- lib/generators/rspec/json_api/install/install_generator.rb
|
|
93
95
|
- lib/generators/rspec/json_api/install/templates/rspec/json_api/interfaces/.empty_directory
|
|
94
96
|
- lib/generators/rspec/json_api/install/templates/rspec/json_api/types/.empty_directory
|
|
95
97
|
- lib/generators/rspec/json_api/interface/interface_generator.rb
|
|
96
|
-
- lib/generators/rspec/json_api/interface/templates/interface.erb
|
|
97
98
|
- lib/generators/rspec/json_api/type/type_generator.rb
|
|
98
99
|
- lib/rspec/json_api.rb
|
|
99
|
-
- lib/rspec/json_api/
|
|
100
|
-
- lib/rspec/json_api/compare_hash.rb
|
|
100
|
+
- lib/rspec/json_api/constraints.rb
|
|
101
101
|
- lib/rspec/json_api/interfaces/example_interface.rb
|
|
102
102
|
- lib/rspec/json_api/matchers.rb
|
|
103
103
|
- lib/rspec/json_api/matchers/have_no_content.rb
|
|
104
104
|
- lib/rspec/json_api/matchers/match_json_schema.rb
|
|
105
|
+
- lib/rspec/json_api/schema_match.rb
|
|
106
|
+
- lib/rspec/json_api/traversal.rb
|
|
105
107
|
- lib/rspec/json_api/types/email.rb
|
|
106
108
|
- lib/rspec/json_api/types/uri.rb
|
|
107
109
|
- lib/rspec/json_api/types/uuid.rb
|
|
@@ -114,7 +116,7 @@ metadata:
|
|
|
114
116
|
homepage_uri: https://github.com/nomtek/rspec-json_api
|
|
115
117
|
source_code_uri: https://github.com/nomtek/rspec-json_api
|
|
116
118
|
changelog_uri: https://github.com/nomtek/rspec-json_api/blob/master/CHANGELOG.md
|
|
117
|
-
|
|
119
|
+
rubygems_mfa_required: 'true'
|
|
118
120
|
rdoc_options: []
|
|
119
121
|
require_paths:
|
|
120
122
|
- lib
|
|
@@ -122,15 +124,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
122
124
|
requirements:
|
|
123
125
|
- - ">="
|
|
124
126
|
- !ruby/object:Gem::Version
|
|
125
|
-
version: 3.
|
|
127
|
+
version: 3.2.0
|
|
126
128
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
129
|
requirements:
|
|
128
130
|
- - ">="
|
|
129
131
|
- !ruby/object:Gem::Version
|
|
130
132
|
version: '0'
|
|
131
133
|
requirements: []
|
|
132
|
-
rubygems_version:
|
|
133
|
-
signing_key:
|
|
134
|
+
rubygems_version: 4.0.3
|
|
134
135
|
specification_version: 4
|
|
135
136
|
summary: RSpec extension to test JSON API response.
|
|
136
137
|
test_files: []
|
data/lib/extentions/array.rb
DELETED
data/lib/extentions/hash.rb
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Extension methods for hash class
|
|
4
|
-
class Hash
|
|
5
|
-
def deep_keys
|
|
6
|
-
each_with_object([]) do |(k, v), keys|
|
|
7
|
-
keys << k
|
|
8
|
-
keys << v.deep_keys if v.respond_to?(:keys)
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def deep_key_paths
|
|
13
|
-
stack = map { |k, v| [[k], v] }
|
|
14
|
-
key_map = []
|
|
15
|
-
|
|
16
|
-
until stack.empty?
|
|
17
|
-
key, value = stack.pop
|
|
18
|
-
|
|
19
|
-
key_map << key unless value.is_a? Hash
|
|
20
|
-
|
|
21
|
-
next unless value.is_a? Hash
|
|
22
|
-
|
|
23
|
-
value.map do |k, v|
|
|
24
|
-
stack.push [key.dup << k, v]
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
key_map.reverse
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def sanitize!(keys)
|
|
32
|
-
keep_if do |k, _v|
|
|
33
|
-
keys.include?(k)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RSpec
|
|
4
|
-
module JsonApi
|
|
5
|
-
module CompareArray
|
|
6
|
-
extend self
|
|
7
|
-
|
|
8
|
-
def compare(actual, expected)
|
|
9
|
-
if interface?(expected)
|
|
10
|
-
actual.all? do |actual_elem|
|
|
11
|
-
return false unless actual_elem.deep_keys == expected[0].deep_keys
|
|
12
|
-
|
|
13
|
-
CompareHash.compare(actual_elem, expected[0])
|
|
14
|
-
end
|
|
15
|
-
else
|
|
16
|
-
actual.each_with_index.all? do |actual_elem, index|
|
|
17
|
-
compare_primitive_type_element(actual, expected, actual_elem, index)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def interface?(expected_value)
|
|
25
|
-
expected_value.size == 1 && expected_value[0].is_a?(Hash)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def compare_primitive_type_element(actual, expected, actual_elem, index)
|
|
29
|
-
if actual[index].respond_to?(:deep_keys) && expected[index].respond_to?(:deep_keys)
|
|
30
|
-
return false unless actual[index].deep_keys == expected[index].deep_keys
|
|
31
|
-
|
|
32
|
-
CompareHash.compare(actual_elem, expected[index])
|
|
33
|
-
else
|
|
34
|
-
CompareHash.compare_values(actual[index], expected[index])
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|