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,200 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
describe Committee::RequestUnpacker do
|
6
|
+
it "unpacks JSON on Content-Type: application/json" do
|
7
|
+
env = {
|
8
|
+
"CONTENT_TYPE" => "application/json",
|
9
|
+
"rack.input" => StringIO.new('{"x":"y"}'),
|
10
|
+
}
|
11
|
+
request = Rack::Request.new(env)
|
12
|
+
unpacker = Committee::RequestUnpacker.new
|
13
|
+
assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
|
14
|
+
end
|
15
|
+
|
16
|
+
it "unpacks JSON on Content-Type: application/vnd.api+json" do
|
17
|
+
env = {
|
18
|
+
"CONTENT_TYPE" => "application/vnd.api+json",
|
19
|
+
"rack.input" => StringIO.new('{"x":"y"}'),
|
20
|
+
}
|
21
|
+
request = Rack::Request.new(env)
|
22
|
+
unpacker = Committee::RequestUnpacker.new
|
23
|
+
assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
|
24
|
+
end
|
25
|
+
|
26
|
+
it "unpacks JSON on no Content-Type" do
|
27
|
+
env = {
|
28
|
+
"rack.input" => StringIO.new('{"x":"y"}'),
|
29
|
+
}
|
30
|
+
request = Rack::Request.new(env)
|
31
|
+
unpacker = Committee::RequestUnpacker.new
|
32
|
+
assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
|
33
|
+
end
|
34
|
+
|
35
|
+
it "doesn't unpack JSON on application/x-ndjson" do
|
36
|
+
env = {
|
37
|
+
"CONTENT_TYPE" => "application/x-ndjson",
|
38
|
+
"rack.input" => StringIO.new('{"x":"y"}\n{"a":"b"}'),
|
39
|
+
}
|
40
|
+
request = Rack::Request.new(env)
|
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
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns {} when unpacking non-JSON with optimistic_json" do
|
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
|
79
|
+
end
|
80
|
+
|
81
|
+
it "unpacks an empty hash on an empty request body" do
|
82
|
+
env = {
|
83
|
+
"CONTENT_TYPE" => "application/json",
|
84
|
+
"rack.input" => StringIO.new(""),
|
85
|
+
}
|
86
|
+
request = Rack::Request.new(env)
|
87
|
+
unpacker = Committee::RequestUnpacker.new
|
88
|
+
assert_equal([{}, false], unpacker.unpack_request_params(request))
|
89
|
+
end
|
90
|
+
|
91
|
+
it "doesn't unpack form params" do
|
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
|
101
|
+
end
|
102
|
+
|
103
|
+
it "unpacks form params with allow_form_params" do
|
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
|
113
|
+
end
|
114
|
+
|
115
|
+
it "unpacks form & query params with allow_form_params and allow_query_params" do
|
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
|
126
|
+
end
|
127
|
+
|
128
|
+
it "unpacks query params with allow_query_params" do
|
129
|
+
env = {
|
130
|
+
"rack.input" => StringIO.new(""),
|
131
|
+
"QUERY_STRING" => "a=b"
|
132
|
+
}
|
133
|
+
request = Rack::Request.new(env)
|
134
|
+
unpacker = Committee::RequestUnpacker.new(allow_query_params: true)
|
135
|
+
assert_equal({ "a" => "b" }, unpacker.unpack_query_params(request))
|
136
|
+
end
|
137
|
+
|
138
|
+
it "errors if JSON is not an object" do
|
139
|
+
env = {
|
140
|
+
"CONTENT_TYPE" => "application/json",
|
141
|
+
"rack.input" => StringIO.new('[2]'),
|
142
|
+
}
|
143
|
+
request = Rack::Request.new(env)
|
144
|
+
assert_raises(Committee::BadRequest) do
|
145
|
+
Committee::RequestUnpacker.new.unpack_request_params(request)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it "errors on an unknown Content-Type" do
|
150
|
+
env = {
|
151
|
+
"CONTENT_TYPE" => "application/whats-this",
|
152
|
+
"rack.input" => StringIO.new('{"x":"y"}'),
|
153
|
+
}
|
154
|
+
request = Rack::Request.new(env)
|
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))
|
199
|
+
end
|
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
|