committee 3.3.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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|