brainstem 1.4.1 → 2.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 +4 -4
- data/CHANGELOG.md +77 -0
- data/README.md +119 -0
- data/docs/api_doc_generator.markdown +45 -4
- data/docs/brainstem_executable.markdown +1 -1
- data/docs/oas_2_docgen.png +0 -0
- data/docs/oas_2_docgen_ascii.txt +78 -0
- data/lib/brainstem/api_docs.rb +23 -9
- data/lib/brainstem/api_docs/abstract_collection.rb +0 -13
- data/lib/brainstem/api_docs/atlas.rb +0 -14
- data/lib/brainstem/api_docs/builder.rb +0 -14
- data/lib/brainstem/api_docs/controller.rb +7 -16
- data/lib/brainstem/api_docs/controller_collection.rb +0 -3
- data/lib/brainstem/api_docs/endpoint.rb +73 -19
- data/lib/brainstem/api_docs/endpoint_collection.rb +0 -7
- data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +0 -2
- data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +1 -9
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +1 -9
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +39 -24
- data/lib/brainstem/api_docs/formatters/markdown/helper.rb +0 -13
- data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +22 -35
- data/lib/brainstem/api_docs/formatters/open_api_specification/helper.rb +66 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter.rb +57 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter.rb +311 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter.rb +197 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter.rb +60 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb +162 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter.rb +126 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter.rb +132 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter.rb +99 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter.rb +123 -0
- data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +0 -7
- data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +1 -20
- data/lib/brainstem/api_docs/presenter.rb +21 -27
- data/lib/brainstem/api_docs/presenter_collection.rb +1 -11
- data/lib/brainstem/api_docs/resolver.rb +1 -8
- data/lib/brainstem/api_docs/sinks/abstract_sink.rb +0 -4
- data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +0 -9
- data/lib/brainstem/api_docs/sinks/open_api_specification_sink.rb +234 -0
- data/lib/brainstem/api_docs/sinks/stdout_sink.rb +0 -5
- data/lib/brainstem/cli.rb +0 -13
- data/lib/brainstem/cli/abstract_command.rb +0 -7
- data/lib/brainstem/cli/generate_api_docs_command.rb +48 -24
- data/lib/brainstem/concerns/controller_dsl.rb +288 -145
- data/lib/brainstem/concerns/formattable.rb +0 -5
- data/lib/brainstem/concerns/optional.rb +0 -1
- data/lib/brainstem/concerns/presenter_dsl.rb +2 -21
- data/lib/brainstem/dsl/configuration.rb +0 -11
- data/lib/brainstem/presenter.rb +0 -4
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/abstract_collection_spec.rb +0 -11
- data/spec/brainstem/api_docs/atlas_spec.rb +0 -6
- data/spec/brainstem/api_docs/builder_spec.rb +0 -4
- data/spec/brainstem/api_docs/controller_collection_spec.rb +0 -2
- data/spec/brainstem/api_docs/controller_spec.rb +29 -18
- data/spec/brainstem/api_docs/endpoint_collection_spec.rb +0 -6
- data/spec/brainstem/api_docs/endpoint_spec.rb +343 -13
- data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +0 -2
- data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +0 -1
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +0 -5
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +94 -8
- data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +0 -8
- data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +0 -7
- data/spec/brainstem/api_docs/formatters/open_api_specification/helper_spec.rb +210 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter_spec.rb +81 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter_spec.rb +672 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter_spec.rb +335 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter_spec.rb +59 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter_spec.rb +308 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter_spec.rb +89 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter_spec.rb +430 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter_spec.rb +190 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter_spec.rb +217 -0
- data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +0 -2
- data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +0 -2
- data/spec/brainstem/api_docs/presenter_collection_spec.rb +0 -2
- data/spec/brainstem/api_docs/presenter_spec.rb +58 -18
- data/spec/brainstem/api_docs/resolver_spec.rb +0 -1
- data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +0 -2
- data/spec/brainstem/api_docs/sinks/open_api_specification_sink_spec.rb +371 -0
- data/spec/brainstem/api_docs_spec.rb +2 -0
- data/spec/brainstem/cli/abstract_command_spec.rb +0 -4
- data/spec/brainstem/cli/generate_api_docs_command_spec.rb +53 -2
- data/spec/brainstem/concerns/controller_dsl_spec.rb +430 -64
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +0 -20
- data/spec/brainstem/preloader_spec.rb +0 -7
- data/spec/brainstem/presenter_spec.rb +0 -1
- data/spec/dummy/rails.rb +0 -1
- data/spec/spec_helpers/db.rb +0 -1
- metadata +37 -2
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter'
|
|
3
|
+
require 'brainstem/api_docs/endpoint'
|
|
4
|
+
|
|
5
|
+
module Brainstem
|
|
6
|
+
module ApiDocs
|
|
7
|
+
module Formatters
|
|
8
|
+
module OpenApiSpecification
|
|
9
|
+
module Version2
|
|
10
|
+
module Endpoint
|
|
11
|
+
describe ResponseDefinitionsFormatter do
|
|
12
|
+
let(:controller) { Object.new }
|
|
13
|
+
let(:presenter) { Object.new }
|
|
14
|
+
let(:atlas) { Object.new }
|
|
15
|
+
let(:action) { 'show' }
|
|
16
|
+
let(:http_methods) { %w(GET) }
|
|
17
|
+
let(:endpoint) {
|
|
18
|
+
::Brainstem::ApiDocs::Endpoint.new(
|
|
19
|
+
atlas,
|
|
20
|
+
endpoint_args
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
let(:endpoint_args) { {} }
|
|
24
|
+
|
|
25
|
+
subject { described_class.new(endpoint) }
|
|
26
|
+
|
|
27
|
+
before do
|
|
28
|
+
stub(presenter).contextual_documentation(:title) { 'Widget' }
|
|
29
|
+
stub(endpoint).presenter { presenter }
|
|
30
|
+
stub(endpoint).action { action }
|
|
31
|
+
stub(endpoint).custom_response_configuration_tree { {} }
|
|
32
|
+
stub(endpoint).http_methods { http_methods }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#call' do
|
|
36
|
+
context 'when delete request' do
|
|
37
|
+
let(:http_methods) { %w(DELETE) }
|
|
38
|
+
|
|
39
|
+
it 'formats the delete response and error response' do
|
|
40
|
+
any_instance_of(described_class) do |instance|
|
|
41
|
+
mock(instance).format_delete_response!
|
|
42
|
+
mock(instance).format_error_responses!
|
|
43
|
+
|
|
44
|
+
dont_allow(instance).format_schema_response!
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
subject.call
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'when request is not delete' do
|
|
52
|
+
let(:http_methods) { %w(GET) }
|
|
53
|
+
|
|
54
|
+
it 'formats the schema response and error response' do
|
|
55
|
+
any_instance_of(described_class) do |instance|
|
|
56
|
+
mock(instance).format_schema_response!
|
|
57
|
+
mock(instance).format_error_responses!
|
|
58
|
+
|
|
59
|
+
dont_allow(instance).format_delete_response!
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
subject.call
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#formatting' do
|
|
69
|
+
describe '#success_response_description' do
|
|
70
|
+
subject { described_class.new(endpoint).send(:success_response_description) }
|
|
71
|
+
|
|
72
|
+
context 'when `GET` request' do
|
|
73
|
+
let(:http_methods) { %w(GET) }
|
|
74
|
+
|
|
75
|
+
it { is_expected.to eq('A list of Widgets have been retrieved.') }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context 'when `POST` request' do
|
|
79
|
+
let(:http_methods) { %w(POST) }
|
|
80
|
+
|
|
81
|
+
it { is_expected.to eq('Widget has been created.') }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context 'when `PUT` request' do
|
|
85
|
+
let(:http_methods) { %w(PUT) }
|
|
86
|
+
|
|
87
|
+
it { is_expected.to eq('Widget has been updated.') }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context 'when `PATCH` request' do
|
|
91
|
+
let(:http_methods) { %w(PATCH) }
|
|
92
|
+
|
|
93
|
+
it { is_expected.to eq('Widget has been updated.') }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context 'when `DELETE` request' do
|
|
97
|
+
let(:http_methods) { %w(DELETE) }
|
|
98
|
+
|
|
99
|
+
it { is_expected.to eq('Widget has been deleted.') }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe '#format_delete_response!' do
|
|
104
|
+
let(:http_methods) { %w(DELETE) }
|
|
105
|
+
|
|
106
|
+
it 'returns the structure response for a destroy action' do
|
|
107
|
+
subject.send(:format_delete_response!)
|
|
108
|
+
|
|
109
|
+
expect(subject.output).to eq('204' => { 'description' => 'Widget has been deleted.' })
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe '#format_schema_response!' do
|
|
114
|
+
before do
|
|
115
|
+
stub(presenter).brainstem_keys { ['widgets'] }
|
|
116
|
+
stub(presenter).target_class { 'Widget' }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'returns the structured response for an endpoint' do
|
|
120
|
+
subject.send(:format_schema_response!)
|
|
121
|
+
|
|
122
|
+
expect(subject.output).to eq('200' => {
|
|
123
|
+
'description' => 'A list of Widgets have been retrieved.',
|
|
124
|
+
'schema' => {
|
|
125
|
+
'type' => 'object',
|
|
126
|
+
'properties' => {
|
|
127
|
+
'count' => { 'type' => 'integer', 'format' => 'int32' },
|
|
128
|
+
'meta' => {
|
|
129
|
+
'type' => 'object',
|
|
130
|
+
'properties' => {
|
|
131
|
+
'count' => { 'type' => 'integer', 'format' => 'int32' },
|
|
132
|
+
'page_count' => { 'type' => 'integer', 'format' => 'int32' },
|
|
133
|
+
'page_number' => { 'type' => 'integer', 'format' => 'int32' },
|
|
134
|
+
'page_size' => { 'type' => 'integer', 'format' => 'int32' },
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
'results' => {
|
|
138
|
+
'type' => 'array',
|
|
139
|
+
'items' => {
|
|
140
|
+
'type' => 'object',
|
|
141
|
+
'properties' => {
|
|
142
|
+
'key' => { 'type' => 'string' },
|
|
143
|
+
'id' => { 'type' => 'string' }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
'widgets' => {
|
|
148
|
+
'type' => 'object',
|
|
149
|
+
'additionalProperties' => {
|
|
150
|
+
'$ref' => '#/definitions/Widget'
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
describe '#format_error_responses!' do
|
|
160
|
+
it 'returns the structured errors responses for an endpoint' do
|
|
161
|
+
subject.send(:format_error_responses!)
|
|
162
|
+
|
|
163
|
+
expect(subject.output).to eq(
|
|
164
|
+
'400' => { 'description' => 'Bad Request', 'schema' => { '$ref' => '#/definitions/Errors' } },
|
|
165
|
+
'401' => { 'description' => 'Unauthorized request', 'schema' => { '$ref' => '#/definitions/Errors' } },
|
|
166
|
+
'403' => { 'description' => 'Forbidden request', 'schema' => { '$ref' => '#/definitions/Errors' } },
|
|
167
|
+
'404' => { 'description' => 'Page Not Found', 'schema' => { '$ref' => '#/definitions/Errors' } },
|
|
168
|
+
'503' => { 'description' => 'Service is unavailable', 'schema' => { '$ref' => '#/definitions/Errors' } }
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe '#format_custom_response!' do
|
|
174
|
+
context 'when the response is a hash' do
|
|
175
|
+
before do
|
|
176
|
+
stub(endpoint).custom_response_configuration_tree {
|
|
177
|
+
{
|
|
178
|
+
'_config' => {
|
|
179
|
+
'type' => 'hash',
|
|
180
|
+
},
|
|
181
|
+
'widget_name' => {
|
|
182
|
+
'_config' => {
|
|
183
|
+
'type' => 'string',
|
|
184
|
+
'info' => 'The name of the widget.',
|
|
185
|
+
'nodoc' => false
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
'widget_permission' => {
|
|
189
|
+
'_config' => {
|
|
190
|
+
'type' => 'hash',
|
|
191
|
+
'info' => 'The permissions of the widget.',
|
|
192
|
+
'nodoc' => false
|
|
193
|
+
},
|
|
194
|
+
'can_edit' => {
|
|
195
|
+
'_config' => {
|
|
196
|
+
'type' => 'boolean',
|
|
197
|
+
'info' => 'Can edit the widget.',
|
|
198
|
+
'nodoc' => false
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
}.with_indifferent_access
|
|
203
|
+
}
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it 'returns the response structure' do
|
|
207
|
+
subject.send(:format_custom_response!)
|
|
208
|
+
|
|
209
|
+
expect(subject.output).to eq('200' => {
|
|
210
|
+
'description' => 'A list of Widgets have been retrieved.',
|
|
211
|
+
'schema' => {
|
|
212
|
+
'type' => 'object',
|
|
213
|
+
'properties' => {
|
|
214
|
+
'widget_name' => {
|
|
215
|
+
'type' => 'string',
|
|
216
|
+
'description' => 'The name of the widget.'
|
|
217
|
+
},
|
|
218
|
+
'widget_permission' => {
|
|
219
|
+
'type' => 'object',
|
|
220
|
+
'description' => 'The permissions of the widget.',
|
|
221
|
+
'properties' => {
|
|
222
|
+
'can_edit' => {
|
|
223
|
+
'type' => 'boolean',
|
|
224
|
+
'description' => 'Can edit the widget.'
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
context 'when the response is an array' do
|
|
235
|
+
before do
|
|
236
|
+
stub(endpoint).custom_response_configuration_tree {
|
|
237
|
+
{
|
|
238
|
+
'_config' => {
|
|
239
|
+
'type' => 'array',
|
|
240
|
+
'item_type' => 'hash',
|
|
241
|
+
},
|
|
242
|
+
'widget_name' => {
|
|
243
|
+
'_config' => {
|
|
244
|
+
'type' => 'string',
|
|
245
|
+
'info' => 'The name of the widget.',
|
|
246
|
+
'nodoc' => false
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
'widget_permissions' => {
|
|
250
|
+
'_config' => {
|
|
251
|
+
'type' => 'array',
|
|
252
|
+
'item_type' => 'hash',
|
|
253
|
+
'info' => 'The permissions of the widget.',
|
|
254
|
+
'nodoc' => false
|
|
255
|
+
},
|
|
256
|
+
'can_edit' => {
|
|
257
|
+
'_config' => {
|
|
258
|
+
'type' => 'boolean',
|
|
259
|
+
'info' => 'Can edit the widget.',
|
|
260
|
+
'nodoc' => false
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
}.with_indifferent_access
|
|
265
|
+
}
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'returns the response structure' do
|
|
269
|
+
subject.send(:format_custom_response!)
|
|
270
|
+
|
|
271
|
+
expect(subject.output).to eq('200' => {
|
|
272
|
+
'description' => 'A list of Widgets have been retrieved.',
|
|
273
|
+
'schema' => {
|
|
274
|
+
'type' => 'array',
|
|
275
|
+
'items' => {
|
|
276
|
+
'type' => 'object',
|
|
277
|
+
'properties' => {
|
|
278
|
+
'widget_name' => {
|
|
279
|
+
'type' => 'string',
|
|
280
|
+
'description' => 'The name of the widget.'
|
|
281
|
+
},
|
|
282
|
+
'widget_permissions' => {
|
|
283
|
+
'type' => 'array',
|
|
284
|
+
'description' => 'The permissions of the widget.',
|
|
285
|
+
'items' => {
|
|
286
|
+
'type' => 'object',
|
|
287
|
+
'properties' => {
|
|
288
|
+
'can_edit' => {
|
|
289
|
+
'type' => 'boolean',
|
|
290
|
+
'description' => 'Can edit the widget.'
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
context 'when the response is not a hash or array' do
|
|
303
|
+
before do
|
|
304
|
+
stub(endpoint).custom_response_configuration_tree {
|
|
305
|
+
{
|
|
306
|
+
'_config' => {
|
|
307
|
+
'type' => 'boolean',
|
|
308
|
+
'info' => 'whether the widget exists',
|
|
309
|
+
'nodoc' => false
|
|
310
|
+
},
|
|
311
|
+
}.with_indifferent_access
|
|
312
|
+
}
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
it 'returns the response structure' do
|
|
316
|
+
subject.send(:format_custom_response!)
|
|
317
|
+
|
|
318
|
+
expect(subject.output).to eq('200' => {
|
|
319
|
+
'description' => 'A list of Widgets have been retrieved.',
|
|
320
|
+
'schema' => {
|
|
321
|
+
'type' => 'boolean',
|
|
322
|
+
'description' => 'Whether the widget exists.'
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter'
|
|
3
|
+
require 'brainstem/api_docs/endpoint_collection'
|
|
4
|
+
|
|
5
|
+
module Brainstem
|
|
6
|
+
module ApiDocs
|
|
7
|
+
module Formatters
|
|
8
|
+
module OpenApiSpecification
|
|
9
|
+
module Version2
|
|
10
|
+
describe EndpointCollectionFormatter do
|
|
11
|
+
let(:atlas) { Object.new }
|
|
12
|
+
let(:endpoint_collection) { EndpointCollection.new(atlas) }
|
|
13
|
+
let(:nodoc) { false }
|
|
14
|
+
|
|
15
|
+
subject { described_class.new(endpoint_collection) }
|
|
16
|
+
|
|
17
|
+
describe "#call" do
|
|
18
|
+
it "formats each endpoint" do
|
|
19
|
+
mock(subject).format_endpoints!
|
|
20
|
+
subject.call
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "formatting" do
|
|
25
|
+
describe "#format_endpoints!" do
|
|
26
|
+
let(:endpoint_1) { Object.new }
|
|
27
|
+
let(:endpoint_2) { Object.new }
|
|
28
|
+
let(:endpoint_3) { Object.new }
|
|
29
|
+
let(:endpoint_4) { Object.new }
|
|
30
|
+
|
|
31
|
+
before do
|
|
32
|
+
mock(endpoint_collection).only_documentable { [endpoint_1, endpoint_2, endpoint_3, endpoint_4] }
|
|
33
|
+
|
|
34
|
+
mock(endpoint_1).formatted_as(:oas_v2) { { "/sprockets" => { "get" => { info: "Get all sprockets" } } } }
|
|
35
|
+
mock(endpoint_2).formatted_as(:oas_v2) { { "/sprockets" => { "post" => { info: "Create a sprocket" } } } }
|
|
36
|
+
mock(endpoint_3).formatted_as(:oas_v2) { { "/sprockets/{id}" => { "patch" => { info: "Update a sprocket" } } } }
|
|
37
|
+
mock(endpoint_4).formatted_as(:oas_v2) { {} }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "joins each documentable endpoint" do
|
|
41
|
+
subject.send(:format_endpoints!)
|
|
42
|
+
expect(subject.output).to eq({
|
|
43
|
+
"/sprockets" => {
|
|
44
|
+
"get" => { info: "Get all sprockets" },
|
|
45
|
+
"post" => { info: "Create a sprocket" },
|
|
46
|
+
},
|
|
47
|
+
"/sprockets/{id}" => {
|
|
48
|
+
"patch" => { info: "Update a sprocket" }
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter_spec.rb
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter"
|
|
3
|
+
require "brainstem/api_docs/endpoint"
|
|
4
|
+
|
|
5
|
+
module Brainstem
|
|
6
|
+
module ApiDocs
|
|
7
|
+
module Formatters
|
|
8
|
+
module OpenApiSpecification
|
|
9
|
+
module Version2
|
|
10
|
+
describe EndpointFormatter do
|
|
11
|
+
let(:controller) { Object.new }
|
|
12
|
+
let(:presenter) { Object.new }
|
|
13
|
+
let(:atlas) { Object.new }
|
|
14
|
+
let(:endpoint) {
|
|
15
|
+
::Brainstem::ApiDocs::Endpoint.new(
|
|
16
|
+
atlas,
|
|
17
|
+
{
|
|
18
|
+
controller: controller,
|
|
19
|
+
http_methods: %w(get post),
|
|
20
|
+
path: "/v2/widgets(.:format)"
|
|
21
|
+
}.merge(endpoint_args)
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
let(:endpoint_args) { {} }
|
|
25
|
+
let(:nodoc) { false }
|
|
26
|
+
let(:controller_tag) { nil }
|
|
27
|
+
let(:security) { nil }
|
|
28
|
+
|
|
29
|
+
subject { described_class.new(endpoint) }
|
|
30
|
+
|
|
31
|
+
before do
|
|
32
|
+
stub(endpoint).presenter { presenter }
|
|
33
|
+
stub(endpoint).security { security }
|
|
34
|
+
stub(controller).tag { controller_tag }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "#call" do
|
|
38
|
+
before do
|
|
39
|
+
stub(endpoint).nodoc? { nodoc }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "when endpoint has no presenter associated with it" do
|
|
43
|
+
let(:nodoc) { true }
|
|
44
|
+
let(:presenter) { nil }
|
|
45
|
+
|
|
46
|
+
before do
|
|
47
|
+
any_instance_of(described_class) do |instance|
|
|
48
|
+
dont_allow(instance).format_summary!
|
|
49
|
+
dont_allow(instance).format_optional_info!
|
|
50
|
+
dont_allow(instance).format_security!
|
|
51
|
+
dont_allow(instance).format_parameters!
|
|
52
|
+
dont_allow(instance).format_response!
|
|
53
|
+
dont_allow(instance).format_tags!
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "returns an empty output" do
|
|
58
|
+
expect(subject.call).to eq({})
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "when it is nodoc" do
|
|
63
|
+
let(:nodoc) { true }
|
|
64
|
+
|
|
65
|
+
before do
|
|
66
|
+
any_instance_of(described_class) do |instance|
|
|
67
|
+
dont_allow(instance).format_summary!
|
|
68
|
+
dont_allow(instance).format_optional_info!
|
|
69
|
+
dont_allow(instance).format_security!
|
|
70
|
+
dont_allow(instance).format_parameters!
|
|
71
|
+
dont_allow(instance).format_response!
|
|
72
|
+
dont_allow(instance).format_tags!
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "returns an empty output" do
|
|
77
|
+
expect(subject.call).to eq({})
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context "when it is not nodoc" do
|
|
82
|
+
before do
|
|
83
|
+
stub(controller).tag { "CRUD: Widgets" }
|
|
84
|
+
stub(endpoint).security { "CRUD: Widgets" }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "formats endpoint properties" do
|
|
88
|
+
any_instance_of(described_class) do |instance|
|
|
89
|
+
mock(instance).format_summary!
|
|
90
|
+
mock(instance).format_optional_info!
|
|
91
|
+
mock(instance).format_security!
|
|
92
|
+
mock(instance).format_parameters!
|
|
93
|
+
mock(instance).format_response!
|
|
94
|
+
mock(instance).format_tags!
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
subject.call
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "formatting" do
|
|
103
|
+
let(:lorem) { "lorem ipsum dolor sit amet" }
|
|
104
|
+
let(:default_config) { {} }
|
|
105
|
+
let(:show_config) { {} }
|
|
106
|
+
|
|
107
|
+
let(:configuration) { {
|
|
108
|
+
:_default => default_config,
|
|
109
|
+
:show => show_config,
|
|
110
|
+
} }
|
|
111
|
+
|
|
112
|
+
let(:endpoint_args) { { action: :show } }
|
|
113
|
+
|
|
114
|
+
before do
|
|
115
|
+
stub(controller).configuration { configuration }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe "#formatted_url" do
|
|
119
|
+
let(:endpoint_args) { { path: "/widgets(.:format)" } }
|
|
120
|
+
|
|
121
|
+
it "returns the url without the format" do
|
|
122
|
+
expect(subject.send(:formatted_url)).to eq("/widgets")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context "when :id is present in the url" do
|
|
126
|
+
let(:endpoint_args) { { path: "/widgets/:id(.:format)" } }
|
|
127
|
+
|
|
128
|
+
it "replace the :id param with {id}" do
|
|
129
|
+
expect(subject.send(:formatted_url)).to eq("/widgets/{id}")
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
context "when :some_id is present in the url" do
|
|
134
|
+
let(:endpoint_args) { { path: "/widgets/:some_id/blah/:id(.:format)" } }
|
|
135
|
+
|
|
136
|
+
it "replace the :some_id param with {some_id}" do
|
|
137
|
+
expect(subject.send(:formatted_url)).to eq("/widgets/{some_id}/blah/{id}")
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
describe "#format_summary!" do
|
|
143
|
+
it "sets the title in the output" do
|
|
144
|
+
stub(endpoint).title { lorem }
|
|
145
|
+
subject.send(:format_summary!)
|
|
146
|
+
expect(subject.output["/widgets"]["get"]["summary"]).to eq(lorem)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe "#format_tags!" do
|
|
151
|
+
let(:controller_name) { "awesome_sauce" }
|
|
152
|
+
|
|
153
|
+
before do
|
|
154
|
+
stub(controller).name { controller_name }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
context "when controller is not assigned a tag" do
|
|
158
|
+
before do
|
|
159
|
+
stub(controller).tag { nil }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "sets the title in the output" do
|
|
163
|
+
subject.send(:format_tags!)
|
|
164
|
+
expect(subject.output["/widgets"]["get"]["tags"]).to eq(["Awesome Sauce"])
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
context "when controller is assigned a tag" do
|
|
169
|
+
before do
|
|
170
|
+
stub(controller).tag { "Breaking Bad" }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "sets the assigned tag name in the output" do
|
|
174
|
+
subject.send(:format_tags!)
|
|
175
|
+
expect(subject.output["/widgets"]["get"]["tags"]).to eq(["Breaking Bad"])
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
describe "#format_optional_info!" do
|
|
181
|
+
context "when present" do
|
|
182
|
+
let(:description) { " lorem ipsum dolor sit amet " }
|
|
183
|
+
let(:operation_id) { "Operation 1" }
|
|
184
|
+
let(:consumes) { %w(application/json) }
|
|
185
|
+
let(:produces) { %w(application/json) }
|
|
186
|
+
let(:schemes) { %w(http https) }
|
|
187
|
+
let(:external_docs) { { url: "/", description: "Blah" } }
|
|
188
|
+
let(:deprecated) { true }
|
|
189
|
+
|
|
190
|
+
before do
|
|
191
|
+
stub(endpoint).description { lorem }
|
|
192
|
+
stub(endpoint).operation_id { operation_id }
|
|
193
|
+
stub(endpoint).consumes { consumes }
|
|
194
|
+
stub(endpoint).produces { produces }
|
|
195
|
+
stub(endpoint).schemes { schemes }
|
|
196
|
+
stub(endpoint).external_docs { external_docs }
|
|
197
|
+
stub(endpoint).deprecated { deprecated }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "includes the description key with the given description" do
|
|
201
|
+
subject.send(:format_optional_info!)
|
|
202
|
+
|
|
203
|
+
output = subject.output["/widgets"]["get"]
|
|
204
|
+
expect(output["description"]).to eq("Lorem ipsum dolor sit amet.")
|
|
205
|
+
expect(output["operation_id"]).to eq(operation_id)
|
|
206
|
+
expect(output["consumes"]).to eq(consumes)
|
|
207
|
+
expect(output["produces"]).to eq(produces)
|
|
208
|
+
expect(output["schemes"]).to eq(schemes)
|
|
209
|
+
expect(output["external_docs"]).to eq(external_docs.with_indifferent_access)
|
|
210
|
+
expect(output["deprecated"]).to eq(deprecated)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
context "when absent" do
|
|
215
|
+
before do
|
|
216
|
+
stub(endpoint).description { nil }
|
|
217
|
+
stub(endpoint).operation_id { nil }
|
|
218
|
+
stub(endpoint).consumes { nil }
|
|
219
|
+
stub(endpoint).produces { nil }
|
|
220
|
+
stub(endpoint).schemes { nil }
|
|
221
|
+
stub(endpoint).external_docs { nil }
|
|
222
|
+
stub(endpoint).deprecated { nil }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "does not include the description key" do
|
|
226
|
+
subject.send(:format_optional_info!)
|
|
227
|
+
|
|
228
|
+
output = subject.output["/widgets"]["get"].keys
|
|
229
|
+
expect(output).to_not include(
|
|
230
|
+
"description",
|
|
231
|
+
"operation_id",
|
|
232
|
+
"consumes",
|
|
233
|
+
"produces",
|
|
234
|
+
"schemes",
|
|
235
|
+
"external_docs",
|
|
236
|
+
"deprecated"
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
describe "#format_security!" do
|
|
243
|
+
before do
|
|
244
|
+
stub(endpoint).security { security }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
context "when specified" do
|
|
248
|
+
let(:security) { { petstore_auth: ["write:pets", "read:pets"] } }
|
|
249
|
+
|
|
250
|
+
it "sets the security property in the output" do
|
|
251
|
+
subject.send(:format_security!)
|
|
252
|
+
|
|
253
|
+
expect(subject.output["/widgets"]["get"]["security"]).to eq(security.with_indifferent_access)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# NOTE: Empty Array removes all security restrictions on the endpoint.
|
|
258
|
+
context "when empty array is specified" do
|
|
259
|
+
let(:security) { [] }
|
|
260
|
+
|
|
261
|
+
it "sets the security property as an emtpy array" do
|
|
262
|
+
subject.send(:format_security!)
|
|
263
|
+
|
|
264
|
+
expect(subject.output["/widgets"]["get"]["security"]).to eq([])
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
context "when nothing is specified" do
|
|
269
|
+
let(:security) { nil }
|
|
270
|
+
|
|
271
|
+
it "does not add the security property" do
|
|
272
|
+
subject.send(:format_security!)
|
|
273
|
+
|
|
274
|
+
expect(subject.output["/widgets"]["get"]).to_not have_key("security")
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe "#format_parameters!" do
|
|
280
|
+
let(:formatted_params) { { "params_for_endpoint" => true } }
|
|
281
|
+
|
|
282
|
+
it "calls EndpointParamsFormatter" do
|
|
283
|
+
mock(Brainstem::ApiDocs::FORMATTERS[:parameters][:oas_v2])
|
|
284
|
+
.call(endpoint) { formatted_params }
|
|
285
|
+
subject.send(:format_parameters!)
|
|
286
|
+
|
|
287
|
+
expect(subject.output["/widgets"]["get"]["parameters"]).to eq(formatted_params)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
describe "#format_response!" do
|
|
292
|
+
let(:formatted_response) { { "response_for_endpoint" => true } }
|
|
293
|
+
|
|
294
|
+
it "calls EndpointResponseFormatter" do
|
|
295
|
+
mock(Brainstem::ApiDocs::FORMATTERS[:response][:oas_v2])
|
|
296
|
+
.call(endpoint) { formatted_response }
|
|
297
|
+
subject.send(:format_response!)
|
|
298
|
+
|
|
299
|
+
expect(subject.output["/widgets"]["get"]["responses"]).to eq(formatted_response)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|