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.
- checksums.yaml +7 -0
- data/bin/committee-stub +23 -0
- data/lib/committee/bin/committee_stub.rb +67 -0
- data/lib/committee/drivers/driver.rb +47 -0
- data/lib/committee/drivers/hyper_schema/driver.rb +105 -0
- data/lib/committee/drivers/hyper_schema/link.rb +68 -0
- data/lib/committee/drivers/hyper_schema/schema.rb +22 -0
- data/lib/committee/drivers/hyper_schema.rb +12 -0
- data/lib/committee/drivers/open_api_2/driver.rb +252 -0
- data/lib/committee/drivers/open_api_2/header_schema_builder.rb +33 -0
- data/lib/committee/drivers/open_api_2/link.rb +36 -0
- data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +83 -0
- data/lib/committee/drivers/open_api_2/schema.rb +26 -0
- data/lib/committee/drivers/open_api_2/schema_builder.rb +33 -0
- data/lib/committee/drivers/open_api_2.rb +13 -0
- data/lib/committee/drivers/open_api_3/driver.rb +51 -0
- data/lib/committee/drivers/open_api_3/schema.rb +41 -0
- data/lib/committee/drivers/open_api_3.rb +11 -0
- data/lib/committee/drivers/schema.rb +23 -0
- data/lib/committee/drivers.rb +84 -0
- data/lib/committee/errors.rb +36 -0
- data/lib/committee/middleware/base.rb +57 -0
- data/lib/committee/middleware/request_validation.rb +41 -0
- data/lib/committee/middleware/response_validation.rb +58 -0
- data/lib/committee/middleware/stub.rb +75 -0
- data/lib/committee/middleware.rb +11 -0
- data/lib/committee/request_unpacker.rb +91 -0
- data/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +79 -0
- data/lib/committee/schema_validator/hyper_schema/request_validator.rb +55 -0
- data/lib/committee/schema_validator/hyper_schema/response_generator.rb +102 -0
- data/lib/committee/schema_validator/hyper_schema/response_validator.rb +89 -0
- data/lib/committee/schema_validator/hyper_schema/router.rb +46 -0
- data/lib/committee/schema_validator/hyper_schema/string_params_coercer.rb +105 -0
- data/lib/committee/schema_validator/hyper_schema.rb +119 -0
- data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +139 -0
- data/lib/committee/schema_validator/open_api_3/request_validator.rb +52 -0
- data/lib/committee/schema_validator/open_api_3/response_validator.rb +29 -0
- data/lib/committee/schema_validator/open_api_3/router.rb +45 -0
- data/lib/committee/schema_validator/open_api_3.rb +120 -0
- data/lib/committee/schema_validator/option.rb +60 -0
- data/lib/committee/schema_validator.rb +23 -0
- data/lib/committee/test/methods.rb +84 -0
- data/lib/committee/test/schema_coverage.rb +101 -0
- data/lib/committee/utils.rb +28 -0
- data/lib/committee/validation_error.rb +26 -0
- data/lib/committee/version.rb +5 -0
- data/lib/committee.rb +40 -0
- data/test/bin/committee_stub_test.rb +57 -0
- data/test/bin_test.rb +25 -0
- data/test/committee_test.rb +77 -0
- data/test/drivers/hyper_schema/driver_test.rb +49 -0
- data/test/drivers/hyper_schema/link_test.rb +56 -0
- data/test/drivers/open_api_2/driver_test.rb +156 -0
- data/test/drivers/open_api_2/header_schema_builder_test.rb +26 -0
- data/test/drivers/open_api_2/link_test.rb +52 -0
- data/test/drivers/open_api_2/parameter_schema_builder_test.rb +195 -0
- data/test/drivers/open_api_3/driver_test.rb +84 -0
- data/test/drivers_test.rb +154 -0
- data/test/middleware/base_test.rb +130 -0
- data/test/middleware/request_validation_open_api_3_test.rb +626 -0
- data/test/middleware/request_validation_test.rb +516 -0
- data/test/middleware/response_validation_open_api_3_test.rb +291 -0
- data/test/middleware/response_validation_test.rb +189 -0
- data/test/middleware/stub_test.rb +145 -0
- data/test/request_unpacker_test.rb +200 -0
- data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +111 -0
- data/test/schema_validator/hyper_schema/request_validator_test.rb +151 -0
- data/test/schema_validator/hyper_schema/response_generator_test.rb +142 -0
- data/test/schema_validator/hyper_schema/response_validator_test.rb +118 -0
- data/test/schema_validator/hyper_schema/router_test.rb +88 -0
- data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +137 -0
- data/test/schema_validator/open_api_3/operation_wrapper_test.rb +218 -0
- data/test/schema_validator/open_api_3/request_validator_test.rb +110 -0
- data/test/schema_validator/open_api_3/response_validator_test.rb +92 -0
- data/test/test/methods_new_version_test.rb +97 -0
- data/test/test/methods_test.rb +363 -0
- data/test/test/schema_coverage_test.rb +216 -0
- data/test/test_helper.rb +120 -0
- data/test/validation_error_test.rb +25 -0
- 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
|