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,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