committee 1.15.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +5 -5
  2. data/bin/committee-stub +11 -38
  3. data/lib/committee/bin/committee_stub.rb +67 -0
  4. data/lib/committee/drivers/driver.rb +47 -0
  5. data/lib/committee/drivers/hyper_schema/driver.rb +105 -0
  6. data/lib/committee/drivers/hyper_schema/link.rb +68 -0
  7. data/lib/committee/drivers/hyper_schema/schema.rb +22 -0
  8. data/lib/committee/drivers/hyper_schema.rb +12 -0
  9. data/lib/committee/drivers/open_api_2/driver.rb +252 -0
  10. data/lib/committee/drivers/open_api_2/header_schema_builder.rb +33 -0
  11. data/lib/committee/drivers/open_api_2/link.rb +36 -0
  12. data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +83 -0
  13. data/lib/committee/drivers/open_api_2/schema.rb +26 -0
  14. data/lib/committee/drivers/open_api_2/schema_builder.rb +33 -0
  15. data/lib/committee/drivers/open_api_2.rb +13 -0
  16. data/lib/committee/drivers/open_api_3/driver.rb +51 -0
  17. data/lib/committee/drivers/open_api_3/schema.rb +41 -0
  18. data/lib/committee/drivers/open_api_3.rb +11 -0
  19. data/lib/committee/drivers/schema.rb +23 -0
  20. data/lib/committee/drivers.rb +84 -0
  21. data/lib/committee/errors.rb +17 -0
  22. data/lib/committee/middleware/base.rb +46 -29
  23. data/lib/committee/middleware/request_validation.rb +31 -49
  24. data/lib/committee/middleware/response_validation.rb +48 -25
  25. data/lib/committee/middleware/stub.rb +62 -37
  26. data/lib/committee/middleware.rb +11 -0
  27. data/lib/committee/request_unpacker.rb +58 -50
  28. data/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +79 -0
  29. data/lib/committee/schema_validator/hyper_schema/request_validator.rb +55 -0
  30. data/lib/committee/schema_validator/hyper_schema/response_generator.rb +102 -0
  31. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +89 -0
  32. data/lib/committee/schema_validator/hyper_schema/router.rb +46 -0
  33. data/lib/committee/schema_validator/hyper_schema/string_params_coercer.rb +105 -0
  34. data/lib/committee/schema_validator/hyper_schema.rb +119 -0
  35. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +139 -0
  36. data/lib/committee/schema_validator/open_api_3/request_validator.rb +52 -0
  37. data/lib/committee/schema_validator/open_api_3/response_validator.rb +29 -0
  38. data/lib/committee/schema_validator/open_api_3/router.rb +45 -0
  39. data/lib/committee/schema_validator/open_api_3.rb +120 -0
  40. data/lib/committee/schema_validator/option.rb +60 -0
  41. data/lib/committee/schema_validator.rb +23 -0
  42. data/lib/committee/test/methods.rb +68 -38
  43. data/lib/committee/test/schema_coverage.rb +101 -0
  44. data/lib/committee/utils.rb +28 -0
  45. data/lib/committee/validation_error.rb +5 -2
  46. data/lib/committee/version.rb +5 -0
  47. data/lib/committee.rb +31 -18
  48. data/test/bin/committee_stub_test.rb +57 -0
  49. data/test/bin_test.rb +25 -0
  50. data/test/committee_test.rb +77 -0
  51. data/test/drivers/hyper_schema/driver_test.rb +49 -0
  52. data/test/drivers/hyper_schema/link_test.rb +56 -0
  53. data/test/drivers/open_api_2/driver_test.rb +156 -0
  54. data/test/drivers/open_api_2/header_schema_builder_test.rb +26 -0
  55. data/test/drivers/open_api_2/link_test.rb +52 -0
  56. data/test/drivers/open_api_2/parameter_schema_builder_test.rb +195 -0
  57. data/test/drivers/open_api_3/driver_test.rb +84 -0
  58. data/test/drivers_test.rb +154 -0
  59. data/test/middleware/base_test.rb +96 -7
  60. data/test/middleware/request_validation_open_api_3_test.rb +626 -0
  61. data/test/middleware/request_validation_test.rb +423 -32
  62. data/test/middleware/response_validation_open_api_3_test.rb +291 -0
  63. data/test/middleware/response_validation_test.rb +125 -23
  64. data/test/middleware/stub_test.rb +81 -20
  65. data/test/request_unpacker_test.rb +126 -52
  66. data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +111 -0
  67. data/test/schema_validator/hyper_schema/request_validator_test.rb +151 -0
  68. data/test/schema_validator/hyper_schema/response_generator_test.rb +142 -0
  69. data/test/{response_validator_test.rb → schema_validator/hyper_schema/response_validator_test.rb} +43 -6
  70. data/test/schema_validator/hyper_schema/router_test.rb +88 -0
  71. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +137 -0
  72. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +218 -0
  73. data/test/schema_validator/open_api_3/request_validator_test.rb +110 -0
  74. data/test/schema_validator/open_api_3/response_validator_test.rb +92 -0
  75. data/test/test/methods_new_version_test.rb +97 -0
  76. data/test/test/methods_test.rb +334 -27
  77. data/test/test/schema_coverage_test.rb +216 -0
  78. data/test/test_helper.rb +108 -1
  79. data/test/validation_error_test.rb +3 -1
  80. metadata +190 -27
  81. data/lib/committee/query_params_coercer.rb +0 -45
  82. data/lib/committee/request_validator.rb +0 -44
  83. data/lib/committee/response_generator.rb +0 -35
  84. data/lib/committee/response_validator.rb +0 -59
  85. data/lib/committee/router.rb +0 -62
  86. data/test/query_params_coercer_test.rb +0 -70
  87. data/test/request_validator_test.rb +0 -103
  88. data/test/response_generator_test.rb +0 -61
  89. data/test/router_test.rb +0 -38
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::Middleware::ResponseValidation do
6
+ include Rack::Test::Methods
7
+
8
+ CHARACTERS_RESPONSE = {"Otonokizaka" => ["Honoka.Kousaka"]}
9
+
10
+ def app
11
+ @app
12
+ end
13
+
14
+ it "passes through a valid response" do
15
+ @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, schema: open_api_3_schema)
16
+ get "/characters"
17
+ assert_equal 200, last_response.status
18
+ end
19
+
20
+ it "passes through a invalid json" do
21
+ @app = new_response_rack("not_json", {}, schema: open_api_3_schema)
22
+
23
+ get "/characters"
24
+
25
+ assert_equal 500, last_response.status
26
+ assert_equal "{\"id\":\"invalid_response\",\"message\":\"Response wasn't valid JSON.\"}", last_response.body
27
+ end
28
+
29
+ it "passes through a invalid json with ignore_error option" do
30
+ @app = new_response_rack("not_json", {}, schema: open_api_3_schema, ignore_error: true)
31
+
32
+ get "/characters"
33
+
34
+ assert_equal 200, last_response.status
35
+ end
36
+
37
+ it "passes through a invalid json with parse_response_by_content_type option" do
38
+ @app = new_response_rack("csv response", { "Content-Type" => "test/csv"}, schema: open_api_3_schema, parse_response_by_content_type: true)
39
+
40
+ get "/csv"
41
+
42
+ assert_equal 200, last_response.status
43
+ end
44
+
45
+ it "passes through not definition" do
46
+ @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, schema: open_api_3_schema)
47
+ get "/no_data"
48
+ assert_equal 200, last_response.status
49
+ end
50
+
51
+ it "detects a response invalid due to schema" do
52
+ @app = new_response_rack("[]", {}, schema: open_api_3_schema, raise: true)
53
+
54
+ e = assert_raises(Committee::InvalidResponse) {
55
+ get "/characters"
56
+ }
57
+
58
+ assert_match(/expected object, but received Array: /i, e.message)
59
+ end
60
+
61
+ it "passes through a 204 (no content) response" do
62
+ @app = new_response_rack("", {}, {schema: open_api_3_schema}, {status: 204})
63
+ post "/validate"
64
+ assert_equal 204, last_response.status
65
+ end
66
+
67
+ it "passes through a 304 (not modified) response" do
68
+ @app = new_response_rack("", {}, {schema: open_api_3_schema}, {status: 304})
69
+ post "/validate"
70
+ assert_equal 304, last_response.status
71
+ end
72
+
73
+ it "passes through a valid response with prefix" do
74
+ @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, schema: open_api_3_schema, prefix: "/v1")
75
+ get "/v1/characters"
76
+ assert_equal 200, last_response.status
77
+ end
78
+
79
+ it "passes through a invalid json with prefix" do
80
+ @app = new_response_rack("not_json", {}, schema: open_api_3_schema, prefix: "/v1")
81
+
82
+ get "/v1/characters"
83
+
84
+ assert_equal 500, last_response.status
85
+ assert_equal "{\"id\":\"invalid_response\",\"message\":\"Response wasn't valid JSON.\"}", last_response.body
86
+ end
87
+
88
+ it "rescues JSON errors" do
89
+ @app = new_response_rack("_42", {}, schema: open_api_3_schema, raise: true)
90
+ assert_raises(Committee::InvalidResponse) do
91
+ get "/characters"
92
+ end
93
+ end
94
+
95
+ it "not parameter request" do
96
+ @app = new_response_rack({integer: '1'}.to_json, {}, schema: open_api_3_schema, raise: true)
97
+
98
+ assert_raises(Committee::InvalidResponse) do
99
+ patch "/validate_no_parameter", {no_schema: 'no'}
100
+ end
101
+ end
102
+
103
+ it "optionally validates non-2xx blank responses" do
104
+ @app = new_response_rack("", {}, schema: open_api_3_schema, validate_success_only: false)
105
+ get "/characters"
106
+ assert_equal 200, last_response.status
107
+ end
108
+
109
+ it "optionally validates non-2xx invalid responses with invalid json" do
110
+ @app = new_response_rack("{_}", {}, schema: open_api_3_schema, validate_success_only: false)
111
+ get "/characters"
112
+ assert_equal 500, last_response.status
113
+ assert_match(/valid JSON/i, last_response.body)
114
+ end
115
+
116
+ describe "remote schema $ref" do
117
+ it "passes through a valid response" do
118
+ @app = new_response_rack(JSON.generate({ "sample" => "value" }), {}, schema: open_api_3_schema)
119
+ get "/ref-sample"
120
+ assert_equal 200, last_response.status
121
+ end
122
+
123
+ it "detects a invalid response" do
124
+ @app = new_response_rack("{}", {}, schema: open_api_3_schema)
125
+ get "/ref-sample"
126
+ assert_equal 500, last_response.status
127
+ end
128
+ end
129
+
130
+ describe 'check header' do
131
+ [
132
+ { check_header: true, description: 'valid value', header: { 'integer' => 1 }, expected: { status: 200 } },
133
+ { check_header: true, description: 'missing value', header: { 'integer' => nil }, expected: { error: 'headers/integer/schema does not allow null values' } },
134
+ { check_header: true, description: 'invalid value', header: { 'integer' => 'x' }, expected: { error: 'headers/integer/schema expected integer, but received String: "x"' } },
135
+
136
+ { check_header: false, description: 'valid value', header: { 'integer' => 1 }, expected: { status: 200 } },
137
+ { check_header: false, description: 'missing value', header: { 'integer' => nil }, expected: { status: 200 } },
138
+ { check_header: false, description: 'invalid value', header: { 'integer' => 'x' }, expected: { status: 200 } },
139
+ ].each do |h|
140
+ check_header = h[:check_header]
141
+ description = h[:description]
142
+ header = h[:header]
143
+ expected = h[:expected]
144
+ describe "when #{check_header}" do
145
+ %w(get post put patch delete options).each do |method|
146
+ describe method do
147
+ describe description do
148
+ if expected[:error].nil?
149
+ it 'should pass' do
150
+ @app = new_response_rack({}.to_json, header, schema: open_api_3_schema, raise: true, check_header: check_header)
151
+
152
+ send(method, "/header")
153
+ assert_equal expected[:status], last_response.status
154
+ end
155
+ else
156
+ it 'should fail' do
157
+ @app = new_response_rack({}.to_json, header, schema: open_api_3_schema, raise: true, check_header: check_header)
158
+
159
+ error = assert_raises(Committee::InvalidResponse) do
160
+ get "/header"
161
+ end
162
+ assert_match(expected[:error], error.message)
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ describe 'validate error option' do
173
+ it "detects an invalid response status code" do
174
+ @app = new_response_rack({ integer: '1' }.to_json,
175
+ {},
176
+ app_status: 400,
177
+ schema: open_api_3_schema,
178
+ raise: true,
179
+ validate_success_only: false)
180
+
181
+
182
+ e = assert_raises(Committee::InvalidResponse) do
183
+ get "/characters"
184
+ end
185
+
186
+ assert_match(/but received String: \"1\"/i, e.message)
187
+ end
188
+
189
+ it "detects an invalid response status code with validate_success_only=true" do
190
+ @app = new_response_rack({ string_1: :honoka }.to_json,
191
+ {},
192
+ app_status: 400,
193
+ schema: open_api_3_schema,
194
+ raise: true,
195
+ validate_success_only: true)
196
+
197
+
198
+ get "/characters"
199
+
200
+ assert_equal 400, last_response.status
201
+ end
202
+ end
203
+
204
+ describe ':accept_request_filter' do
205
+ [
206
+ { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 500 } },
207
+ { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/c') }, expected: { status: 500 } },
208
+ { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
209
+ ].each do |h|
210
+ description = h[:description]
211
+ accept_request_filter = h[:accept_request_filter]
212
+ expected = h[:expected]
213
+
214
+ it description do
215
+ @app = new_response_rack('not_json', {}, schema: open_api_3_schema, prefix: '/v1', accept_request_filter: accept_request_filter)
216
+
217
+ get 'v1/characters'
218
+
219
+ assert_equal expected[:status], last_response.status
220
+ end
221
+ end
222
+ end
223
+
224
+ it 'does not suppress application error' do
225
+ @app = Rack::Builder.new {
226
+ use Committee::Middleware::ResponseValidation, {schema: open_api_3_schema, raise: true}
227
+ run lambda { |_|
228
+ JSON.load('-') # invalid json
229
+ }
230
+ }
231
+
232
+ assert_raises(JSON::ParserError) do
233
+ get "/error", nil
234
+ end
235
+ end
236
+
237
+ it "strict and invalid status" do
238
+ @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, {schema: open_api_3_schema, strict: true}, {status: 201})
239
+ get "/characters"
240
+ assert_equal 500, last_response.status
241
+ end
242
+
243
+ it "strict and invalid status with raise" do
244
+ @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, {schema: open_api_3_schema, strict: true, raise: true}, {status: 201})
245
+
246
+ assert_raises(Committee::InvalidResponse) do
247
+ get "/characters"
248
+ end
249
+ end
250
+
251
+ it "strict and invalid content type" do
252
+ @app = new_response_rack("abc",
253
+ {},
254
+ {schema: open_api_3_schema, strict: true},
255
+ {content_type: 'application/text'}
256
+ )
257
+ get "/characters"
258
+ assert_equal 500, last_response.status
259
+ end
260
+
261
+ it "strict and invalid content type with raise" do
262
+ @app = new_response_rack("abc",
263
+ {},
264
+ {schema: open_api_3_schema, strict: true, raise: true},
265
+ {content_type: 'application/text'}
266
+ )
267
+
268
+ assert_raises(Committee::InvalidResponse) do
269
+ get "/characters"
270
+ end
271
+ end
272
+
273
+ private
274
+
275
+ def new_response_rack(response, headers = {}, options = {}, rack_options = {})
276
+ # TODO: delete when 5.0.0 released because default value changed
277
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
278
+
279
+ status = rack_options[:status] || 200
280
+ content_type = rack_options[:content_type] || "application/json"
281
+ headers = {
282
+ "Content-Type" => content_type
283
+ }.merge(headers)
284
+ Rack::Builder.new {
285
+ use Committee::Middleware::ResponseValidation, options
286
+ run lambda { |_|
287
+ [options.fetch(:app_status, status), headers, [response]]
288
+ }
289
+ }
290
+ end
291
+ end
@@ -1,4 +1,6 @@
1
- require_relative "../test_helper"
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
2
4
 
3
5
  describe Committee::Middleware::ResponseValidation do
4
6
  include Rack::Test::Methods
@@ -8,75 +10,175 @@ describe Committee::Middleware::ResponseValidation do
8
10
  end
9
11
 
10
12
  it "passes through a valid response" do
11
- @app = new_rack_app(JSON.generate([ValidApp]))
13
+ @app = new_rack_app(JSON.generate([ValidApp]), {}, schema: hyper_schema)
14
+ get "/apps"
15
+ assert_equal 200, last_response.status
16
+ end
17
+
18
+ # TODO: remove 5.0.0
19
+ it "passes through a valid response" do
20
+ # will show deprecated message
21
+ @app = new_rack_app(JSON.generate([ValidApp]), {}, schema: hyper_schema, strict: true)
22
+ get "/apps"
23
+ assert_equal 200, last_response.status
24
+ end
25
+
26
+ it "doesn't call error_handler (has a arg) when response is valid" do
27
+ called = false
28
+ pr = ->(_e) { called = true }
29
+ @app = new_rack_app(JSON.generate([ValidApp]), {}, schema: hyper_schema, error_handler: pr)
30
+ get "/apps"
31
+ assert !called, "error_handler is called"
32
+ end
33
+
34
+ it "doesn't call error_handler (has two args) when response is valid" do
35
+ called = false
36
+ pr = ->(_e, _env) { called = true }
37
+ @app = new_rack_app(JSON.generate([ValidApp]), {}, schema: hyper_schema, error_handler: pr)
38
+ get "/apps"
39
+ assert !called, "error_handler is called"
40
+ end
41
+
42
+ it "detects a response invalid due to schema" do
43
+ @app = new_rack_app("{}", {}, schema: hyper_schema)
44
+ get "/apps"
45
+ assert_equal 500, last_response.status
46
+ assert_match(/{} is not an array/i, last_response.body)
47
+ end
48
+
49
+ it "detects a response invalid due to schema with ignore_error option" do
50
+ @app = new_rack_app("{}", {}, schema: hyper_schema, ignore_error: true)
12
51
  get "/apps"
13
52
  assert_equal 200, last_response.status
14
53
  end
15
54
 
16
- it "detects an invalid response" do
17
- @app = new_rack_app("")
55
+ it "detects a response invalid due to not being JSON" do
56
+ @app = new_rack_app("{_}", {}, schema: hyper_schema)
18
57
  get "/apps"
19
58
  assert_equal 500, last_response.status
20
- assert_match /valid JSON/i, last_response.body
59
+ assert_match(/valid JSON/i, last_response.body)
21
60
  end
22
61
 
23
62
  it "ignores a non-2xx invalid response" do
24
- @app = new_rack_app("[]", {}, app_status: 404)
63
+ @app = new_rack_app("[]", {}, app_status: 404, schema: hyper_schema)
25
64
  get "/apps"
26
65
  assert_equal 404, last_response.status
27
66
  end
28
67
 
29
68
  it "optionally validates non-2xx invalid responses" do
30
- @app = new_rack_app("", {}, {app_status: 404, validate_errors: true})
69
+ @app = new_rack_app("", {}, app_status: 404, validate_success_only: false, schema: hyper_schema)
70
+ get "/apps"
71
+ assert_equal 500, last_response.status
72
+ assert_match(/Invalid response/i, last_response.body)
73
+ end
31
74
 
75
+ it "optionally validates non-2xx invalid responses with invalid json" do
76
+ @app = new_rack_app("{_}", {}, app_status: 404, validate_success_only: false, schema: hyper_schema)
32
77
  get "/apps"
33
78
  assert_equal 500, last_response.status
34
- assert_match /valid JSON/i, last_response.body
79
+ assert_match(/valid JSON/i, last_response.body)
35
80
  end
36
81
 
37
82
  it "passes through a 204 (no content) response" do
38
- @app = new_rack_app("", {}, app_status: 204)
83
+ @app = new_rack_app("", {}, app_status: 204, schema: hyper_schema)
39
84
  get "/apps"
40
85
  assert_equal 204, last_response.status
41
86
  end
42
87
 
88
+ it "passes through a 304 (not modified) response" do
89
+ @app = new_rack_app("", {}, app_status: 304, schema: hyper_schema)
90
+ get "/apps"
91
+ assert_equal 304, last_response.status
92
+ end
93
+
94
+ it "skip validation when 4xx" do
95
+ @app = new_rack_app("[{x:y}]", {}, schema: hyper_schema, validate_success_only: true, app_status: 400)
96
+ get "/apps"
97
+ assert_equal 400, last_response.status
98
+ assert_match("[{x:y}]", last_response.body)
99
+ end
100
+
43
101
  it "rescues JSON errors" do
44
- @app = new_rack_app("[{x:y}]")
102
+ @app = new_rack_app("[{x:y}]", {}, schema: hyper_schema, validate_success_only: false)
45
103
  get "/apps"
46
104
  assert_equal 500, last_response.status
47
- assert_match /valid json/i, last_response.body
105
+ assert_match(/valid json/i, last_response.body)
106
+ end
107
+
108
+ it "calls error_handler (has two args) when it rescues JSON errors" do
109
+ called_err = nil
110
+ pr = ->(e, _env) { called_err = e }
111
+ @app = new_rack_app("[{x:y}]", {}, schema: hyper_schema, error_handler: pr)
112
+ get "/apps"
113
+ assert_kind_of JSON::ParserError, called_err
48
114
  end
49
115
 
50
116
  it "takes a prefix" do
51
- @app = new_rack_app(JSON.generate([ValidApp]), {}, prefix: "/v1")
117
+ @app = new_rack_app(JSON.generate([ValidApp]), {}, prefix: "/v1",
118
+ schema: hyper_schema)
52
119
  get "/v1/apps"
53
120
  assert_equal 200, last_response.status
54
121
  end
55
122
 
56
- it "warns when sending a deprecated string" do
57
- mock(Committee).warn_deprecated.with_any_args
58
- @app = new_rack_app(JSON.generate([ValidApp]), {},
59
- schema: File.read("./test/data/schema.json"))
60
- get "/apps"
61
- assert_equal 200, last_response.status
123
+ it "rescues JSON errors" do
124
+ @app = new_rack_app("[{x:y}]", {}, raise: true, schema: hyper_schema)
125
+ assert_raises(Committee::InvalidResponse) do
126
+ get "/apps"
127
+ end
62
128
  end
63
129
 
64
- it "rescues JSON errors" do
65
- @app = new_rack_app("[{x:y}]", {}, raise: true)
130
+ it "calls error_handler (has two args) when it rescues JSON errors" do
131
+ called_err = nil
132
+ pr = ->(e, _env) { called_err = e }
133
+ @app = new_rack_app("[{x:y}]", {}, raise: true, schema: hyper_schema, error_handler: pr)
66
134
  assert_raises(Committee::InvalidResponse) do
67
135
  get "/apps"
136
+ assert_kind_of JSON::ParserError, called_err
137
+ end
138
+ end
139
+
140
+ it "passes through a valid response for OpenAPI" do
141
+ @app = new_rack_app(JSON.generate([ValidPet]), {},
142
+ schema: open_api_2_schema)
143
+ get "/api/pets"
144
+ assert_equal 200, last_response.status
145
+ end
146
+
147
+ it "detects an invalid response for OpenAPI" do
148
+ @app = new_rack_app("{_}", {}, schema: open_api_2_schema)
149
+ get "/api/pets"
150
+ assert_equal 500, last_response.status
151
+ assert_match(/valid JSON/i, last_response.body)
152
+ end
153
+
154
+ describe ':accept_request_filter' do
155
+ [
156
+ { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 500 } },
157
+ { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/a') }, expected: { status: 500 } },
158
+ { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
159
+ ].each do |h|
160
+ description = h[:description]
161
+ accept_request_filter = h[:accept_request_filter]
162
+ expected = h[:expected]
163
+ it description do
164
+ @app = new_rack_app('not_json', {}, schema: hyper_schema, prefix: '/v1', accept_request_filter: accept_request_filter)
165
+
166
+ get '/v1/apps'
167
+
168
+ assert_equal expected[:status], last_response.status
169
+ end
68
170
  end
69
171
  end
70
172
 
71
173
  private
72
174
 
73
175
  def new_rack_app(response, headers = {}, options = {})
176
+ # TODO: delete when 5.0.0 released because default value changed
177
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
178
+
74
179
  headers = {
75
180
  "Content-Type" => "application/json"
76
181
  }.merge(headers)
77
- options = {
78
- schema: JSON.parse(File.read("./test/data/schema.json"))
79
- }.merge(options)
80
182
  Rack::Builder.new {
81
183
  use Committee::Middleware::ResponseValidation, options
82
184
  run lambda { |_|
@@ -1,4 +1,6 @@
1
- require_relative "../test_helper"
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
2
4
 
3
5
  describe Committee::Middleware::Stub do
4
6
  include Rack::Test::Methods
@@ -8,7 +10,7 @@ describe Committee::Middleware::Stub do
8
10
  end
9
11
 
10
12
  it "responds with a stubbed response" do
11
- @app = new_rack_app
13
+ @app = new_rack_app(schema: hyper_schema)
12
14
  get "/apps/heroku-api"
13
15
  assert_equal 200, last_response.status
14
16
  data = JSON.parse(last_response.body)
@@ -16,13 +18,13 @@ describe Committee::Middleware::Stub do
16
18
  end
17
19
 
18
20
  it "responds with 201 on create actions" do
19
- @app = new_rack_app
21
+ @app = new_rack_app(schema: hyper_schema)
20
22
  post "/apps"
21
23
  assert_equal 201, last_response.status
22
24
  end
23
25
 
24
26
  it "optionally calls into application" do
25
- @app = new_rack_app(call: true)
27
+ @app = new_rack_app(call: true, schema: hyper_schema)
26
28
  get "/apps/heroku-api"
27
29
  assert_equal 200, last_response.status
28
30
  assert_equal ValidApp,
@@ -30,37 +32,79 @@ describe Committee::Middleware::Stub do
30
32
  end
31
33
 
32
34
  it "optionally returns the application's response" do
33
- @app = new_rack_app(call: true, suppress: true)
35
+ @app = new_rack_app(call: true, schema: hyper_schema, suppress: true)
34
36
  get "/apps/heroku-api"
35
37
  assert_equal 429, last_response.status
36
38
  assert_equal ValidApp,
37
39
  JSON.parse(last_response.headers["Committee-Response"])
38
- assert_equal "", last_response.body
40
+ assert_equal "", last_response.body
41
+ end
42
+
43
+ it "optionally returns the application's response schema" do
44
+ @app = new_rack_app(call: true, schema: hyper_schema, suppress: true)
45
+ get "/apps/heroku-api"
46
+ assert_equal 429, last_response.status
47
+ assert_equal "#/definitions/app/links/2/targetSchema",
48
+ last_response.headers["Committee-Response-Schema"]
49
+ assert_equal "", last_response.body
39
50
  end
40
51
 
41
52
  it "takes a prefix" do
42
- @app = new_rack_app(prefix: "/v1")
53
+ @app = new_rack_app(prefix: "/v1", schema: hyper_schema)
43
54
  get "/v1/apps/heroku-api"
44
55
  assert_equal 200, last_response.status
45
56
  data = JSON.parse(last_response.body)
46
57
  assert_equal ValidApp.keys.sort, data.keys.sort
47
58
  end
48
59
 
49
- it "warns when sending a deprecated string" do
50
- mock(Committee).warn_deprecated.with_any_args
51
- @app = new_rack_app(schema: File.read("./test/data/schema.json"))
60
+ it "allows the stub's response to be replaced" do
61
+ response = { replaced: true }
62
+ @app = new_rack_app(call: true, response: response, schema: hyper_schema)
52
63
  get "/apps/heroku-api"
53
64
  assert_equal 200, last_response.status
65
+ assert_equal response, JSON.parse(last_response.body, symbolize_names: true)
66
+ end
67
+
68
+ it "responds with a stubbed response for OpenAPI" do
69
+ @app = new_rack_app(schema: open_api_2_schema)
70
+ get "/api/pets/fido"
71
+ assert_equal 200, last_response.status
54
72
  data = JSON.parse(last_response.body)
55
- assert_equal ValidApp.keys.sort, data.keys.sort
73
+ assert_equal ValidPet.keys.sort, data.keys.sort
56
74
  end
57
75
 
58
- it "allows the stub's response to be replaced" do
59
- response = { replaced: true }
60
- @app = new_rack_app(call: true, response: response)
76
+ it "calls into app for links that are undefined" do
77
+ @app = new_rack_app(call: false, schema: hyper_schema)
78
+ post "/foos"
79
+ assert_equal 429, last_response.status
80
+ end
81
+
82
+ it "caches the response if called multiple times" do
83
+ cache = {}
84
+ @app = new_rack_app(cache: cache, schema: hyper_schema)
85
+
61
86
  get "/apps/heroku-api"
62
87
  assert_equal 200, last_response.status
63
- assert_equal response, JSON.parse(last_response.body, symbolize_names: true)
88
+
89
+ data, _schema = cache[cache.first[0]]
90
+ assert_equal ValidApp.keys.sort, data.keys.sort
91
+
92
+ # replace what we have in the cache
93
+ cache[cache.first[0]] = { "cached" => true }
94
+
95
+ get "/apps/heroku-api"
96
+ assert_equal 200, last_response.status
97
+ data = JSON.parse(last_response.body)
98
+ assert_equal({ "cached" => true }, data)
99
+ end
100
+
101
+ it "caches the response if called multiple times" do
102
+ cache = {}
103
+ @app = new_rack_app(cache: cache, schema: open_api_3_schema)
104
+
105
+ assert_raises(Committee::OpenAPI3Unsupported) do
106
+ get "/characters"
107
+ end
64
108
  end
65
109
 
66
110
  private
@@ -68,15 +112,32 @@ describe Committee::Middleware::Stub do
68
112
  def new_rack_app(options = {})
69
113
  response = options.delete(:response)
70
114
  suppress = options.delete(:suppress)
71
- options = {
72
- schema: JSON.parse(File.read("./test/data/schema.json"))
73
- }.merge(options)
115
+
116
+ # TODO: delete when 5.0.0 released because default value changed
117
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
118
+
74
119
  Rack::Builder.new {
75
120
  use Committee::Middleware::Stub, options
76
121
  run lambda { |env|
77
- env["committee.response"] = response if response
78
- headers = { "Committee-Response" => JSON.generate(env["committee.response"]) }
122
+ if response
123
+ env["committee.response"] = response
124
+ end
125
+
126
+ headers = {}
127
+ if res = env["committee.response"]
128
+ headers.merge!({
129
+ "Committee-Response" => JSON.generate(res)
130
+ })
131
+ end
132
+
133
+ if res = env["committee.response_schema"]
134
+ headers.merge!({
135
+ "Committee-Response-Schema" => res.pointer,
136
+ })
137
+ end
138
+
79
139
  env["committee.suppress"] = suppress
140
+
80
141
  [429, headers, []]
81
142
  }
82
143
  }