nice_http 1.10.1 → 1.11.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/README.md +38 -5
- data/lib/nice_http/manage/request.rb +8 -6
- data/lib/nice_http/manage/response.rb +7 -6
- data/lib/nice_http/methods/delete.rb +2 -1
- data/lib/nice_http/methods/head.rb +2 -1
- data/lib/nice_http/methods/patch.rb +2 -1
- data/lib/nice_http/methods/post.rb +2 -1
- data/lib/nice_http/methods/put.rb +2 -1
- data/lib/nice_http/utils/expand_patterns.rb +130 -0
- data/lib/nice_http/validate_response.rb +30 -5
- data/lib/nice_http.rb +1 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '08a3b0309aabfc6296d1bb905ae4dda3ea550702a08d6f5bc96aae80acbaf8b3'
|
|
4
|
+
data.tar.gz: c4b6b1b7e01b548d369b0acff34acf8a36f1b0cb9bad682aa4e293a52a2296ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 013f23c8d0df421fa63f8c00cd6948930040d0e4743d9a40a36c529e3b8a37243e8d6d5dd7017f3141e68a5e53c8b9d3ae044980074f91bf23ff2e367d67ee28
|
|
7
|
+
data.tar.gz: 02353a935cf0674d0ca05e3166f61775087debdb4595258d8508b17f5a99d0106c96f827c4dee9532e777a61f74c08236097754b75e6b1f714687e0a39d29891
|
data/README.md
CHANGED
|
@@ -20,7 +20,7 @@ NiceHttp is able to use hashes as requests data and uses the Request Hash struct
|
|
|
20
20
|
|
|
21
21
|
**On the next link you have a full example using nice_http and RSpec to test REST APIs, Uber API and Reqres API: https://github.com/MarioRuiz/api-testing-example**
|
|
22
22
|
|
|
23
|
-
**Related gems:** NiceHttp uses [nice_hash](https://github.com/MarioRuiz/nice_hash) (v1.
|
|
23
|
+
**Related gems:** NiceHttp uses [nice_hash](https://github.com/MarioRuiz/nice_hash) (v1.21+) for request/response hashes (e.g. `resp.data.json`, `set_values`, `nice_merge`) and for generating headers. [string_pattern](https://github.com/MarioRuiz/string_pattern) (v2.5+) is used by nice_hash for pattern-based string generation and validation. To generate random or pattern-based request data, see the [nice_hash documentation](https://github.com/MarioRuiz/nice_hash). Example: 1000 valid requests + 800 with one wrong field to test validation — [gist](https://gist.github.com/MarioRuiz/824d7a462b62fd85f02c1a09455deefb). Upgrading from nice_http 1.10: see [Compatibility with nice_hash 1.21](#compatibility-with-nice_hash-121).
|
|
24
24
|
|
|
25
25
|
# Table of Contents
|
|
26
26
|
|
|
@@ -44,6 +44,7 @@ NiceHttp is able to use hashes as requests data and uses the Request Hash struct
|
|
|
44
44
|
- [Http stats](#http-stats)
|
|
45
45
|
- [Testing with generated data](#testing-with-generated-data)
|
|
46
46
|
- [Validating API responses](#validating-api-responses)
|
|
47
|
+
- [Compatibility with nice_hash 1.21](#compatibility-with-nice_hash-121)
|
|
47
48
|
- [Tips](#tips)
|
|
48
49
|
- [Download a file](#download-a-file)
|
|
49
50
|
- [Send multipart content](#send-multipart-content)
|
|
@@ -758,7 +759,7 @@ This will generate an items key that will contain an array of the values you add
|
|
|
758
759
|
|
|
759
760
|
When you build request hashes with [nice_hash](https://github.com/MarioRuiz/nice_hash) patterns (in `data`, `headers`, or `values_for`), you can:
|
|
760
761
|
|
|
761
|
-
* **Reproducible tests:** Use `seed` so the same request is generated every time
|
|
762
|
+
* **Reproducible tests:** Use `seed` so the same request is generated every time. Strings from a seed in nice_hash 1.19 will not match 1.21. `:uuid`, `:iban` and `:luhn` are not produced from the seed.
|
|
762
763
|
```ruby
|
|
763
764
|
req = { path: "/api/users", data: { name: :"10-20:L", email: :"20-40:@" } }
|
|
764
765
|
resp = http.post req.generate(seed: 42) # same payload every run
|
|
@@ -771,10 +772,26 @@ When you build request hashes with [nice_hash](https://github.com/MarioRuiz/nice
|
|
|
771
772
|
requests.each { |r| http.post r }
|
|
772
773
|
```
|
|
773
774
|
|
|
774
|
-
* **
|
|
775
|
+
* **Shorthands on send:** A pattern symbol in `data`, in a nested hash, in an array of hashes, or in that request's headers is expanded on each call. Strings are left as written, so `"10:N"` is sent as the characters `10:N`. A symbol that is not a pattern, such as `:admin`, is sent as the text `"admin"`.
|
|
775
776
|
```ruby
|
|
776
|
-
resp = http.post(
|
|
777
|
+
resp = http.post({
|
|
778
|
+
path: "/api/items",
|
|
779
|
+
data: {
|
|
780
|
+
id: :uuid,
|
|
781
|
+
email: :email,
|
|
782
|
+
iban: [:iban, "DE"],
|
|
783
|
+
card: [:luhn, 15],
|
|
784
|
+
tags: [:"3:L"],
|
|
785
|
+
age: 18..30,
|
|
786
|
+
created: DateTime,
|
|
787
|
+
active: Boolean,
|
|
788
|
+
token: /[A-Z0-9]{8}/,
|
|
789
|
+
code: "10:N",
|
|
790
|
+
role: :admin
|
|
791
|
+
}
|
|
792
|
+
})
|
|
777
793
|
```
|
|
794
|
+
The same values inside `values_for` are expanded after they are copied into `data`. Connection headers are still generated once, when the client is created, so a pattern there stays the same for every request on that client. A one-element pattern array in those headers becomes 1 to 5 strings for the life of the connection. A pattern header passed on a single request is generated again on the next request.
|
|
778
795
|
|
|
779
796
|
## Validating API responses
|
|
780
797
|
|
|
@@ -792,7 +809,23 @@ You can validate JSON responses against a structure or patterns using [nice_hash
|
|
|
792
809
|
# => { "user.name" => { expected: "Alice", got: "Bob" } } or {}
|
|
793
810
|
```
|
|
794
811
|
|
|
795
|
-
* **Pattern validation:**
|
|
812
|
+
* **Pattern validation:** `NiceHttp.validate_response` compares the shape and, when the expected value is a pattern symbol, shorthand (`:uuid`, `:email`, `:iban`, `:luhn`), checksum pair (`[:iban, "DE"]`, `[:luhn, 15]`), one-element pattern array, range, regexp, `DateTime` or `Boolean`, checks that the response value matches. String keys and symbol keys are equivalent. A plain string in the expected structure is not treated as a pattern. A missing key still fails. `explain:` is present only when a pattern fails.
|
|
813
|
+
```ruby
|
|
814
|
+
result = NiceHttp.validate_response(resp, { "id" => :uuid, email: :email }, explain: true)
|
|
815
|
+
result[:ok] # => true / false
|
|
816
|
+
result[:explain] # only when a pattern fails: { email: { valid:, errors:, message:, index: } }
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
## Compatibility with nice_hash 1.21
|
|
820
|
+
|
|
821
|
+
nice_http 1.11 requires nice_hash 1.21 and string_pattern 2.5 (Ruby >= 3.0).
|
|
822
|
+
|
|
823
|
+
* `generate(seed:)` / `generate_n(seed:)` do not repeat the strings from nice_hash 1.19. Do not assert `:uuid`, `:iban` or `:luhn` from a seed.
|
|
824
|
+
* `StringPattern.block_list` matches literals. Set `StringPattern.block_list_regexp = true` when the list holds regular expressions.
|
|
825
|
+
* A `Regexp` pattern must match the whole value.
|
|
826
|
+
* `[:"3:L"]` generates 1 to 5 strings, including in connection headers.
|
|
827
|
+
* Pattern symbols and shorthands in a request body or request header are expanded when the request is sent. `"10:N"` and `"a|b"` are not.
|
|
828
|
+
* `validate_response` can return `ok: true` for string keys, and for pattern placeholders whose value matches, where 1.10 returned `ok: false`.
|
|
796
829
|
|
|
797
830
|
## Tips
|
|
798
831
|
|
|
@@ -11,7 +11,7 @@ module NiceHttpManageRequest
|
|
|
11
11
|
######################################################
|
|
12
12
|
def manage_request(*arguments_param)
|
|
13
13
|
if arguments_param.size == 1 and arguments_param[0].kind_of?(Hash)
|
|
14
|
-
arguments = [arguments_param[0]
|
|
14
|
+
arguments = [NiceHttpUtils.copy_request(arguments_param[0])]
|
|
15
15
|
else
|
|
16
16
|
arguments = arguments_param
|
|
17
17
|
end
|
|
@@ -146,16 +146,16 @@ module NiceHttpManageRequest
|
|
|
146
146
|
values_for_orig = {}
|
|
147
147
|
if @defaults_request.key?(:values_for) and @defaults_request.is_a?(Hash) and @defaults_request[:values_for].size > 0
|
|
148
148
|
if arguments[0].include?(:values_for)
|
|
149
|
-
values_for_orig = arguments[0][:values_for]
|
|
149
|
+
values_for_orig = NiceHttpUtils.copy_request(arguments[0][:values_for])
|
|
150
150
|
arguments[0][:values_for] = @defaults_request[:values_for].nice_merge(arguments[0][:values_for])
|
|
151
151
|
else
|
|
152
|
-
arguments[0][:values_for] = @defaults_request[:values_for]
|
|
152
|
+
arguments[0][:values_for] = NiceHttpUtils.copy_request(@defaults_request[:values_for])
|
|
153
153
|
end
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
if @values_for.size > 0
|
|
157
157
|
if arguments[0][:values_for].nil?
|
|
158
|
-
arguments[0][:values_for] = @values_for
|
|
158
|
+
arguments[0][:values_for] = NiceHttpUtils.copy_request(@values_for)
|
|
159
159
|
else
|
|
160
160
|
arguments[0][:values_for] = @values_for.nice_merge(arguments[0][:values_for])
|
|
161
161
|
end
|
|
@@ -186,13 +186,14 @@ module NiceHttpManageRequest
|
|
|
186
186
|
}
|
|
187
187
|
end
|
|
188
188
|
elsif data.kind_of?(Hash)
|
|
189
|
-
data_orig = data
|
|
189
|
+
data_orig = NiceHttpUtils.copy_request(data)
|
|
190
190
|
data.nice_merge!(@defaults_request[:data]) if @defaults_request.key?(:data)
|
|
191
191
|
data = NiceHttpUtils.set_lambdas(data, data_orig)
|
|
192
192
|
|
|
193
193
|
if arguments[0].include?(:values_for)
|
|
194
194
|
data = data.set_values(arguments[0][:values_for])
|
|
195
195
|
end
|
|
196
|
+
data = NiceHttpUtils.expand_patterns(data)
|
|
196
197
|
data = data.to_json()
|
|
197
198
|
|
|
198
199
|
elsif data.kind_of?(Array)
|
|
@@ -213,7 +214,7 @@ module NiceHttpManageRequest
|
|
|
213
214
|
end
|
|
214
215
|
data_arr.push(data_n)
|
|
215
216
|
}
|
|
216
|
-
data = data_arr.to_json()
|
|
217
|
+
data = NiceHttpUtils.expand_patterns(data_arr).to_json()
|
|
217
218
|
else
|
|
218
219
|
@logger.fatal("Wrong format on request application/json, be sure is a Hash, Array of Hashes or JSON string")
|
|
219
220
|
return :error, :error, :error
|
|
@@ -254,6 +255,7 @@ module NiceHttpManageRequest
|
|
|
254
255
|
end
|
|
255
256
|
data_s = data_s.to_s().gsub("<", "<")
|
|
256
257
|
end
|
|
258
|
+
headers_t = NiceHttpUtils.expand_patterns(headers_t) if headers_t.is_a?(Hash)
|
|
257
259
|
if headers_t.keys.include?("Accept-Encoding")
|
|
258
260
|
headers_t["Accept-Encoding"].gsub!("gzip", "") #removed so the response is in plain text
|
|
259
261
|
end
|
|
@@ -90,12 +90,13 @@ module NiceHttpManageResponse
|
|
|
90
90
|
value_orig = value
|
|
91
91
|
if key.kind_of?(Symbol)
|
|
92
92
|
if key == :code or key == :data or key == :header or key == :message
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
content_type = @response[:'content-type'].to_s
|
|
94
|
+
if key == :data and !content_type.include?("text/html")
|
|
95
|
+
if key == :data and content_type != "" and
|
|
96
|
+
(!content_type.include?("text") and
|
|
97
|
+
!content_type.include?("json") and
|
|
98
|
+
!content_type.include?("xml") and
|
|
99
|
+
!content_type.include?("charset=utf-8"))
|
|
99
100
|
message += "\n data: It's not text data so won't be in the logs."
|
|
100
101
|
else
|
|
101
102
|
begin
|
|
@@ -94,7 +94,8 @@ module NiceHttpHttpMethods
|
|
|
94
94
|
@http.start()
|
|
95
95
|
@headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) }
|
|
96
96
|
@start_time_net = Time.now if @start_time_net.nil?
|
|
97
|
-
resp
|
|
97
|
+
resp = @http.delete(path, headers_t)
|
|
98
|
+
data = resp.body
|
|
98
99
|
end
|
|
99
100
|
end
|
|
100
101
|
manage_response(resp, data)
|
|
@@ -59,7 +59,8 @@ module NiceHttpHttpMethods
|
|
|
59
59
|
@http.start()
|
|
60
60
|
@headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) }
|
|
61
61
|
@start_time_net = Time.now if @start_time_net.nil?
|
|
62
|
-
resp
|
|
62
|
+
resp = @http.head(path, headers_t)
|
|
63
|
+
data = resp.body
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
manage_response(resp, data)
|
|
@@ -74,7 +74,8 @@ module NiceHttpHttpMethods
|
|
|
74
74
|
@http.start()
|
|
75
75
|
@headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) }
|
|
76
76
|
@start_time_net = Time.now if @start_time_net.nil?
|
|
77
|
-
resp
|
|
77
|
+
resp = @http.patch(path, data, headers_t)
|
|
78
|
+
data = resp.body
|
|
78
79
|
end
|
|
79
80
|
end
|
|
80
81
|
manage_response(resp, data)
|
|
@@ -93,7 +93,8 @@ module NiceHttpHttpMethods
|
|
|
93
93
|
@http.start()
|
|
94
94
|
@start_time_net = Time.now if @start_time_net.nil?
|
|
95
95
|
@headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) }
|
|
96
|
-
resp
|
|
96
|
+
resp = @http.post(path, data, headers_t)
|
|
97
|
+
data = resp.body
|
|
97
98
|
end
|
|
98
99
|
end
|
|
99
100
|
manage_response(resp, data)
|
|
@@ -73,7 +73,8 @@ module NiceHttpHttpMethods
|
|
|
73
73
|
@http.start()
|
|
74
74
|
@headers_orig.each { |k, v| headers_t[k] = v.call if v.is_a?(Proc) and headers_t.key?(k) }
|
|
75
75
|
@start_time_net = Time.now if @start_time_net.nil?
|
|
76
|
-
resp
|
|
76
|
+
resp = @http.send_request("PUT", path, data, headers_t)
|
|
77
|
+
data = resp.body
|
|
77
78
|
end
|
|
78
79
|
end
|
|
79
80
|
manage_response(resp, data)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module NiceHttpUtils
|
|
2
|
+
PATTERN_SYMBOL = /\A!?\d+-?\d*:.+/
|
|
3
|
+
SELECTOR_SYMBOL = /\A(?:[\w\s\-]+\|)+[\w\s\-]+\z/
|
|
4
|
+
SHORTHANDS = [:uuid, :email, :iban, :luhn].freeze
|
|
5
|
+
|
|
6
|
+
# A value NiceHttp expands before sending, or that validate_response checks as a pattern.
|
|
7
|
+
# Plain strings are never patterns here, even when they look like "10:N" or "a|b".
|
|
8
|
+
def self.pattern_value?(value)
|
|
9
|
+
return true if pattern_symbol?(value)
|
|
10
|
+
return true if value.is_a?(Range) || value.is_a?(Regexp)
|
|
11
|
+
return true if defined?(DateTime) && value.is_a?(Class) && value == DateTime
|
|
12
|
+
return true if defined?(Boolean) && value.is_a?(Module) && value == Boolean
|
|
13
|
+
return true if checksum_array?(value)
|
|
14
|
+
return true if one_pattern_array?(value)
|
|
15
|
+
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.contains_pattern?(obj)
|
|
20
|
+
return true if pattern_value?(obj)
|
|
21
|
+
|
|
22
|
+
case obj
|
|
23
|
+
when Hash
|
|
24
|
+
obj.any? { |_key, value| contains_pattern?(value) }
|
|
25
|
+
when Array
|
|
26
|
+
obj.any? { |value| contains_pattern?(value) }
|
|
27
|
+
else
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Copy of expected where pattern placeholders are replaced by the actual value,
|
|
33
|
+
# so compare_structure checks shape and leaves pattern checks to NiceHash.validate.
|
|
34
|
+
def self.structure_without_patterns(expected, actual)
|
|
35
|
+
if pattern_value?(expected)
|
|
36
|
+
return actual.nil? ? expected : actual
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
case expected
|
|
40
|
+
when Hash
|
|
41
|
+
expected.each_with_object({}) do |(key, value), copy|
|
|
42
|
+
child = actual.is_a?(Hash) ? actual[key] : nil
|
|
43
|
+
copy[key] = structure_without_patterns(value, child)
|
|
44
|
+
end
|
|
45
|
+
when Array
|
|
46
|
+
if expected.empty?
|
|
47
|
+
expected
|
|
48
|
+
else
|
|
49
|
+
child = actual.is_a?(Array) ? actual[0] : nil
|
|
50
|
+
[structure_without_patterns(expected[0], child)]
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
expected
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Copy a request without cloning Class or Module.
|
|
58
|
+
# DateTime.clone is an anonymous class and Boolean.clone is an anonymous module,
|
|
59
|
+
# so NiceHash.generate would no longer recognize them.
|
|
60
|
+
def self.copy_request(obj)
|
|
61
|
+
case obj
|
|
62
|
+
when Hash
|
|
63
|
+
obj.each_with_object({}) { |(key, value), copy| copy[key] = copy_request(value) }
|
|
64
|
+
when Array
|
|
65
|
+
obj.map { |value| copy_request(value) }
|
|
66
|
+
else
|
|
67
|
+
return obj if obj.is_a?(Module)
|
|
68
|
+
|
|
69
|
+
obj.clone
|
|
70
|
+
end
|
|
71
|
+
rescue StandardError
|
|
72
|
+
obj
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# New hash or array. Strings, numbers, booleans, nil and procs are copied as they are.
|
|
76
|
+
def self.expand_patterns(obj)
|
|
77
|
+
case obj
|
|
78
|
+
when Hash
|
|
79
|
+
obj.each_with_object({}) { |(key, value), copy| copy[key] = expand_pattern_value(value) }
|
|
80
|
+
when Array
|
|
81
|
+
if pattern_value?(obj)
|
|
82
|
+
generate_pattern(obj)
|
|
83
|
+
else
|
|
84
|
+
obj.map { |value| expand_pattern_value(value) }
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
expand_pattern_value(obj)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.expand_pattern_value(value)
|
|
92
|
+
if pattern_value?(value)
|
|
93
|
+
generate_pattern(value)
|
|
94
|
+
elsif value.is_a?(Hash) || value.is_a?(Array)
|
|
95
|
+
expand_patterns(value)
|
|
96
|
+
else
|
|
97
|
+
value
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.generate_pattern(value)
|
|
102
|
+
NiceHash.generate({ pattern: value })[:pattern]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.pattern_symbol?(value)
|
|
106
|
+
return false unless value.is_a?(Symbol)
|
|
107
|
+
return true if SHORTHANDS.include?(value)
|
|
108
|
+
|
|
109
|
+
text = value.to_s
|
|
110
|
+
text.match?(PATTERN_SYMBOL) || text.match?(SELECTOR_SYMBOL)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.checksum_array?(value)
|
|
114
|
+
value.is_a?(Array) && value.size.between?(1, 2) && [:iban, :luhn].include?(value[0])
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.one_pattern_array?(value)
|
|
118
|
+
return false unless value.is_a?(Array) && value.size == 1
|
|
119
|
+
return false if checksum_array?(value)
|
|
120
|
+
|
|
121
|
+
element = value[0]
|
|
122
|
+
return false unless element.is_a?(Symbol)
|
|
123
|
+
|
|
124
|
+
StringPattern.analyze(element, silent: true).is_a?(StringPattern::Pattern)
|
|
125
|
+
rescue StandardError
|
|
126
|
+
false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private_class_method :expand_pattern_value, :generate_pattern, :pattern_symbol?, :checksum_array?, :one_pattern_array?
|
|
130
|
+
end
|
|
@@ -5,8 +5,13 @@ class NiceHttp
|
|
|
5
5
|
#
|
|
6
6
|
# @param resp [Hash] Response object (e.g. from get/post). Must have :data (body string or Hash/Array).
|
|
7
7
|
# @param expected_structure [Hash, Array] Structure with expected keys and nesting (values can be placeholders).
|
|
8
|
-
# @param options [Hash] Optional. :include_diff => true
|
|
9
|
-
#
|
|
8
|
+
# @param options [Hash] Optional. :include_diff => true adds a diff when validation fails.
|
|
9
|
+
# :explain => true adds NiceHash pattern errors when a pattern in expected_structure does not match.
|
|
10
|
+
# String keys in expected_structure are treated the same as symbol keys.
|
|
11
|
+
# Pattern checks run only when expected_structure contains pattern symbols, shorthands
|
|
12
|
+
# (:uuid, :email, :iban, :luhn), checksum pairs, a one-element pattern array, a Range, a Regexp, DateTime or Boolean.
|
|
13
|
+
# A plain string such as "10:N" stays a literal and is not validated as a pattern.
|
|
14
|
+
# @return [Hash] { ok: true } when it matches, or { ok: false } with optional :diff and :explain.
|
|
10
15
|
# @example
|
|
11
16
|
# resp = http.get("/api/users/1")
|
|
12
17
|
# result = NiceHttp.validate_response(resp, { user: { name: "xxx", id: 1 } })
|
|
@@ -14,6 +19,9 @@ class NiceHttp
|
|
|
14
19
|
# @example
|
|
15
20
|
# result = NiceHttp.validate_response(resp, expected, include_diff: true)
|
|
16
21
|
# puts result[:diff] unless result[:ok]
|
|
22
|
+
# @example
|
|
23
|
+
# result = NiceHttp.validate_response(resp, { email: :email }, explain: true)
|
|
24
|
+
# result[:explain][:email][:message] unless result[:ok]
|
|
17
25
|
######################################################
|
|
18
26
|
def self.validate_response(resp, expected_structure, options = {})
|
|
19
27
|
data = resp.is_a?(Hash) ? resp[:data] : resp.data
|
|
@@ -30,14 +38,31 @@ class NiceHttp
|
|
|
30
38
|
return { ok: false, error: "response data is missing or not JSON" }
|
|
31
39
|
end
|
|
32
40
|
|
|
33
|
-
# Normalize to symbol keys so compare_structure/diff match
|
|
41
|
+
# Normalize to symbol keys so compare_structure/diff match either string or symbol keys
|
|
34
42
|
parsed = deep_symbolize(parsed) if parsed.is_a?(Hash) || parsed.is_a?(Array)
|
|
43
|
+
expected = if expected_structure.is_a?(Hash) || expected_structure.is_a?(Array)
|
|
44
|
+
deep_symbolize(expected_structure)
|
|
45
|
+
else
|
|
46
|
+
expected_structure
|
|
47
|
+
end
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
pattern_errors = {}
|
|
50
|
+
if NiceHttpUtils.contains_pattern?(expected)
|
|
51
|
+
comparable = NiceHttpUtils.structure_without_patterns(expected, parsed)
|
|
52
|
+
ok = NiceHash.compare_structure(comparable, parsed)
|
|
53
|
+
wrapped = NiceHash.validate({ root: expected }, { root: parsed }, only_patterns: true, explain: options[:explain] ? true : false)
|
|
54
|
+
pattern_errors = wrapped.is_a?(Hash) && wrapped.key?(:root) ? wrapped[:root] : wrapped
|
|
55
|
+
ok &&= pattern_errors.respond_to?(:empty?) && pattern_errors.empty?
|
|
56
|
+
else
|
|
57
|
+
ok = NiceHash.compare_structure(expected, parsed)
|
|
58
|
+
end
|
|
37
59
|
|
|
38
60
|
result = { ok: ok }
|
|
39
61
|
if !ok && options[:include_diff]
|
|
40
|
-
result[:diff] = NiceHash.diff(
|
|
62
|
+
result[:diff] = NiceHash.diff(expected, parsed)
|
|
63
|
+
end
|
|
64
|
+
if !ok && options[:explain] && pattern_errors.respond_to?(:empty?) && !pattern_errors.empty?
|
|
65
|
+
result[:explain] = pattern_errors
|
|
41
66
|
end
|
|
42
67
|
result
|
|
43
68
|
end
|
data/lib/nice_http.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative "nice_http/utils/basic_authentication"
|
|
|
16
16
|
require_relative "nice_http/utils/get_value_xml_tag"
|
|
17
17
|
require_relative "nice_http/utils/set_value_xml_tag"
|
|
18
18
|
require_relative "nice_http/utils/set_lambdas"
|
|
19
|
+
require_relative "nice_http/utils/expand_patterns"
|
|
19
20
|
require_relative "nice_http/reset"
|
|
20
21
|
require_relative "nice_http/add_stats"
|
|
21
22
|
require_relative "nice_http/defaults"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nice_http
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mario Ruiz
|
|
@@ -15,20 +15,20 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
18
|
+
version: '1.21'
|
|
19
19
|
- - ">="
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: 1.
|
|
21
|
+
version: 1.21.0
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
25
|
requirements:
|
|
26
26
|
- - "~>"
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: '1.
|
|
28
|
+
version: '1.21'
|
|
29
29
|
- - ">="
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
|
-
version: 1.
|
|
31
|
+
version: 1.21.0
|
|
32
32
|
- !ruby/object:Gem::Dependency
|
|
33
33
|
name: rspec
|
|
34
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -83,6 +83,7 @@ files:
|
|
|
83
83
|
- lib/nice_http/reset.rb
|
|
84
84
|
- lib/nice_http/save_stats.rb
|
|
85
85
|
- lib/nice_http/utils/basic_authentication.rb
|
|
86
|
+
- lib/nice_http/utils/expand_patterns.rb
|
|
86
87
|
- lib/nice_http/utils/get_value_xml_tag.rb
|
|
87
88
|
- lib/nice_http/utils/set_lambdas.rb
|
|
88
89
|
- lib/nice_http/utils/set_value_xml_tag.rb
|
|
@@ -99,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
99
100
|
requirements:
|
|
100
101
|
- - ">="
|
|
101
102
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '
|
|
103
|
+
version: '3.0'
|
|
103
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
105
|
requirements:
|
|
105
106
|
- - ">="
|