committee 3.3.0 → 5.0.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/committee/drivers/open_api_2/driver.rb +1 -2
  3. data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +1 -1
  4. data/lib/committee/drivers.rb +22 -10
  5. data/lib/committee/errors.rb +12 -0
  6. data/lib/committee/middleware/base.rb +5 -4
  7. data/lib/committee/middleware/request_validation.rb +4 -18
  8. data/lib/committee/middleware/response_validation.rb +15 -16
  9. data/lib/committee/request_unpacker.rb +46 -60
  10. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +8 -2
  11. data/lib/committee/schema_validator/hyper_schema.rb +41 -27
  12. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +44 -37
  13. data/lib/committee/schema_validator/open_api_3/request_validator.rb +11 -2
  14. data/lib/committee/schema_validator/open_api_3/router.rb +3 -1
  15. data/lib/committee/schema_validator/open_api_3.rb +52 -26
  16. data/lib/committee/schema_validator/option.rb +14 -3
  17. data/lib/committee/schema_validator.rb +1 -1
  18. data/lib/committee/test/methods.rb +27 -16
  19. data/lib/committee/test/schema_coverage.rb +101 -0
  20. data/lib/committee/utils.rb +28 -0
  21. data/lib/committee/validation_error.rb +3 -2
  22. data/lib/committee/version.rb +5 -0
  23. data/lib/committee.rb +11 -4
  24. data/test/bin/committee_stub_test.rb +5 -1
  25. data/test/committee_test.rb +29 -3
  26. data/test/drivers/open_api_3/driver_test.rb +1 -1
  27. data/test/drivers_test.rb +20 -7
  28. data/test/middleware/base_test.rb +9 -10
  29. data/test/middleware/request_validation_open_api_3_test.rb +175 -18
  30. data/test/middleware/request_validation_test.rb +20 -28
  31. data/test/middleware/response_validation_open_api_3_test.rb +96 -7
  32. data/test/middleware/response_validation_test.rb +21 -26
  33. data/test/middleware/stub_test.rb +4 -0
  34. data/test/request_unpacker_test.rb +51 -110
  35. data/test/schema_validator/hyper_schema/response_validator_test.rb +10 -0
  36. data/test/schema_validator/hyper_schema/router_test.rb +4 -0
  37. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +1 -1
  38. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +72 -20
  39. data/test/schema_validator/open_api_3/request_validator_test.rb +27 -0
  40. data/test/schema_validator/open_api_3/response_validator_test.rb +26 -5
  41. data/test/test/methods_new_version_test.rb +17 -5
  42. data/test/test/methods_test.rb +155 -31
  43. data/test/test/schema_coverage_test.rb +216 -0
  44. data/test/test_helper.rb +34 -4
  45. metadata +47 -15
@@ -34,11 +34,27 @@ describe Committee::Middleware::RequestValidation do
34
34
  params = { "datetime_string" => "2016-04-01T16:00:00.000+09:00" }
35
35
 
36
36
  check_parameter = lambda { |env|
37
+ assert_equal DateTime, env['test.query_hash']["datetime_string"].class
38
+ assert_equal String, env['rack.request.query_hash']["datetime_string"].class
39
+ [200, {}, []]
40
+ }
41
+
42
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_3_schema, coerce_date_times: true, query_hash_key: "test.query_hash")
43
+
44
+ get "/string_params_coercer", params
45
+ assert_equal 200, last_response.status
46
+ end
47
+
48
+ it "passes given a datetime and with coerce_date_times enabled on GET endpoint overwrite query_hash" do
49
+ params = { "datetime_string" => "2016-04-01T16:00:00.000+09:00" }
50
+
51
+ check_parameter = lambda { |env|
52
+ assert_nil env['committee.query_hash']
37
53
  assert_equal DateTime, env['rack.request.query_hash']["datetime_string"].class
38
54
  [200, {}, []]
39
55
  }
40
56
 
41
- @app = new_rack_app_with_lambda(check_parameter, schema: open_api_3_schema, coerce_date_times: true)
57
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_3_schema, coerce_date_times: true, query_hash_key: "rack.request.query_hash")
42
58
 
43
59
  get "/string_params_coercer", params
44
60
  assert_equal 200, last_response.status
@@ -49,7 +65,7 @@ describe Committee::Middleware::RequestValidation do
49
65
 
50
66
  @app = new_rack_app(schema: open_api_3_schema, allow_get_body: true)
51
67
 
52
- get "/get_endpoint_with_requered_parameter", { no_problem: true }, { input: params.to_json }
68
+ get "/get_endpoint_with_required_parameter", { no_problem: true }, { input: params.to_json }
53
69
  assert_equal 200, last_response.status
54
70
  end
55
71
 
@@ -58,7 +74,7 @@ describe Committee::Middleware::RequestValidation do
58
74
 
59
75
  @app = new_rack_app(schema: open_api_3_schema, allow_get_body: false)
60
76
 
61
- get "/get_endpoint_with_requered_parameter", { no_problem: true }, { input: params.to_json }
77
+ get "/get_endpoint_with_required_parameter", { no_problem: true }, { input: params.to_json }
62
78
  assert_equal 400, last_response.status
63
79
  end
64
80
 
@@ -154,7 +170,7 @@ describe Committee::Middleware::RequestValidation do
154
170
  }
155
171
 
156
172
  check_parameter = lambda { |env|
157
- hash = env['rack.request.query_hash']
173
+ hash = env["committee.query_hash"]
158
174
  assert_equal DateTime, hash['nested_array'].first['update_time'].class
159
175
  assert_equal 1, hash['nested_array'].first['per_page']
160
176
 
@@ -249,8 +265,7 @@ describe Committee::Middleware::RequestValidation do
249
265
  }
250
266
  post "/characters", JSON.generate(params)
251
267
  assert_equal 400, last_response.status
252
- # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
253
- assert_match(/expected string, but received #{1.class}:/i, last_response.body)
268
+ assert_match(/expected string, but received Integer:/i, last_response.body)
254
269
  end
255
270
 
256
271
  it "rescues JSON errors" do
@@ -279,8 +294,7 @@ describe Committee::Middleware::RequestValidation do
279
294
  header "Content-Type", "application/json"
280
295
  post "/v1/characters", JSON.generate(params)
281
296
  assert_equal 400, last_response.status
282
- # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
283
- assert_match(/expected string, but received #{1.class}: /i, last_response.body)
297
+ assert_match(/expected string, but received Integer: /i, last_response.body)
284
298
  end
285
299
 
286
300
  it "ignores paths outside the prefix" do
@@ -333,7 +347,7 @@ describe Committee::Middleware::RequestValidation do
333
347
  get "/coerce_path_params/#{not_an_integer}", nil
334
348
  end
335
349
 
336
- assert_match(/expected integer, but received String: abc/i, e.message)
350
+ assert_match(/expected integer, but received String: \"abc\"/i, e.message)
337
351
  end
338
352
 
339
353
  it "optionally raises an error" do
@@ -362,7 +376,7 @@ describe Committee::Middleware::RequestValidation do
362
376
 
363
377
  it "passes through a valid request for OpenAPI3" do
364
378
  check_parameter = lambda { |env|
365
- assert_equal 3, env['rack.request.query_hash']['limit']
379
+ assert_equal 3, env['committee.query_hash']['limit'] #5.0.x-
366
380
  [200, {}, []]
367
381
  }
368
382
 
@@ -376,7 +390,7 @@ describe Committee::Middleware::RequestValidation do
376
390
  get "/characters?limit=foo"
377
391
 
378
392
  assert_equal 400, last_response.status
379
- assert_match(/expected integer, but received String: foo/i, last_response.body)
393
+ assert_match(/expected integer, but received String: \\"foo\\"/i, last_response.body)
380
394
  end
381
395
 
382
396
  it "ignores errors when ignore_error: true" do
@@ -396,28 +410,155 @@ describe Committee::Middleware::RequestValidation do
396
410
  get "/coerce_path_params/1"
397
411
  end
398
412
 
413
+ describe "overwrite same parameter (old rule)" do
414
+ # (high priority) path_hash_key -> request_body_hash -> query_param
415
+ it "set query parameter to committee.params and query hash" do
416
+ @app = new_rack_app_with_lambda(lambda do |env|
417
+ assert_equal env['committee.params']['integer'], 42
418
+ assert_equal env['committee.params'][:integer], 42
419
+ assert_equal env['committee.query_hash']['integer'], 42
420
+ #assert_equal env['rack.request.query_hash'][:integer], 42 # this isn't hash indifferent hash because we use rack.request.query_hash
421
+ [204, {}, []]
422
+ end, schema: open_api_3_schema, parameter_overwite_by_rails_rule: false)
423
+
424
+ header "Content-Type", "application/json"
425
+ post '/overwrite_same_parameter?integer=42'
426
+ assert_equal 204, last_response.status
427
+ end
428
+
429
+ it "request body precedence over query parameter" do
430
+ @app = new_rack_app_with_lambda(lambda do |env|
431
+ assert_equal env['committee.params']['integer'], 21
432
+ assert_equal env['committee.params'][:integer], 21
433
+ assert_equal env['committee.request_body_hash']['integer'], 21
434
+ assert_equal env['committee.request_body_hash'][:integer], 21
435
+ assert_equal env['committee.query_hash']['integer'], 42
436
+ [204, {}, []]
437
+ end, schema: open_api_3_schema, parameter_overwite_by_rails_rule: false)
438
+
439
+ params = {integer: 21}
440
+
441
+ header "Content-Type", "application/json"
442
+ post '/overwrite_same_parameter?integer=42', JSON.generate(params)
443
+ assert_equal 204, last_response.status
444
+ end
445
+
446
+ it "path parameter precedence over request body" do
447
+ @app = new_rack_app_with_lambda(lambda do |env|
448
+ assert_equal env['committee.params']['integer'], 84
449
+ assert_equal env['committee.params'][:integer], 84
450
+ assert_equal env['committee.path_hash']['integer'], 84
451
+ assert_equal env['committee.path_hash'][:integer], 84
452
+ assert_equal env['committee.request_body_hash']['integer'], 21
453
+ assert_equal env['committee.request_body_hash'][:integer], 21
454
+ assert_equal env['committee.query_hash']['integer'], 84 # we can't use query_parameter :(
455
+ #assert_equal env['rack.request.query_hash'][:integer], 21 # this isn't hash indifferent hash because we use rack.request.query_hash
456
+ [204, {}, []]
457
+ end, schema: open_api_3_schema, parameter_overwite_by_rails_rule: false)
458
+
459
+ params = {integer: 21}
460
+
461
+ header "Content-Type", "application/json"
462
+ post '/overwrite_same_parameter/84?integer=42', JSON.generate(params)
463
+ assert_equal 204, last_response.status
464
+ end
465
+ end
466
+
467
+ describe "overwrite same parameter (new rule and seme to Rails)" do
468
+ # (high priority) path_hash_key -> query_param -> request_body_hash
469
+ it "set request body to committee.params and query hash" do
470
+ @app = new_rack_app_with_lambda(lambda do |env|
471
+ assert_equal env['committee.params']['integer'], 21
472
+ assert_equal env['committee.params'][:integer], 21
473
+ assert_equal env['committee.request_body_hash']['integer'], 21
474
+ assert_equal env['committee.request_body_hash'][:integer], 21
475
+ [204, {}, []]
476
+ end, schema: open_api_3_schema)
477
+
478
+ params = {integer: 21}
479
+
480
+ header "Content-Type", "application/json"
481
+ post '/overwrite_same_parameter', JSON.generate(params)
482
+ assert_equal 204, last_response.status
483
+ end
484
+
485
+ it "query parameter precedence over request body" do
486
+ @app = new_rack_app_with_lambda(lambda do |env|
487
+ assert_equal env['committee.params']['integer'], 42
488
+ assert_equal env['committee.params'][:integer], 42
489
+ assert_equal env['committee.request_body_hash']['integer'], 21
490
+ assert_equal env['committee.request_body_hash'][:integer], 21
491
+ assert_equal env['committee.query_hash']['integer'], 42
492
+ [204, {}, []]
493
+ end, schema: open_api_3_schema)
494
+
495
+ params = {integer: 21}
496
+
497
+ header "Content-Type", "application/json"
498
+ post '/overwrite_same_parameter?integer=42', JSON.generate(params)
499
+ assert_equal 204, last_response.status
500
+ end
501
+
502
+ it "path path parameter precedence over query parameter" do
503
+ @app = new_rack_app_with_lambda(lambda do |env|
504
+ assert_equal env['committee.params']['integer'], 84
505
+ assert_equal env['committee.params'][:integer], 84
506
+ assert_equal env['committee.request_body_hash']['integer'], 21
507
+ assert_equal env['committee.request_body_hash'][:integer], 21
508
+ assert_equal env['committee.query_hash']['integer'], 84 # we can't use query_parameter :(
509
+ assert_equal env['committee.path_hash']['integer'], 84
510
+ assert_equal env['committee.path_hash'][:integer], 84
511
+ [204, {}, []]
512
+ end, schema: open_api_3_schema)
513
+
514
+ params = {integer: 21}
515
+
516
+ header "Content-Type", "application/json"
517
+ post '/overwrite_same_parameter/84?integer=42', JSON.generate(params)
518
+ assert_equal 204, last_response.status
519
+ end
520
+ end
521
+
522
+ it "unpacker test" do
523
+ @app = new_rack_app_with_lambda(lambda do |env|
524
+ assert_equal '21', env['committee.params']['integer'] # query parameter has precedence
525
+ assert_equal '21', env['committee.params'][:integer]
526
+ assert_equal '21', env['rack.request.query_hash']['integer']
527
+ assert_equal 42, env['committee.request_body_hash']['integer']
528
+ [204, {}, []]
529
+ end, schema: open_api_3_schema, raise: true)
530
+
531
+ header "Content-Type", "application/x-www-form-urlencoded"
532
+ post '/validate?integer=21', "integer=42"
533
+ assert_equal 204, last_response.status
534
+ end
535
+
399
536
  it "OpenAPI3 raise not support method" do
400
537
  @app = new_rack_app(schema: open_api_3_schema)
401
538
 
402
539
  e = assert_raises(RuntimeError) {
403
- head "/characters", {}
540
+ custom_request('TRACE', "/characters")
404
541
  }
405
542
 
406
- assert_equal 'Committee OpenAPI3 not support head method', e.message
407
- end
543
+ assert_equal 'Committee OpenAPI3 not support trace method', e.message
544
+ end
408
545
 
409
546
  describe 'check header' do
410
547
  [
411
548
  { check_header: true, description: 'valid value', value: 1, expected: { status: 200 } },
412
549
  { check_header: true, description: 'missing value', value: nil, expected: { status: 400, error: 'missing required parameters: integer' } },
413
- { check_header: true, description: 'invalid value', value: 'x', expected: { status: 400, error: 'expected integer, but received String: x' } },
550
+ { check_header: true, description: 'invalid value', value: 'x', expected: { status: 400, error: 'expected integer, but received String: \\"x\\"' } },
414
551
 
415
552
  { check_header: false, description: 'valid value', value: 1, expected: { status: 200 } },
416
553
  { check_header: false, description: 'missing value', value: nil, expected: { status: 200 } },
417
554
  { check_header: false, description: 'invalid value', value: 'x', expected: { status: 200 } },
418
- ].each do |check_header:, description:, value:, expected:|
555
+ ].each do |h|
556
+ check_header = h[:check_header]
557
+ description = h[:description]
558
+ value = h[:value]
559
+ expected = h[:expected]
419
560
  describe "when #{check_header}" do
420
- %w(get post put patch delete).each do |method|
561
+ %w(get post put patch delete options).each do |method|
421
562
  describe method do
422
563
  describe description do
423
564
  it (expected[:error].nil? ? 'should pass' : 'should fail') do
@@ -441,7 +582,10 @@ describe Committee::Middleware::RequestValidation do
441
582
  { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 400 } },
442
583
  { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/c') }, expected: { status: 400 } },
443
584
  { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
444
- ].each do |description:, accept_request_filter:, expected:|
585
+ ].each do |h|
586
+ description = h[:description]
587
+ accept_request_filter = h[:accept_request_filter]
588
+ expected = h[:expected]
445
589
  it description do
446
590
  @app = new_rack_app(prefix: '/v1', schema: open_api_3_schema, accept_request_filter: accept_request_filter)
447
591
 
@@ -452,6 +596,16 @@ describe Committee::Middleware::RequestValidation do
452
596
  end
453
597
  end
454
598
 
599
+ it 'does not suppress application error' do
600
+ @app = new_rack_app_with_lambda(lambda { |_|
601
+ JSON.load('-') # invalid json
602
+ }, schema: open_api_3_schema, raise: true)
603
+
604
+ assert_raises(JSON::ParserError) do
605
+ get "/error", nil
606
+ end
607
+ end
608
+
455
609
  private
456
610
 
457
611
  def new_rack_app(options = {})
@@ -461,6 +615,9 @@ describe Committee::Middleware::RequestValidation do
461
615
  end
462
616
 
463
617
  def new_rack_app_with_lambda(check_lambda, options = {})
618
+ # TODO: delete when 5.0.0 released because default value changed
619
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
620
+
464
621
  Rack::Builder.new {
465
622
  use Committee::Middleware::RequestValidation, options
466
623
  run check_lambda
@@ -306,21 +306,6 @@ describe Committee::Middleware::RequestValidation do
306
306
  assert_equal 200, last_response.status
307
307
  end
308
308
 
309
- it "calls error_handler (has a arg) when request is invalid" do
310
- called_err = nil
311
- pr = ->(e) { called_err = e }
312
- @app = new_rack_app(schema: hyper_schema, error_handler: pr)
313
- header "Content-Type", "application/json"
314
- params = {
315
- "name" => 1
316
- }
317
- _, err = capture_io do
318
- post "/apps", JSON.generate(params)
319
- end
320
- assert_kind_of Committee::InvalidRequest, called_err
321
- assert_match(/\[DEPRECATION\]/i, err)
322
- end
323
-
324
309
  it "calls error_handler (has two args) when request is invalid" do
325
310
  called_err = nil
326
311
  pr = ->(e, _env) { called_err = e }
@@ -341,18 +326,6 @@ describe Committee::Middleware::RequestValidation do
341
326
  assert_match(/valid json/i, last_response.body)
342
327
  end
343
328
 
344
- it "calls error_handler (has a arg) when it rescues JSON errors" do
345
- called_err = nil
346
- pr = ->(e) { called_err = e }
347
- @app = new_rack_app(schema: hyper_schema, error_handler: pr)
348
- header "Content-Type", "application/json"
349
- _, err = capture_io do
350
- post "/apps", "{x:y}"
351
- end
352
- assert_kind_of JSON::ParserError, called_err
353
- assert_match(/\[DEPRECATION\]/i, err)
354
- end
355
-
356
329
  it "calls error_handler (has two args) when it rescues JSON errors" do
357
330
  called_err = nil
358
331
  pr = ->(e, _env) { called_err = e }
@@ -435,6 +408,19 @@ describe Committee::Middleware::RequestValidation do
435
408
  assert_equal 200, last_response.status
436
409
  end
437
410
 
411
+ it "coerce form params" do
412
+ check_parameter = lambda { |env|
413
+ assert_equal 3, env['committee.params']['age']
414
+ assert_equal 3, env['committee.request_body_hash']['age']
415
+ [200, {}, []]
416
+ }
417
+
418
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_2_form_schema, raise: true, allow_form_params: true, coerce_form_params: true)
419
+ header "Content-Type", "application/x-www-form-urlencoded"
420
+ post "/api/pets", "age=3&name=ab"
421
+ assert_equal 200, last_response.status
422
+ end
423
+
438
424
  it "detects an invalid request for OpenAPI" do
439
425
  @app = new_rack_app(schema: open_api_2_schema)
440
426
  get "/api/pets?limit=foo", nil, { "HTTP_AUTH_TOKEN" => "xxx" }
@@ -495,7 +481,10 @@ describe Committee::Middleware::RequestValidation do
495
481
  { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 400 } },
496
482
  { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/a') }, expected: { status: 400 } },
497
483
  { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
498
- ].each do |description:, accept_request_filter:, expected:|
484
+ ].each do |h|
485
+ description = h[:description]
486
+ accept_request_filter = h[:accept_request_filter]
487
+ expected = h[:expected]
499
488
  it description do
500
489
  @app = new_rack_app(prefix: '/v1', schema: hyper_schema, accept_request_filter: accept_request_filter)
501
490
 
@@ -516,6 +505,9 @@ describe Committee::Middleware::RequestValidation do
516
505
 
517
506
 
518
507
  def new_rack_app_with_lambda(check_lambda, options = {})
508
+ # TODO: delete when 5.0.0 released because default value changed
509
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
510
+
519
511
  Rack::Builder.new {
520
512
  use Committee::Middleware::RequestValidation, options
521
513
  run check_lambda
@@ -34,6 +34,14 @@ describe Committee::Middleware::ResponseValidation do
34
34
  assert_equal 200, last_response.status
35
35
  end
36
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
+
37
45
  it "passes through not definition" do
38
46
  @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, schema: open_api_3_schema)
39
47
  get "/no_data"
@@ -56,6 +64,12 @@ describe Committee::Middleware::ResponseValidation do
56
64
  assert_equal 204, last_response.status
57
65
  end
58
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
+
59
73
  it "passes through a valid response with prefix" do
60
74
  @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, schema: open_api_3_schema, prefix: "/v1")
61
75
  get "/v1/characters"
@@ -78,7 +92,7 @@ describe Committee::Middleware::ResponseValidation do
78
92
  end
79
93
  end
80
94
 
81
- it "not parameter requset" do
95
+ it "not parameter request" do
82
96
  @app = new_response_rack({integer: '1'}.to_json, {}, schema: open_api_3_schema, raise: true)
83
97
 
84
98
  assert_raises(Committee::InvalidResponse) do
@@ -99,18 +113,36 @@ describe Committee::Middleware::ResponseValidation do
99
113
  assert_match(/valid JSON/i, last_response.body)
100
114
  end
101
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
+
102
130
  describe 'check header' do
103
131
  [
104
132
  { check_header: true, description: 'valid value', header: { 'integer' => 1 }, expected: { status: 200 } },
105
133
  { check_header: true, description: 'missing value', header: { 'integer' => nil }, expected: { error: 'headers/integer/schema does not allow null values' } },
106
- { check_header: true, description: 'invalid value', header: { 'integer' => 'x' }, expected: { error: 'headers/integer/schema expected integer, but received String: x' } },
134
+ { check_header: true, description: 'invalid value', header: { 'integer' => 'x' }, expected: { error: 'headers/integer/schema expected integer, but received String: "x"' } },
107
135
 
108
136
  { check_header: false, description: 'valid value', header: { 'integer' => 1 }, expected: { status: 200 } },
109
137
  { check_header: false, description: 'missing value', header: { 'integer' => nil }, expected: { status: 200 } },
110
138
  { check_header: false, description: 'invalid value', header: { 'integer' => 'x' }, expected: { status: 200 } },
111
- ].each do |check_header:, description:, header:, expected:|
139
+ ].each do |h|
140
+ check_header = h[:check_header]
141
+ description = h[:description]
142
+ header = h[:header]
143
+ expected = h[:expected]
112
144
  describe "when #{check_header}" do
113
- %w(get post put patch delete).each do |method|
145
+ %w(get post put patch delete options).each do |method|
114
146
  describe method do
115
147
  describe description do
116
148
  if expected[:error].nil?
@@ -151,7 +183,7 @@ describe Committee::Middleware::ResponseValidation do
151
183
  get "/characters"
152
184
  end
153
185
 
154
- assert_match(/but received String: 1/i, e.message)
186
+ assert_match(/but received String: \"1\"/i, e.message)
155
187
  end
156
188
 
157
189
  it "detects an invalid response status code with validate_success_only=true" do
@@ -174,7 +206,11 @@ describe Committee::Middleware::ResponseValidation do
174
206
  { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 500 } },
175
207
  { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/c') }, expected: { status: 500 } },
176
208
  { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
177
- ].each do |description:, accept_request_filter:, expected:|
209
+ ].each do |h|
210
+ description = h[:description]
211
+ accept_request_filter = h[:accept_request_filter]
212
+ expected = h[:expected]
213
+
178
214
  it description do
179
215
  @app = new_response_rack('not_json', {}, schema: open_api_3_schema, prefix: '/v1', accept_request_filter: accept_request_filter)
180
216
 
@@ -185,12 +221,65 @@ describe Committee::Middleware::ResponseValidation do
185
221
  end
186
222
  end
187
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
+
188
273
  private
189
274
 
190
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
+
191
279
  status = rack_options[:status] || 200
280
+ content_type = rack_options[:content_type] || "application/json"
192
281
  headers = {
193
- "Content-Type" => "application/json"
282
+ "Content-Type" => content_type
194
283
  }.merge(headers)
195
284
  Rack::Builder.new {
196
285
  use Committee::Middleware::ResponseValidation, options
@@ -15,6 +15,14 @@ describe Committee::Middleware::ResponseValidation do
15
15
  assert_equal 200, last_response.status
16
16
  end
17
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
+
18
26
  it "doesn't call error_handler (has a arg) when response is valid" do
19
27
  called = false
20
28
  pr = ->(_e) { called = true }
@@ -77,6 +85,12 @@ describe Committee::Middleware::ResponseValidation do
77
85
  assert_equal 204, last_response.status
78
86
  end
79
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
+
80
94
  it "skip validation when 4xx" do
81
95
  @app = new_rack_app("[{x:y}]", {}, schema: hyper_schema, validate_success_only: true, app_status: 400)
82
96
  get "/apps"
@@ -91,17 +105,6 @@ describe Committee::Middleware::ResponseValidation do
91
105
  assert_match(/valid json/i, last_response.body)
92
106
  end
93
107
 
94
- it "calls error_handler (has a arg) when it rescues JSON errors" do
95
- called_err = nil
96
- pr = ->(e) { called_err = e }
97
- @app = new_rack_app("[{x:y}]", {}, schema: hyper_schema, error_handler: pr)
98
- _, err = capture_io do
99
- get "/apps"
100
- end
101
- assert_kind_of JSON::ParserError, called_err
102
- assert_match(/\[DEPRECATION\]/i, err)
103
- end
104
-
105
108
  it "calls error_handler (has two args) when it rescues JSON errors" do
106
109
  called_err = nil
107
110
  pr = ->(e, _env) { called_err = e }
@@ -124,20 +127,6 @@ describe Committee::Middleware::ResponseValidation do
124
127
  end
125
128
  end
126
129
 
127
- it "calls error_handler (has a arg) when it rescues JSON errors" do
128
- called_err = nil
129
- pr = ->(e) { called_err = e }
130
- @app = new_rack_app("[{x:y}]", {}, raise: true, schema: hyper_schema, error_handler: pr)
131
- assert_raises(Committee::InvalidResponse) do
132
- _, err = capture_io do
133
- get "/apps"
134
- end
135
-
136
- assert_kind_of JSON::ParserError, called_err
137
- assert_match(/\[DEPRECATION\]/i, err)
138
- end
139
- end
140
-
141
130
  it "calls error_handler (has two args) when it rescues JSON errors" do
142
131
  called_err = nil
143
132
  pr = ->(e, _env) { called_err = e }
@@ -167,7 +156,10 @@ describe Committee::Middleware::ResponseValidation do
167
156
  { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 500 } },
168
157
  { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/a') }, expected: { status: 500 } },
169
158
  { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
170
- ].each do |description:, accept_request_filter:, expected:|
159
+ ].each do |h|
160
+ description = h[:description]
161
+ accept_request_filter = h[:accept_request_filter]
162
+ expected = h[:expected]
171
163
  it description do
172
164
  @app = new_rack_app('not_json', {}, schema: hyper_schema, prefix: '/v1', accept_request_filter: accept_request_filter)
173
165
 
@@ -181,6 +173,9 @@ describe Committee::Middleware::ResponseValidation do
181
173
  private
182
174
 
183
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
+
184
179
  headers = {
185
180
  "Content-Type" => "application/json"
186
181
  }.merge(headers)
@@ -112,6 +112,10 @@ describe Committee::Middleware::Stub do
112
112
  def new_rack_app(options = {})
113
113
  response = options.delete(:response)
114
114
  suppress = options.delete(:suppress)
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
+
115
119
  Rack::Builder.new {
116
120
  use Committee::Middleware::Stub, options
117
121
  run lambda { |env|