committee_firetail 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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/bin/committee-stub +23 -0
  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 +36 -0
  22. data/lib/committee/middleware/base.rb +57 -0
  23. data/lib/committee/middleware/request_validation.rb +41 -0
  24. data/lib/committee/middleware/response_validation.rb +58 -0
  25. data/lib/committee/middleware/stub.rb +75 -0
  26. data/lib/committee/middleware.rb +11 -0
  27. data/lib/committee/request_unpacker.rb +91 -0
  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 +84 -0
  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 +26 -0
  46. data/lib/committee/version.rb +5 -0
  47. data/lib/committee.rb +40 -0
  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 +130 -0
  60. data/test/middleware/request_validation_open_api_3_test.rb +626 -0
  61. data/test/middleware/request_validation_test.rb +516 -0
  62. data/test/middleware/response_validation_open_api_3_test.rb +291 -0
  63. data/test/middleware/response_validation_test.rb +189 -0
  64. data/test/middleware/stub_test.rb +145 -0
  65. data/test/request_unpacker_test.rb +200 -0
  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/schema_validator/hyper_schema/response_validator_test.rb +118 -0
  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 +363 -0
  77. data/test/test/schema_coverage_test.rb +216 -0
  78. data/test/test_helper.rb +120 -0
  79. data/test/validation_error_test.rb +25 -0
  80. metadata +328 -0
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::HyperSchema::ResponseValidator do
6
+ before do
7
+ @status = 200
8
+ @headers = {
9
+ "Content-Type" => "application/json"
10
+ }
11
+ @data = ValidApp.dup
12
+ @schema = JsonSchema.parse!(hyper_schema_data)
13
+ @schema.expand_references!
14
+ # GET /apps/:id
15
+ @get_link = @link = @schema.properties["app"].links[2]
16
+ # GET /apps
17
+ @list_link = @schema.properties["app"].links[3]
18
+ @type_schema = @schema.properties["app"]
19
+ end
20
+
21
+ it "passes through a valid response" do
22
+ call
23
+ end
24
+
25
+ it "passes through a valid response with Content-Type options" do
26
+ @headers = { "Content-Type" => "application/json; charset=utf-8" }
27
+ call
28
+ end
29
+
30
+ it "passes through a valid list response" do
31
+ @data = [@data]
32
+ @link = @list_link
33
+ call
34
+ end
35
+
36
+ it "passes through a 204 No Content response" do
37
+ @status, @headers, @data = 204, {}, nil
38
+ call
39
+ end
40
+
41
+ it "passes through a 304 Not Modified response" do
42
+ @status, @headers, @data = 304, {}, nil
43
+ call
44
+ end
45
+
46
+ it "passes through a valid list response for for rel instances links" do
47
+ @link = @list_link
48
+
49
+ # forces the link to use `parent`
50
+ @link.target_schema = nil
51
+
52
+ # We're testing for legacy behavior here: even without a `targetSchema` as
53
+ # long as `rel` is set to `instances` we still wrap the result in an
54
+ # array.
55
+ assert_equal "instances", @link.rel
56
+
57
+ @data = [@data]
58
+ @link = @list_link
59
+ call
60
+ end
61
+
62
+ it "detects an improperly formatted list response for rel instances link" do
63
+ @link = @list_link
64
+
65
+ # forces the link to use `parent`
66
+ @link.target_schema = nil
67
+
68
+ # We're testing for legacy behavior here: even without a `targetSchema` as
69
+ # long as `rel` is set to `instances` we still wrap the result in an
70
+ # array.
71
+ assert_equal "instances", @link.rel
72
+
73
+ e = assert_raises(Committee::InvalidResponse) { call }
74
+ message = "List endpoints must return an array of objects."
75
+ assert_equal message, e.message
76
+ end
77
+
78
+ it "detects a blank response Content-Type" do
79
+ @headers = {}
80
+ e = assert_raises(Committee::InvalidResponse) { call }
81
+ message =
82
+ %{"Content-Type" response header must be set to "#{@link.enc_type}".}
83
+ assert_equal message, e.message
84
+ end
85
+
86
+ it "detects an invalid response Content-Type" do
87
+ @headers = { "Content-Type" => "text/html" }
88
+ e = assert_raises(Committee::InvalidResponse) { call }
89
+ message =
90
+ %{"Content-Type" response header must be set to "#{@link.enc_type}".}
91
+ assert_equal message, e.message
92
+ end
93
+
94
+ it "allows no Content-Type for 204 No Content" do
95
+ @status, @headers = 204, {}
96
+ call
97
+ end
98
+
99
+ it "allows no Content-Type for 304 Not Modified" do
100
+ @status, @headers = 304, {}
101
+ call
102
+ end
103
+
104
+ it "raises errors generated by json_schema" do
105
+ @data.merge!("name" => "%@!")
106
+ e = assert_raises(Committee::InvalidResponse) { call }
107
+ message = %{Invalid response.\n\n#/name: failed schema #/definitions/app/properties/name: %@! does not match /^[a-z][a-z0-9-]{3,30}$/.}
108
+ assert_equal message, e.message
109
+ end
110
+
111
+ private
112
+
113
+ def call
114
+ # hyper-schema link should be dropped into driver wrapper before it's used
115
+ link = Committee::Drivers::HyperSchema::Link.new(@link)
116
+ Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link).call(@status, @headers, @data)
117
+ end
118
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::HyperSchema::Router do
6
+ it "builds routes without parameters" do
7
+ link, _ = hyper_schema_router.find_link("GET", "/apps")
8
+ refute_nil link
9
+ end
10
+
11
+ it "builds routes with parameters" do
12
+ link, _ = hyper_schema_router.find_link("GET", "/apps/123")
13
+ refute_nil link
14
+ end
15
+
16
+ it "doesn't match anything on a /" do
17
+ link, _ = hyper_schema_router.find_link("GET", "/")
18
+ assert_nil link
19
+ end
20
+
21
+ it "takes a prefix" do
22
+ # this is a sociopathic example
23
+ link, _ = hyper_schema_router(prefix: "/kpi").find_link("GET", "/kpi/apps/123")
24
+ refute_nil link
25
+ end
26
+
27
+ it "includes all paths without a prefix" do
28
+ link, _ = hyper_schema_router.includes?("/")
29
+ refute_nil link
30
+
31
+ link, _ = hyper_schema_router.includes?("/apps")
32
+ refute_nil link
33
+ end
34
+
35
+ it "only includes the prefix path with a prefix" do
36
+ link, _ = hyper_schema_router(prefix: "/kpi").includes?("/")
37
+ assert_nil link
38
+
39
+ link, _ = hyper_schema_router(prefix: "/kpi").includes?("/kpi")
40
+ refute_nil link
41
+
42
+ link, _ = hyper_schema_router(prefix: "/kpi").includes?("/kpi/apps")
43
+ refute_nil link
44
+ end
45
+
46
+ it "provides named parameters" do
47
+ link, param_matches = open_api_2_router.find_link("GET", "/api/pets/fido")
48
+ refute_nil link
49
+ assert_equal '/api/pets/{id}', link.href
50
+ assert_equal({ "id" => "fido" }, param_matches)
51
+ end
52
+
53
+ it "doesn't provide named parameters where none are available" do
54
+ link, param_matches = open_api_2_router.find_link("GET", "/api/pets")
55
+ refute_nil link
56
+ assert_equal({}, param_matches)
57
+ end
58
+
59
+ it "finds a path which has fewer matches" do
60
+ link, _ = open_api_2_router.find_link("GET", "/api/pets/dog")
61
+ refute_nil link
62
+ assert_equal '/api/pets/dog', link.href
63
+ end
64
+
65
+ it "fewer match not support in HyperSchema" do
66
+ link, _ = hyper_schema_router.find_link("GET", "/apps/abc")
67
+ refute_nil link
68
+ assert_equal '/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fname)}', link.href
69
+ end
70
+
71
+ def hyper_schema_router(options = {})
72
+ # TODO: delete when 5.0.0 released because default value changed
73
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
74
+ schema = Committee::Drivers::HyperSchema::Driver.new.parse(hyper_schema_data)
75
+ validator_option = Committee::SchemaValidator::Option.new(options, schema, :hyper_schema)
76
+
77
+ Committee::SchemaValidator::HyperSchema::Router.new(schema, validator_option)
78
+ end
79
+
80
+ def open_api_2_router(options = {})
81
+ # TODO: delete when 5.0.0 released because default value changed
82
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
83
+ schema = Committee::Drivers::OpenAPI2::Driver.new.parse(open_api_2_data)
84
+ validator_option = Committee::SchemaValidator::Option.new(options, schema, :hyper_schema)
85
+
86
+ Committee::SchemaValidator::HyperSchema::Router.new(schema, validator_option)
87
+ end
88
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::HyperSchema::StringParamsCoercer do
6
+ before do
7
+ @schema = JsonSchema.parse!(hyper_schema_data)
8
+ @schema.expand_references!
9
+ # GET /search/apps
10
+ @link = @schema.properties["app"].links[5]
11
+ end
12
+
13
+ it "doesn't coerce params not in the schema" do
14
+ check_convert("owner", "admin", "admin")
15
+ end
16
+
17
+ it "skips values for string param" do
18
+ check_convert("name", "foo", "foo")
19
+ end
20
+
21
+ it "coerces valid values for boolean param" do
22
+ check_convert("deleted", "true", true)
23
+ check_convert("deleted", "false", false)
24
+ check_convert("deleted", "1", true)
25
+ check_convert("deleted", "0", false)
26
+ end
27
+
28
+ it "skips invalid values for boolean param" do
29
+ check_convert("deleted", "foo", "foo")
30
+ end
31
+
32
+ it "coerces valid values for integer param" do
33
+ check_convert("per_page", "3", 3)
34
+ end
35
+
36
+ it "skips invalid values for integer param" do
37
+ check_convert("per_page", "3.5", "3.5")
38
+ check_convert("per_page", "false", "false")
39
+ check_convert("per_page", "", "")
40
+ end
41
+
42
+ it "coerces valid values for number param" do
43
+ check_convert("threshold", "3", 3.0)
44
+ check_convert("threshold", "3.5", 3.5)
45
+ end
46
+
47
+ it "skips invalid values for number param" do
48
+ check_convert("threshold", "false", "false")
49
+ end
50
+
51
+ it "coerces valid values for null param" do
52
+ check_convert("threshold", "", nil)
53
+ end
54
+
55
+ it "pass array property" do
56
+ params = {
57
+ "array_property" => [
58
+ {
59
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
60
+ "per_page" => "1",
61
+ "nested_coercer_object" => {
62
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
63
+ "threshold" => "1.5"
64
+ },
65
+ "nested_no_coercer_object" => {
66
+ "per_page" => "1",
67
+ "threshold" => "1.5"
68
+ },
69
+ "nested_coercer_array" => [
70
+ {
71
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
72
+ "threshold" => "1.5"
73
+ }
74
+ ],
75
+ "nested_no_coercer_array" => [
76
+ {
77
+ "per_page" => "1",
78
+ "threshold" => "1.5"
79
+ }
80
+ ]
81
+ },
82
+ {
83
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
84
+ "per_page" => "1",
85
+ "threshold" => "1.5"
86
+ },
87
+ {
88
+ "threshold" => "1.5",
89
+ "per_page" => "1"
90
+ }
91
+ ],
92
+ }
93
+ call(params, coerce_recursive: true)
94
+
95
+ first_data = params["array_property"][0]
96
+ assert_kind_of String, first_data["update_time"]
97
+ assert_kind_of Integer, first_data["per_page"]
98
+
99
+ second_data = params["array_property"][1]
100
+ assert_kind_of String, second_data["update_time"]
101
+ assert_kind_of Integer, second_data["per_page"]
102
+ assert_kind_of Float, second_data["threshold"]
103
+
104
+ third_data = params["array_property"][1]
105
+ assert_kind_of Integer, third_data["per_page"]
106
+ assert_kind_of Float, third_data["threshold"]
107
+
108
+ assert_kind_of String, first_data["nested_coercer_object"]["update_time"]
109
+ assert_kind_of Float, first_data["nested_coercer_object"]["threshold"]
110
+
111
+ assert_kind_of Integer, first_data["nested_no_coercer_object"]["per_page"]
112
+ assert_kind_of Float, first_data["nested_no_coercer_object"]["threshold"]
113
+
114
+ assert_kind_of String, first_data["nested_coercer_array"].first["update_time"]
115
+ assert_kind_of Float, first_data["nested_coercer_array"].first["threshold"]
116
+
117
+ assert_kind_of Integer, first_data["nested_no_coercer_array"].first["per_page"]
118
+ assert_kind_of Float, first_data["nested_no_coercer_array"].first["threshold"]
119
+ end
120
+
121
+ private
122
+
123
+ def check_convert(key, before_value, after_value)
124
+ data = {key => before_value}
125
+ call(data)
126
+
127
+ if !after_value.nil?
128
+ assert_equal(data[key], after_value)
129
+ else
130
+ assert_nil(data[key])
131
+ end
132
+ end
133
+
134
+ def call(data, options={})
135
+ Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(data, @link.schema, options).call!
136
+ end
137
+ end
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ require "stringio"
6
+
7
+ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
8
+ describe 'validate' do
9
+ before do
10
+ @path = '/validate'
11
+ @method = 'post'
12
+
13
+ # TODO: delete when 5.0.0 released because default value changed
14
+ options = {}
15
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
16
+
17
+ @validator_option = Committee::SchemaValidator::Option.new(options, open_api_3_schema, :open_api_3)
18
+ end
19
+
20
+ def operation_object
21
+ open_api_3_schema.operation_object(@path, @method)
22
+ end
23
+
24
+ HEADER = { 'Content-Type' => 'application/json' }
25
+
26
+ SCHEMA_PROPERTIES_PAIR = [
27
+ ['string', 'str'],
28
+ ['integer', 1],
29
+ ['boolean', true],
30
+ ['boolean', false],
31
+ ['number', 0.1],
32
+ ]
33
+
34
+ it 'correct data' do
35
+ operation_object.validate_request_params({}, {}, SCHEMA_PROPERTIES_PAIR.to_h, HEADER, @validator_option)
36
+ assert true
37
+ end
38
+
39
+ it 'correct object data' do
40
+ operation_object.validate_request_params(
41
+ {},
42
+ {},
43
+ {
44
+ "object_1" =>
45
+ {
46
+ "string_1" => nil,
47
+ "integer_1" => nil,
48
+ "boolean_1" => nil,
49
+ "number_1" => nil
50
+ }
51
+ },
52
+ HEADER,
53
+ @validator_option)
54
+
55
+ assert true
56
+ end
57
+
58
+ it 'invalid params' do
59
+ e = assert_raises(Committee::InvalidRequest) {
60
+ operation_object.validate_request_params({}, {}, {"string" => 1}, HEADER, @validator_option)
61
+ }
62
+
63
+ assert_match(/expected string, but received Integer: 1/i, e.message)
64
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
65
+ end
66
+
67
+ it 'support put method' do
68
+ @method = "put"
69
+ operation_object.validate_request_params({}, {}, {"string" => "str"}, HEADER, @validator_option)
70
+
71
+ e = assert_raises(Committee::InvalidRequest) {
72
+ operation_object.validate_request_params({}, {}, {"string" => 1}, HEADER, @validator_option)
73
+ }
74
+
75
+ assert_match(/expected string, but received Integer: 1/i, e.message)
76
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
77
+ end
78
+
79
+ it 'support patch method' do
80
+ @method = "patch"
81
+ operation_object.validate_request_params({}, {}, {"integer" => 1}, HEADER, @validator_option)
82
+
83
+ e = assert_raises(Committee::InvalidRequest) {
84
+ operation_object.validate_request_params({}, {}, {"integer" => "str"}, HEADER, @validator_option)
85
+ }
86
+
87
+ assert_match(/expected integer, but received String: "str"/i, e.message)
88
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
89
+ end
90
+
91
+ it 'unknown param' do
92
+ operation_object.validate_request_params({}, {}, {"unknown" => 1}, HEADER, @validator_option)
93
+ end
94
+
95
+ describe 'support get method' do
96
+ before do
97
+ @method = "get"
98
+ end
99
+
100
+ it 'correct' do
101
+ operation_object.validate_request_params(
102
+ {},
103
+ {"query_string" => "query", "query_integer_list" => [1, 2]},
104
+ {},
105
+ HEADER,
106
+ @validator_option
107
+ )
108
+
109
+ operation_object.validate_request_params(
110
+ {},
111
+ {"query_string" => "query", "query_integer_list" => [1, 2], "optional_integer" => 1},
112
+ {},
113
+ HEADER,
114
+ @validator_option
115
+ )
116
+
117
+ assert true
118
+ end
119
+
120
+ it 'not exist required' do
121
+ e = assert_raises(Committee::InvalidRequest) {
122
+ operation_object.validate_request_params({}, {"query_integer_list" => [1, 2]}, {}, HEADER, @validator_option)
123
+ }
124
+
125
+ assert_match(/missing required parameters: query_string/i, e.message)
126
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
127
+ end
128
+
129
+ it 'invalid type' do
130
+ e = assert_raises(Committee::InvalidRequest) {
131
+ operation_object.validate_request_params(
132
+ {},
133
+ {"query_string" => 1, "query_integer_list" => [1, 2], "optional_integer" => 1},
134
+ {},
135
+ HEADER,
136
+ @validator_option
137
+ )
138
+ }
139
+
140
+ assert_match(/expected string, but received Integer: 1/i, e.message)
141
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
142
+ end
143
+ end
144
+
145
+ describe 'support delete method' do
146
+ before do
147
+ @path = '/characters'
148
+ @method = "delete"
149
+ end
150
+
151
+ it 'correct' do
152
+ operation_object.validate_request_params({}, {"limit" => "1"}, {}, HEADER, @validator_option)
153
+
154
+ assert true
155
+ end
156
+
157
+ it 'invalid type' do
158
+ e = assert_raises(Committee::InvalidRequest) {
159
+ operation_object.validate_request_params({}, {"limit" => "a"}, {}, HEADER, @validator_option)
160
+ }
161
+
162
+ assert_match(/expected integer, but received String: "a"/i, e.message)
163
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
164
+ end
165
+ end
166
+
167
+ describe 'support head method' do
168
+ before do
169
+ @path = '/characters'
170
+ @method = 'head'
171
+ end
172
+
173
+ it 'correct' do
174
+ operation_object.validate_request_params({}, {"limit" => "1"}, {}, HEADER, @validator_option)
175
+
176
+ assert true
177
+ end
178
+
179
+ it 'invalid type' do
180
+ e = assert_raises(Committee::InvalidRequest) {
181
+ operation_object.validate_request_params({}, {"limit" => "a"}, {}, HEADER, @validator_option)
182
+ }
183
+
184
+ assert_match(/expected integer, but received String: "a"/i, e.message)
185
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
186
+ end
187
+ end
188
+
189
+ it 'support options method' do
190
+ @method = "options"
191
+ operation_object.validate_request_params({}, {}, {"integer" => 1}, HEADER, @validator_option)
192
+
193
+ e = assert_raises(Committee::InvalidRequest) {
194
+ operation_object.validate_request_params({}, {}, {"integer" => "str"}, HEADER, @validator_option)
195
+ }
196
+
197
+ assert_match(/expected integer, but received String: "str"/i, e.message)
198
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
199
+ end
200
+
201
+
202
+ describe '#content_types' do
203
+ it 'returns supported content types' do
204
+ @path = '/validate_content_types'
205
+ @method = 'post'
206
+
207
+ assert_equal ["application/json", "application/binary"], operation_object.request_content_types
208
+ end
209
+
210
+ it 'returns an empty array when the content of requestBody does not exist' do
211
+ @path = '/characters'
212
+ @method = 'get'
213
+
214
+ assert_equal [], operation_object.request_content_types
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::OpenAPI3::RequestValidator do
6
+ describe 'OpenAPI3' do
7
+ include Rack::Test::Methods
8
+
9
+ def app
10
+ @app
11
+ end
12
+
13
+ it "skip validation when link does not exist" do
14
+ @app = new_rack_app(schema: open_api_3_schema)
15
+ params = {}
16
+ header "Content-Type", "application/json"
17
+ post "/unknown", JSON.generate(params)
18
+ assert_equal 200, last_response.status
19
+ end
20
+
21
+ it "optionally content_type check" do
22
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
23
+ params = {
24
+ "string_post_1" => "cloudnasium"
25
+ }
26
+ header "Content-Type", "text/html"
27
+ post "/characters", JSON.generate(params)
28
+ assert_equal 400, last_response.status
29
+
30
+ body = JSON.parse(last_response.body)
31
+ message =
32
+ %{"Content-Type" request header must be set to "application/json".}
33
+
34
+ assert_equal "bad_request", body['id']
35
+ assert_equal message, body['message']
36
+ end
37
+
38
+ it "validates content_type" do
39
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
40
+ params = {
41
+ "string_post_1" => "cloudnasium"
42
+ }
43
+ header "Content-Type", "text/html"
44
+ post "/validate_content_types", JSON.generate(params)
45
+ assert_equal 400, last_response.status
46
+
47
+ body = JSON.parse(last_response.body)
48
+ message =
49
+ %{"Content-Type" request header must be set to any of the following: ["application/json", "application/binary"].}
50
+
51
+ assert_equal message, body['message']
52
+ end
53
+
54
+ it "optionally skip content_type check" do
55
+ @app = new_rack_app(check_content_type: false, schema: open_api_3_schema)
56
+ params = {
57
+ "string_post_1" => "cloudnasium"
58
+ }
59
+ header "Content-Type", "text/html"
60
+ post "/characters", JSON.generate(params)
61
+ assert_equal 200, last_response.status
62
+ end
63
+
64
+ it "if not exist requestBody definition, skip content_type check" do
65
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
66
+ params = {
67
+ "string_post_1" => "cloudnasium"
68
+ }
69
+ header "Content-Type", "application/json"
70
+ patch "/validate_no_parameter", JSON.generate(params)
71
+ assert_equal 200, last_response.status
72
+ end
73
+
74
+ it "skips content_type check with an empty body" do
75
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
76
+ header "Content-Type", "application/x-www-form-urlencoded"
77
+ patch "/validate_empty_optional_body"
78
+ assert_equal 200, last_response.status
79
+ end
80
+
81
+ it "does not mix up parameters and requestBody" do
82
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
83
+ params = {
84
+ "last_name" => "Skywalker"
85
+ }
86
+ header "Content-Type", "application/json"
87
+ post "/additional_properties?first_name=Luke", JSON.generate(params)
88
+ assert_equal 200, last_response.status
89
+ end
90
+
91
+ it "error because content_type check with body" do
92
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
93
+ header "Content-Type", "application/x-www-form-urlencoded"
94
+ patch "/validate_empty_optional_body", "{}"
95
+ assert_equal 400, last_response.status
96
+ end
97
+
98
+ def new_rack_app(options = {})
99
+ # TODO: delete when 5.0.0 released because default value changed
100
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
101
+
102
+ Rack::Builder.new {
103
+ use Committee::Middleware::RequestValidation, options
104
+ run lambda { |_|
105
+ [200, {}, []]
106
+ }
107
+ }
108
+ end
109
+ end
110
+ end