committee 1.15.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/committee-stub +11 -38
- 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 +17 -0
- data/lib/committee/middleware/base.rb +46 -29
- data/lib/committee/middleware/request_validation.rb +31 -49
- data/lib/committee/middleware/response_validation.rb +48 -25
- data/lib/committee/middleware/stub.rb +62 -37
- data/lib/committee/middleware.rb +11 -0
- data/lib/committee/request_unpacker.rb +58 -50
- 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 +68 -38
- data/lib/committee/test/schema_coverage.rb +101 -0
- data/lib/committee/utils.rb +28 -0
- data/lib/committee/validation_error.rb +5 -2
- data/lib/committee/version.rb +5 -0
- data/lib/committee.rb +31 -18
- 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 +96 -7
- data/test/middleware/request_validation_open_api_3_test.rb +626 -0
- data/test/middleware/request_validation_test.rb +423 -32
- data/test/middleware/response_validation_open_api_3_test.rb +291 -0
- data/test/middleware/response_validation_test.rb +125 -23
- data/test/middleware/stub_test.rb +81 -20
- data/test/request_unpacker_test.rb +126 -52
- 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/{response_validator_test.rb → schema_validator/hyper_schema/response_validator_test.rb} +43 -6
- 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 +334 -27
- data/test/test/schema_coverage_test.rb +216 -0
- data/test/test_helper.rb +108 -1
- data/test/validation_error_test.rb +3 -1
- metadata +190 -27
- data/lib/committee/query_params_coercer.rb +0 -45
- data/lib/committee/request_validator.rb +0 -44
- data/lib/committee/response_generator.rb +0 -35
- data/lib/committee/response_validator.rb +0 -59
- data/lib/committee/router.rb +0 -62
- data/test/query_params_coercer_test.rb +0 -70
- data/test/request_validator_test.rb +0 -103
- data/test/response_generator_test.rb +0 -61
- data/test/router_test.rb +0 -38
@@ -1,6 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "test_helper"
|
4
4
|
|
5
5
|
describe Committee::RequestUnpacker do
|
6
6
|
it "unpacks JSON on Content-Type: application/json" do
|
@@ -9,47 +9,73 @@ describe Committee::RequestUnpacker do
|
|
9
9
|
"rack.input" => StringIO.new('{"x":"y"}'),
|
10
10
|
}
|
11
11
|
request = Rack::Request.new(env)
|
12
|
-
|
13
|
-
assert_equal({ "x" => "y" },
|
12
|
+
unpacker = Committee::RequestUnpacker.new
|
13
|
+
assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
|
14
14
|
end
|
15
15
|
|
16
|
-
it "unpacks JSON on
|
16
|
+
it "unpacks JSON on Content-Type: application/vnd.api+json" do
|
17
17
|
env = {
|
18
|
+
"CONTENT_TYPE" => "application/vnd.api+json",
|
18
19
|
"rack.input" => StringIO.new('{"x":"y"}'),
|
19
20
|
}
|
20
21
|
request = Rack::Request.new(env)
|
21
|
-
|
22
|
-
assert_equal({ "x" => "y" },
|
22
|
+
unpacker = Committee::RequestUnpacker.new
|
23
|
+
assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
|
23
24
|
end
|
24
25
|
|
25
|
-
it "
|
26
|
+
it "unpacks JSON on no Content-Type" do
|
26
27
|
env = {
|
27
|
-
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
28
28
|
"rack.input" => StringIO.new('{"x":"y"}'),
|
29
29
|
}
|
30
30
|
request = Rack::Request.new(env)
|
31
|
-
|
32
|
-
assert_equal({},
|
31
|
+
unpacker = Committee::RequestUnpacker.new
|
32
|
+
assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
|
33
33
|
end
|
34
34
|
|
35
|
-
it "
|
35
|
+
it "doesn't unpack JSON on application/x-ndjson" do
|
36
36
|
env = {
|
37
|
-
"CONTENT_TYPE" => "application/x-
|
38
|
-
"rack.input" => StringIO.new('{"x":"y"}'),
|
37
|
+
"CONTENT_TYPE" => "application/x-ndjson",
|
38
|
+
"rack.input" => StringIO.new('{"x":"y"}\n{"a":"b"}'),
|
39
39
|
}
|
40
40
|
request = Rack::Request.new(env)
|
41
|
-
|
42
|
-
assert_equal({
|
41
|
+
unpacker = Committee::RequestUnpacker.new
|
42
|
+
assert_equal([{}, false], unpacker.unpack_request_params(request))
|
43
|
+
end
|
44
|
+
|
45
|
+
it "doesn't unpack JSON under other Content-Types" do
|
46
|
+
%w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
|
47
|
+
env = {
|
48
|
+
"CONTENT_TYPE" => content_type,
|
49
|
+
"rack.input" => StringIO.new('{"x":"y"}'),
|
50
|
+
}
|
51
|
+
request = Rack::Request.new(env)
|
52
|
+
unpacker = Committee::RequestUnpacker.new
|
53
|
+
assert_equal([{}, false], unpacker.unpack_request_params(request))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "unpacks JSON under other Content-Types with optimistic_json" do
|
58
|
+
%w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
|
59
|
+
env = {
|
60
|
+
"CONTENT_TYPE" => content_type,
|
61
|
+
"rack.input" => StringIO.new('{"x":"y"}'),
|
62
|
+
}
|
63
|
+
request = Rack::Request.new(env)
|
64
|
+
unpacker = Committee::RequestUnpacker.new(optimistic_json: true)
|
65
|
+
assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
|
66
|
+
end
|
43
67
|
end
|
44
68
|
|
45
69
|
it "returns {} when unpacking non-JSON with optimistic_json" do
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
70
|
+
%w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
|
71
|
+
env = {
|
72
|
+
"CONTENT_TYPE" => content_type,
|
73
|
+
"rack.input" => StringIO.new('x=y&foo=42'),
|
74
|
+
}
|
75
|
+
request = Rack::Request.new(env)
|
76
|
+
unpacker = Committee::RequestUnpacker.new(optimistic_json: true)
|
77
|
+
assert_equal([{}, false], unpacker.unpack_request_params(request))
|
78
|
+
end
|
53
79
|
end
|
54
80
|
|
55
81
|
it "unpacks an empty hash on an empty request body" do
|
@@ -58,39 +84,45 @@ describe Committee::RequestUnpacker do
|
|
58
84
|
"rack.input" => StringIO.new(""),
|
59
85
|
}
|
60
86
|
request = Rack::Request.new(env)
|
61
|
-
|
62
|
-
assert_equal({},
|
87
|
+
unpacker = Committee::RequestUnpacker.new
|
88
|
+
assert_equal([{}, false], unpacker.unpack_request_params(request))
|
63
89
|
end
|
64
90
|
|
65
91
|
it "doesn't unpack form params" do
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
92
|
+
%w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
|
93
|
+
env = {
|
94
|
+
"CONTENT_TYPE" => content_type,
|
95
|
+
"rack.input" => StringIO.new("x=y"),
|
96
|
+
}
|
97
|
+
request = Rack::Request.new(env)
|
98
|
+
unpacker = Committee::RequestUnpacker.new
|
99
|
+
assert_equal([{}, false], unpacker.unpack_request_params(request))
|
100
|
+
end
|
73
101
|
end
|
74
102
|
|
75
103
|
it "unpacks form params with allow_form_params" do
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
104
|
+
%w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
|
105
|
+
env = {
|
106
|
+
"CONTENT_TYPE" => content_type,
|
107
|
+
"rack.input" => StringIO.new("x=y"),
|
108
|
+
}
|
109
|
+
request = Rack::Request.new(env)
|
110
|
+
unpacker = Committee::RequestUnpacker.new(allow_form_params: true)
|
111
|
+
assert_equal([{ "x" => "y" }, true], unpacker.unpack_request_params(request))
|
112
|
+
end
|
83
113
|
end
|
84
114
|
|
85
115
|
it "unpacks form & query params with allow_form_params and allow_query_params" do
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
116
|
+
%w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
|
117
|
+
env = {
|
118
|
+
"CONTENT_TYPE" => content_type,
|
119
|
+
"rack.input" => StringIO.new("x=y"),
|
120
|
+
"QUERY_STRING" => "a=b"
|
121
|
+
}
|
122
|
+
request = Rack::Request.new(env)
|
123
|
+
unpacker = Committee::RequestUnpacker.new(allow_form_params: true, allow_query_params: true)
|
124
|
+
assert_equal([ { "x" => "y"}, true], unpacker.unpack_request_params(request))
|
125
|
+
end
|
94
126
|
end
|
95
127
|
|
96
128
|
it "unpacks query params with allow_query_params" do
|
@@ -99,8 +131,8 @@ describe Committee::RequestUnpacker do
|
|
99
131
|
"QUERY_STRING" => "a=b"
|
100
132
|
}
|
101
133
|
request = Rack::Request.new(env)
|
102
|
-
|
103
|
-
assert_equal({ "a" => "b" },
|
134
|
+
unpacker = Committee::RequestUnpacker.new(allow_query_params: true)
|
135
|
+
assert_equal({ "a" => "b" }, unpacker.unpack_query_params(request))
|
104
136
|
end
|
105
137
|
|
106
138
|
it "errors if JSON is not an object" do
|
@@ -110,7 +142,7 @@ describe Committee::RequestUnpacker do
|
|
110
142
|
}
|
111
143
|
request = Rack::Request.new(env)
|
112
144
|
assert_raises(Committee::BadRequest) do
|
113
|
-
Committee::RequestUnpacker.new(request)
|
145
|
+
Committee::RequestUnpacker.new.unpack_request_params(request)
|
114
146
|
end
|
115
147
|
end
|
116
148
|
|
@@ -120,7 +152,49 @@ describe Committee::RequestUnpacker do
|
|
120
152
|
"rack.input" => StringIO.new('{"x":"y"}'),
|
121
153
|
}
|
122
154
|
request = Rack::Request.new(env)
|
123
|
-
|
124
|
-
assert_equal({},
|
155
|
+
unpacker = Committee::RequestUnpacker.new
|
156
|
+
assert_equal([{}, false], unpacker.unpack_request_params(request))
|
157
|
+
end
|
158
|
+
|
159
|
+
# this is mostly here for line coverage
|
160
|
+
it "unpacks JSON containing an array" do
|
161
|
+
env = {
|
162
|
+
"rack.input" => StringIO.new('{"x":[]}'),
|
163
|
+
}
|
164
|
+
request = Rack::Request.new(env)
|
165
|
+
unpacker = Committee::RequestUnpacker.new
|
166
|
+
assert_equal([{ "x" => [] }, false], unpacker.unpack_request_params(request))
|
167
|
+
end
|
168
|
+
|
169
|
+
it "unpacks http header" do
|
170
|
+
env = {
|
171
|
+
"HTTP_FOO_BAR" => "some header value",
|
172
|
+
"rack.input" => StringIO.new(""),
|
173
|
+
}
|
174
|
+
request = Rack::Request.new(env)
|
175
|
+
unpacker = Committee::RequestUnpacker.new({ allow_header_params: true })
|
176
|
+
assert_equal({ "FOO-BAR" => "some header value" }, unpacker.unpack_headers(request))
|
177
|
+
end
|
178
|
+
|
179
|
+
it "includes request body when`use_get_body` is true" do
|
180
|
+
env = {
|
181
|
+
"rack.input" => StringIO.new('{"x":1, "y":2}'),
|
182
|
+
"REQUEST_METHOD" => "GET",
|
183
|
+
"QUERY_STRING"=>"data=value&x=aaa",
|
184
|
+
}
|
185
|
+
request = Rack::Request.new(env)
|
186
|
+
unpacker = Committee::RequestUnpacker.new({ allow_query_params: true, allow_get_body: true })
|
187
|
+
assert_equal([{ 'x' => 1, 'y' => 2 }, false], unpacker.unpack_request_params(request))
|
188
|
+
end
|
189
|
+
|
190
|
+
it "doesn't include request body when `use_get_body` is false" do
|
191
|
+
env = {
|
192
|
+
"rack.input" => StringIO.new('{"x":1, "y":2}'),
|
193
|
+
"REQUEST_METHOD" => "GET",
|
194
|
+
"QUERY_STRING"=>"data=value&x=aaa",
|
195
|
+
}
|
196
|
+
request = Rack::Request.new(env)
|
197
|
+
unpacker = Committee::RequestUnpacker.new({ allow_query_params: true, use_get_body: false })
|
198
|
+
assert_equal({ 'data' => 'value', 'x' => 'aaa' }, unpacker.unpack_query_params(request))
|
125
199
|
end
|
126
200
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
describe Committee::SchemaValidator::HyperSchema::ParameterCoercer do
|
6
|
+
before do
|
7
|
+
@schema = JsonSchema.parse!(hyper_schema_data)
|
8
|
+
@schema.expand_references!
|
9
|
+
# POST /apps/:id
|
10
|
+
@link = @schema.properties["app"].links[0]
|
11
|
+
end
|
12
|
+
|
13
|
+
it "pass datetime string" do
|
14
|
+
params = { "update_time" => "2016-04-01T16:00:00.000+09:00"}
|
15
|
+
call(params, coerce_date_times: true)
|
16
|
+
|
17
|
+
assert_kind_of DateTime, params["update_time"]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "don't change object type" do
|
21
|
+
params = HashLikeObject.new
|
22
|
+
params["update_time"] = "2016-04-01T16:00:00.000+09:00"
|
23
|
+
call(params, coerce_date_times: true)
|
24
|
+
|
25
|
+
assert_kind_of DateTime, params["update_time"]
|
26
|
+
assert_kind_of HashLikeObject, params
|
27
|
+
end
|
28
|
+
|
29
|
+
it "pass invalid datetime string, not convert" do
|
30
|
+
invalid_datetime = "llmfllmf"
|
31
|
+
params = { "update_time" => invalid_datetime}
|
32
|
+
call(params, coerce_date_times: true)
|
33
|
+
|
34
|
+
assert_equal invalid_datetime, params["update_time"]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "pass array property" do
|
38
|
+
params = {
|
39
|
+
"array_property" => [
|
40
|
+
{
|
41
|
+
"update_time" => "2016-04-01T16:00:00.000+09:00",
|
42
|
+
"per_page" => 1,
|
43
|
+
"nested_coercer_object" => {
|
44
|
+
"update_time" => "2016-04-01T16:00:00.000+09:00",
|
45
|
+
"threshold" => 1.5
|
46
|
+
},
|
47
|
+
"nested_no_coercer_object" => {
|
48
|
+
"per_page" => 1,
|
49
|
+
"threshold" => 1.5
|
50
|
+
},
|
51
|
+
"nested_coercer_array" => [
|
52
|
+
{
|
53
|
+
"update_time" => "2016-04-01T16:00:00.000+09:00",
|
54
|
+
"threshold" => 1.5
|
55
|
+
}
|
56
|
+
],
|
57
|
+
"nested_no_coercer_array" => [
|
58
|
+
{
|
59
|
+
"per_page" => 1,
|
60
|
+
"threshold" => 1.5
|
61
|
+
}
|
62
|
+
]
|
63
|
+
},
|
64
|
+
{
|
65
|
+
"update_time" => "2016-04-01T16:00:00.000+09:00",
|
66
|
+
"per_page" => 1,
|
67
|
+
"threshold" => 1.5
|
68
|
+
},
|
69
|
+
{
|
70
|
+
"threshold" => 1.5,
|
71
|
+
"per_page" => 1
|
72
|
+
}
|
73
|
+
],
|
74
|
+
}
|
75
|
+
call(params, coerce_date_times: true, coerce_recursive: true)
|
76
|
+
|
77
|
+
first_data = params["array_property"][0]
|
78
|
+
assert_kind_of DateTime, first_data["update_time"]
|
79
|
+
assert_kind_of Integer, first_data["per_page"]
|
80
|
+
|
81
|
+
second_data = params["array_property"][1]
|
82
|
+
assert_kind_of DateTime, second_data["update_time"]
|
83
|
+
assert_kind_of Integer, second_data["per_page"]
|
84
|
+
assert_kind_of Float, second_data["threshold"]
|
85
|
+
|
86
|
+
third_data = params["array_property"][1]
|
87
|
+
assert_kind_of Integer, third_data["per_page"]
|
88
|
+
assert_kind_of Float, third_data["threshold"]
|
89
|
+
|
90
|
+
assert_kind_of DateTime, first_data["nested_coercer_object"]["update_time"]
|
91
|
+
assert_kind_of Float, first_data["nested_coercer_object"]["threshold"]
|
92
|
+
|
93
|
+
assert_kind_of Integer, first_data["nested_no_coercer_object"]["per_page"]
|
94
|
+
assert_kind_of Float, first_data["nested_no_coercer_object"]["threshold"]
|
95
|
+
|
96
|
+
assert_kind_of DateTime, first_data["nested_coercer_array"].first["update_time"]
|
97
|
+
assert_kind_of Float, first_data["nested_coercer_array"].first["threshold"]
|
98
|
+
|
99
|
+
assert_kind_of Integer, first_data["nested_no_coercer_array"].first["per_page"]
|
100
|
+
assert_kind_of Float, first_data["nested_no_coercer_array"].first["threshold"]
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
class HashLikeObject < Hash; end
|
106
|
+
|
107
|
+
def call(params, options={})
|
108
|
+
link = Committee::Drivers::HyperSchema::Link.new(@link)
|
109
|
+
Committee::SchemaValidator::HyperSchema::ParameterCoercer.new(params, link.schema, options).call!
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
require "stringio"
|
6
|
+
|
7
|
+
describe Committee::SchemaValidator::HyperSchema::RequestValidator do
|
8
|
+
describe 'HyperSchema' do
|
9
|
+
before do
|
10
|
+
@schema = JsonSchema.parse!(hyper_schema_data)
|
11
|
+
@schema.expand_references!
|
12
|
+
# POST /apps/:id
|
13
|
+
@link = @schema.properties["app"].links[0]
|
14
|
+
@request = Rack::Request.new({
|
15
|
+
"CONTENT_TYPE" => "application/json",
|
16
|
+
"rack.input" => StringIO.new("{}"),
|
17
|
+
"REQUEST_METHOD" => "POST"
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
it "passes through a valid request with Content-Type options" do
|
22
|
+
@headers = { "Content-Type" => "application/json; charset=utf-8" }
|
23
|
+
call({})
|
24
|
+
end
|
25
|
+
|
26
|
+
it "passes through a valid request" do
|
27
|
+
data = {
|
28
|
+
"name" => "heroku-api",
|
29
|
+
}
|
30
|
+
call(data)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "passes through a valid request with Content-Type options" do
|
34
|
+
@request =
|
35
|
+
Rack::Request.new({
|
36
|
+
"CONTENT_TYPE" => "application/json; charset=utf-8",
|
37
|
+
"rack.input" => StringIO.new("{}"),
|
38
|
+
})
|
39
|
+
data = {
|
40
|
+
"name" => "heroku-api",
|
41
|
+
}
|
42
|
+
call(data)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "detects an invalid request Content-Type" do
|
46
|
+
e = assert_raises(Committee::InvalidRequest) {
|
47
|
+
@request =
|
48
|
+
Rack::Request.new({
|
49
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
50
|
+
"rack.input" => StringIO.new("{}"),
|
51
|
+
})
|
52
|
+
call({})
|
53
|
+
}
|
54
|
+
message =
|
55
|
+
%{"Content-Type" request header must be set to "application/json".}
|
56
|
+
assert_equal message, e.message
|
57
|
+
end
|
58
|
+
|
59
|
+
it "allows skipping content_type check" do
|
60
|
+
@request =
|
61
|
+
Rack::Request.new({
|
62
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
63
|
+
"rack.input" => StringIO.new("{}"),
|
64
|
+
})
|
65
|
+
call({}, {}, check_content_type: false)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "detects an missing parameter in GET requests" do
|
69
|
+
# GET /apps/search?query=...
|
70
|
+
@link = @schema.properties["app"].links[5]
|
71
|
+
@request = Rack::Request.new({})
|
72
|
+
e = assert_raises(Committee::InvalidRequest) do
|
73
|
+
call({})
|
74
|
+
end
|
75
|
+
message =
|
76
|
+
%{Invalid request.\n\n#: failed schema #/definitions/app/links/5/schema: "query" wasn't supplied.}
|
77
|
+
assert_equal message, e.message
|
78
|
+
end
|
79
|
+
|
80
|
+
it "allows an invalid Content-Type with an empty body" do
|
81
|
+
@request =
|
82
|
+
Rack::Request.new({
|
83
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
84
|
+
"rack.input" => StringIO.new(""),
|
85
|
+
})
|
86
|
+
call({})
|
87
|
+
end
|
88
|
+
|
89
|
+
it "detects a parameter of the wrong pattern" do
|
90
|
+
data = {
|
91
|
+
"name" => "%@!"
|
92
|
+
}
|
93
|
+
e = assert_raises(Committee::InvalidRequest) do
|
94
|
+
call(data)
|
95
|
+
end
|
96
|
+
message = %{Invalid request.\n\n#/name: failed schema #/definitions/app/links/0/schema/properties/name: %@! does not match /^[a-z][a-z0-9-]{3,30}$/.}
|
97
|
+
assert_equal message, e.message
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def call(data, headers={}, options={})
|
103
|
+
# hyper-schema link should be dropped into driver wrapper before it's used
|
104
|
+
link = Committee::Drivers::HyperSchema::Link.new(@link)
|
105
|
+
Committee::SchemaValidator::HyperSchema::RequestValidator.new(link, options).call(@request, data, headers)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'OpenAPI 2' do
|
110
|
+
before do
|
111
|
+
@schema = open_api_2_schema
|
112
|
+
@link = @schema.routes['GET'][0][1]
|
113
|
+
@request = Rack::Request.new({
|
114
|
+
"CONTENT_TYPE" => "application/json",
|
115
|
+
"rack.input" => StringIO.new("{}"),
|
116
|
+
"REQUEST_METHOD" => "POST"
|
117
|
+
})
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
it "passes through a valid request" do
|
122
|
+
headers = {
|
123
|
+
"AUTH-TOKEN" => "xxx"
|
124
|
+
}
|
125
|
+
call({}, headers)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "detects an missing header parameter in GET requests" do
|
129
|
+
@link = @schema.routes['GET'][0][1]
|
130
|
+
@request = Rack::Request.new({})
|
131
|
+
e = assert_raises(Committee::InvalidRequest) do
|
132
|
+
call({}, {})
|
133
|
+
end
|
134
|
+
message = %{Invalid request.\n\n#: failed schema : "AUTH-TOKEN" wasn't supplied.}
|
135
|
+
assert_equal message, e.message
|
136
|
+
end
|
137
|
+
|
138
|
+
it "allows skipping header schema check" do
|
139
|
+
@link = @schema.routes['GET'][0][1]
|
140
|
+
@request = Rack::Request.new({})
|
141
|
+
call({}, {}, { check_header: false })
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def call(data, headers={}, options={})
|
147
|
+
# hyper-schema link should be dropped into driver wrapper before it's used
|
148
|
+
Committee::SchemaValidator::HyperSchema::RequestValidator.new(@link, options).call(@request, data, headers)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
describe Committee::SchemaValidator::HyperSchema::ResponseGenerator do
|
6
|
+
before do
|
7
|
+
@schema = JsonSchema.parse!(hyper_schema_data)
|
8
|
+
@schema.expand_references!
|
9
|
+
# GET /apps/:id
|
10
|
+
@get_link = @link = @schema.properties["app"].links[2]
|
11
|
+
# GET /apps
|
12
|
+
@list_link = @schema.properties["app"].links[3]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns the schema used to match" do
|
16
|
+
_data, schema = call
|
17
|
+
assert_equal @get_link.target_schema, schema
|
18
|
+
end
|
19
|
+
|
20
|
+
it "generates string properties" do
|
21
|
+
data, _schema = call
|
22
|
+
assert data["name"].is_a?(String)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "generates non-string properties" do
|
26
|
+
data, _schema = call
|
27
|
+
assert_includes [FalseClass, TrueClass], data["maintenance"].class
|
28
|
+
end
|
29
|
+
|
30
|
+
it "wraps list data in an array" do
|
31
|
+
@link = @list_link
|
32
|
+
|
33
|
+
@link.rel = nil
|
34
|
+
|
35
|
+
data, _schema = call
|
36
|
+
assert data.is_a?(Array)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "wraps list data tagged with rel 'instances' in an array" do
|
40
|
+
@link = @list_link
|
41
|
+
|
42
|
+
# forces the link to use `parent`
|
43
|
+
@link.target_schema = nil
|
44
|
+
|
45
|
+
data, _schema = call
|
46
|
+
|
47
|
+
# We're testing for legacy behavior here: even without a `targetSchema` as
|
48
|
+
# long as `rel` is set to `instances` we still wrap the result in an
|
49
|
+
# array.
|
50
|
+
assert_equal "instances", @list_link.rel
|
51
|
+
|
52
|
+
assert data.is_a?(Array)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "errors with no known route for generation" do
|
56
|
+
@link = @get_link
|
57
|
+
|
58
|
+
# tweak the schema so that it can't be generated
|
59
|
+
property = @schema.properties["app"].properties["maintenance"]
|
60
|
+
property.data["example"] = nil
|
61
|
+
property.type = []
|
62
|
+
|
63
|
+
e = assert_raises(RuntimeError) do
|
64
|
+
call
|
65
|
+
end
|
66
|
+
|
67
|
+
expected = <<-eos.gsub(/\n +/, "").strip
|
68
|
+
At "get /apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fname)}"
|
69
|
+
"#/definitions/app/properties/maintenance": no "example" attribute and
|
70
|
+
"null" is not allowed; don't know how to generate property.
|
71
|
+
eos
|
72
|
+
assert_equal expected, e.message
|
73
|
+
end
|
74
|
+
|
75
|
+
it "generates first enum value for a schema with enum" do
|
76
|
+
link = Committee::Drivers::OpenAPI2::Link.new
|
77
|
+
link.target_schema = JsonSchema::Schema.new
|
78
|
+
link.target_schema.enum = ["foo"]
|
79
|
+
link.target_schema.type = ["string"]
|
80
|
+
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
81
|
+
assert_equal("foo", data)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "generates basic types" do
|
85
|
+
link = Committee::Drivers::OpenAPI2::Link.new
|
86
|
+
link.target_schema = JsonSchema::Schema.new
|
87
|
+
|
88
|
+
link.target_schema.type = ["integer"]
|
89
|
+
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
90
|
+
assert_equal 0, data
|
91
|
+
|
92
|
+
link.target_schema.type = ["null"]
|
93
|
+
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
94
|
+
assert_nil data
|
95
|
+
|
96
|
+
link.target_schema.type = ["string"]
|
97
|
+
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
98
|
+
assert_equal "", data
|
99
|
+
end
|
100
|
+
|
101
|
+
it "generates an empty array for an array type" do
|
102
|
+
link = Committee::Drivers::OpenAPI2::Link.new
|
103
|
+
link.target_schema = JsonSchema::Schema.new
|
104
|
+
link.target_schema.type = ["array"]
|
105
|
+
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
106
|
+
assert_equal([], data)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "generates an empty object for an object with no fields" do
|
110
|
+
link = Committee::Drivers::OpenAPI2::Link.new
|
111
|
+
link.target_schema = JsonSchema::Schema.new
|
112
|
+
link.target_schema.type = ["object"]
|
113
|
+
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
114
|
+
assert_equal({}, data)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "prefers an example to a built-in value" do
|
118
|
+
link = Committee::Drivers::OpenAPI2::Link.new
|
119
|
+
link.target_schema = JsonSchema::Schema.new
|
120
|
+
|
121
|
+
link.target_schema.data = { "example" => 123 }
|
122
|
+
link.target_schema.type = ["integer"]
|
123
|
+
|
124
|
+
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
125
|
+
assert_equal 123, data
|
126
|
+
end
|
127
|
+
|
128
|
+
it "prefers non-null types to null types" do
|
129
|
+
link = Committee::Drivers::OpenAPI2::Link.new
|
130
|
+
link.target_schema = JsonSchema::Schema.new
|
131
|
+
|
132
|
+
link.target_schema.type = ["null", "integer"]
|
133
|
+
data, _schema = Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
134
|
+
assert_equal 0, data
|
135
|
+
end
|
136
|
+
|
137
|
+
def call
|
138
|
+
# hyper-schema link should be dropped into driver wrapper before it's used
|
139
|
+
link = Committee::Drivers::HyperSchema::Link.new(@link)
|
140
|
+
Committee::SchemaValidator::HyperSchema::ResponseGenerator.new.call(link)
|
141
|
+
end
|
142
|
+
end
|