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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/README.md +119 -0
  4. data/docs/api_doc_generator.markdown +45 -4
  5. data/docs/brainstem_executable.markdown +1 -1
  6. data/docs/oas_2_docgen.png +0 -0
  7. data/docs/oas_2_docgen_ascii.txt +78 -0
  8. data/lib/brainstem/api_docs.rb +23 -9
  9. data/lib/brainstem/api_docs/abstract_collection.rb +0 -13
  10. data/lib/brainstem/api_docs/atlas.rb +0 -14
  11. data/lib/brainstem/api_docs/builder.rb +0 -14
  12. data/lib/brainstem/api_docs/controller.rb +7 -16
  13. data/lib/brainstem/api_docs/controller_collection.rb +0 -3
  14. data/lib/brainstem/api_docs/endpoint.rb +73 -19
  15. data/lib/brainstem/api_docs/endpoint_collection.rb +0 -7
  16. data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +0 -2
  17. data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +1 -9
  18. data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +1 -9
  19. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +39 -24
  20. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +0 -13
  21. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +22 -35
  22. data/lib/brainstem/api_docs/formatters/open_api_specification/helper.rb +66 -0
  23. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter.rb +57 -0
  24. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter.rb +311 -0
  25. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter.rb +197 -0
  26. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter.rb +60 -0
  27. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb +162 -0
  28. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter.rb +126 -0
  29. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter.rb +132 -0
  30. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter.rb +99 -0
  31. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter.rb +123 -0
  32. data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +0 -7
  33. data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +1 -20
  34. data/lib/brainstem/api_docs/presenter.rb +21 -27
  35. data/lib/brainstem/api_docs/presenter_collection.rb +1 -11
  36. data/lib/brainstem/api_docs/resolver.rb +1 -8
  37. data/lib/brainstem/api_docs/sinks/abstract_sink.rb +0 -4
  38. data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +0 -9
  39. data/lib/brainstem/api_docs/sinks/open_api_specification_sink.rb +234 -0
  40. data/lib/brainstem/api_docs/sinks/stdout_sink.rb +0 -5
  41. data/lib/brainstem/cli.rb +0 -13
  42. data/lib/brainstem/cli/abstract_command.rb +0 -7
  43. data/lib/brainstem/cli/generate_api_docs_command.rb +48 -24
  44. data/lib/brainstem/concerns/controller_dsl.rb +288 -145
  45. data/lib/brainstem/concerns/formattable.rb +0 -5
  46. data/lib/brainstem/concerns/optional.rb +0 -1
  47. data/lib/brainstem/concerns/presenter_dsl.rb +2 -21
  48. data/lib/brainstem/dsl/configuration.rb +0 -11
  49. data/lib/brainstem/presenter.rb +0 -4
  50. data/lib/brainstem/version.rb +1 -1
  51. data/spec/brainstem/api_docs/abstract_collection_spec.rb +0 -11
  52. data/spec/brainstem/api_docs/atlas_spec.rb +0 -6
  53. data/spec/brainstem/api_docs/builder_spec.rb +0 -4
  54. data/spec/brainstem/api_docs/controller_collection_spec.rb +0 -2
  55. data/spec/brainstem/api_docs/controller_spec.rb +29 -18
  56. data/spec/brainstem/api_docs/endpoint_collection_spec.rb +0 -6
  57. data/spec/brainstem/api_docs/endpoint_spec.rb +343 -13
  58. data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +0 -2
  59. data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +0 -1
  60. data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +0 -5
  61. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +94 -8
  62. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +0 -8
  63. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +0 -7
  64. data/spec/brainstem/api_docs/formatters/open_api_specification/helper_spec.rb +210 -0
  65. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter_spec.rb +81 -0
  66. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter_spec.rb +672 -0
  67. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter_spec.rb +335 -0
  68. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter_spec.rb +59 -0
  69. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter_spec.rb +308 -0
  70. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter_spec.rb +89 -0
  71. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter_spec.rb +430 -0
  72. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter_spec.rb +190 -0
  73. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter_spec.rb +217 -0
  74. data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +0 -2
  75. data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +0 -2
  76. data/spec/brainstem/api_docs/presenter_collection_spec.rb +0 -2
  77. data/spec/brainstem/api_docs/presenter_spec.rb +58 -18
  78. data/spec/brainstem/api_docs/resolver_spec.rb +0 -1
  79. data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +0 -2
  80. data/spec/brainstem/api_docs/sinks/open_api_specification_sink_spec.rb +371 -0
  81. data/spec/brainstem/api_docs_spec.rb +2 -0
  82. data/spec/brainstem/cli/abstract_command_spec.rb +0 -4
  83. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +53 -2
  84. data/spec/brainstem/concerns/controller_dsl_spec.rb +430 -64
  85. data/spec/brainstem/concerns/presenter_dsl_spec.rb +0 -20
  86. data/spec/brainstem/preloader_spec.rb +0 -7
  87. data/spec/brainstem/presenter_spec.rb +0 -1
  88. data/spec/dummy/rails.rb +0 -1
  89. data/spec/spec_helpers/db.rb +0 -1
  90. metadata +37 -2
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter'
3
+ require 'brainstem/api_docs/controller'
4
+
5
+ module Brainstem
6
+ module ApiDocs
7
+ module Formatters
8
+ module OpenApiSpecification
9
+ module Version2
10
+ describe ControllerFormatter do
11
+ let(:const) { Object.new }
12
+ let(:atlas) { Object.new }
13
+ let(:controller) { Controller.new(atlas, const: const) }
14
+ let(:configuration) { {} }
15
+
16
+ let(:endpoint_1) { Object.new }
17
+ let(:endpoints) { [ endpoint_1 ] }
18
+ let(:nodoc) { false }
19
+ let(:options) { {} }
20
+
21
+ subject { described_class.new(controller, options) }
22
+
23
+ before do
24
+ stub(const).configuration { configuration }
25
+ end
26
+
27
+ describe "#call" do
28
+ let(:configuration) { { _default: { nodoc: nodoc } } }
29
+
30
+ context "when nodoc specified" do
31
+ let(:nodoc) { true }
32
+
33
+ before do
34
+ dont_allow(subject).format_actions!
35
+ end
36
+
37
+ it "returns an empty hash" do
38
+ expect(subject.call).to eq({})
39
+ end
40
+ end
41
+
42
+ context "when nodoc not specified" do
43
+ it "formats actions" do
44
+ mock(subject).format_actions!
45
+
46
+ subject.call
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "formatting" do
52
+ let(:lorem) { "lorem ipsum dolor sit amet" }
53
+ let(:default_config) { {} }
54
+ let(:configuration) { { _default: default_config } }
55
+
56
+ describe "#format_actions!" do
57
+ context "if include actions" do
58
+ let(:options) { { include_actions: true } }
59
+
60
+ it "calls formatted_as with :oas_v2 on the sorted endpoints in the controller" do
61
+ stub(controller).valid_sorted_endpoints.stub!.formatted_as(:oas_v2) { { index: true } }
62
+ subject.send(:format_actions!)
63
+ end
64
+ end
65
+
66
+ context "if not include actions" do
67
+ let(:options) { { include_actions: false } }
68
+
69
+ it "shows nothing" do
70
+ subject.send(:format_actions!)
71
+ expect(subject.output).to eq({})
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,672 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_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 ParamDefinitionsFormatter 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
+ {
21
+ http_methods: http_methods,
22
+ path: '/widgets(.:format)'
23
+ }.merge(endpoint_args)
24
+ )
25
+ }
26
+ let(:endpoint_args) { {} }
27
+ let(:nodoc) { false }
28
+
29
+ subject { described_class.new(endpoint) }
30
+
31
+ before do
32
+ stub(endpoint).presenter { presenter }
33
+ stub(endpoint).action { action }
34
+ end
35
+
36
+ describe '#call' do
37
+ context 'when request type is get' do
38
+ let(:http_methods) { %w(GET) }
39
+
40
+ context 'when action is index' do
41
+ let(:action) { 'index' }
42
+
43
+ it 'formats path, shared, query and body params for the endpoint' do
44
+ any_instance_of(described_class) do |instance|
45
+ mock(instance).format_path_params!
46
+ mock(instance).format_optional_params!
47
+ mock(instance).format_include_params!
48
+ mock(instance).format_query_params!
49
+ mock(instance).format_body_params!
50
+ mock(instance).format_pagination_params!
51
+ mock(instance).format_search_param!
52
+ mock(instance).format_only_param!
53
+ mock(instance).format_sort_order_params!
54
+ mock(instance).format_filter_params!
55
+ end
56
+
57
+ subject.call
58
+ end
59
+ end
60
+
61
+ context 'when action is show' do
62
+ let(:action) { 'show' }
63
+
64
+ it 'formats path, optional, query and body params for the endpoint' do
65
+ any_instance_of(described_class) do |instance|
66
+ mock(instance).format_path_params!
67
+ mock(instance).format_optional_params!
68
+ mock(instance).format_include_params!
69
+ mock(instance).format_query_params!
70
+ mock(instance).format_body_params!
71
+
72
+ dont_allow(instance).format_pagination_params!
73
+ dont_allow(instance).format_search_param!
74
+ dont_allow(instance).format_only_param!
75
+ dont_allow(instance).format_sort_order_params!
76
+ dont_allow(instance).format_filter_params!
77
+ end
78
+
79
+ subject.call
80
+ end
81
+ end
82
+ end
83
+
84
+ context 'when request type is `delete`' do
85
+ let(:http_methods) { %w(DELETE) }
86
+ let(:action) { 'destroy' }
87
+
88
+ it 'formats path, query and body param for the endpoint' do
89
+ any_instance_of(described_class) do |instance|
90
+ mock(instance).format_path_params!
91
+ mock(instance).format_query_params!
92
+ mock(instance).format_body_params!
93
+
94
+ dont_allow(instance).format_pagination_params!
95
+ dont_allow(instance).format_search_param!
96
+ dont_allow(instance).format_only_param!
97
+ dont_allow(instance).format_sort_order_params!
98
+ dont_allow(instance).format_optional_params!
99
+ dont_allow(instance).format_include_params!
100
+ dont_allow(instance).format_filter_params!
101
+ end
102
+
103
+ subject.call
104
+ end
105
+ end
106
+
107
+ context 'when request type is not delete' do
108
+ let(:http_methods) { %w(PATCH) }
109
+ let(:action) { 'update' }
110
+
111
+ it 'formats path, query and body param for the endpoint' do
112
+ any_instance_of(described_class) do |instance|
113
+ mock(instance).format_optional_params!
114
+ mock(instance).format_include_params!
115
+ mock(instance).format_path_params!
116
+ mock(instance).format_query_params!
117
+ mock(instance).format_body_params!
118
+
119
+ dont_allow(instance).format_pagination_params!
120
+ dont_allow(instance).format_search_param!
121
+ dont_allow(instance).format_only_param!
122
+ dont_allow(instance).format_sort_order_params!
123
+ dont_allow(instance).format_filter_params!
124
+ end
125
+
126
+ subject.call
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#formatting' do
132
+ describe '#format_path_params' do
133
+ context 'when no path params' do
134
+ let(:endpoint_args) { { path: '/widgets(.:format)' } }
135
+
136
+ it 'does any add path params to the output' do
137
+ subject.send(:format_path_params!)
138
+
139
+ expect(subject.output).to eq([])
140
+ end
141
+ end
142
+
143
+ context 'when single path param is present' do
144
+ let(:endpoint_args) { { path: '/widgets/:id(.:format)' } }
145
+
146
+ it 'adds path params to the output' do
147
+ subject.send(:format_path_params!)
148
+
149
+ expect(subject.output).to eq([
150
+ {
151
+ 'in' => 'path',
152
+ 'name' => 'id',
153
+ 'required' => true,
154
+ 'type' => 'integer',
155
+ 'description' => 'The ID of the Model.'
156
+ }
157
+ ])
158
+ end
159
+ end
160
+
161
+ context 'when multiple path params are present' do
162
+ let(:endpoint_args) { { path: '/sprockets/:sprocket_id/widgets/:id(.:format)' } }
163
+
164
+ it 'adds path params to the output' do
165
+ subject.send(:format_path_params!)
166
+
167
+ expect(subject.output).to eq([
168
+ {
169
+ 'in' => 'path',
170
+ 'name' => 'sprocket_id',
171
+ 'required' => true,
172
+ 'type' => 'integer',
173
+ 'description' => 'The ID of the Sprocket.'
174
+ },
175
+ {
176
+ 'in' => 'path',
177
+ 'name' => 'id',
178
+ 'required' => true,
179
+ 'type' => 'integer',
180
+ 'description' => 'The ID of the Model.'
181
+ }
182
+ ])
183
+ end
184
+ end
185
+ end
186
+
187
+ describe '#format_query_params' do
188
+ let(:mocked_params_configuration_tree) do
189
+ {
190
+ sprocket_name: {
191
+ _config: {
192
+ type: 'string',
193
+ info: 'The name of the sprocket'
194
+ }
195
+ },
196
+ sprocket_ids: {
197
+ _config: {
198
+ type: 'array',
199
+ item_type: 'integer'
200
+ }
201
+ },
202
+ widget: {
203
+ _config: {
204
+ type: 'hash',
205
+ },
206
+ title: {
207
+ _config: {
208
+ type: 'string'
209
+ }
210
+ },
211
+ },
212
+ id: {
213
+ _config: {
214
+ type: 'integer',
215
+ info: 'The ID of the model ',
216
+ required: true
217
+ }
218
+ },
219
+ }.with_indifferent_access
220
+ end
221
+
222
+ before do
223
+ mock(endpoint).params_configuration_tree { mocked_params_configuration_tree }
224
+ end
225
+
226
+ it 'exclusively adds non-nested fields as query params ordered by it\'s required & name attributes' do
227
+ subject.send(:format_query_params!)
228
+
229
+ expect(subject.output).to eq([
230
+ {
231
+ 'in' => 'query',
232
+ 'name' => 'id',
233
+ 'required' => true,
234
+ 'type' => 'integer',
235
+ 'format' => 'int32',
236
+ 'description' => 'The ID of the model.'
237
+ },
238
+ {
239
+ 'in' => 'query',
240
+ 'name' => 'sprocket_ids',
241
+ 'type' => 'array',
242
+ 'items' => { 'type' => 'integer' }
243
+ },
244
+ {
245
+ 'in' => 'query',
246
+ 'name' => 'sprocket_name',
247
+ 'type' => 'string',
248
+ 'description' => 'The name of the sprocket.'
249
+ }
250
+ ])
251
+ end
252
+
253
+ context 'when type of the param is unknown' do
254
+ before do
255
+ mocked_params_configuration_tree[:id][:_config][:type] = 'invalid'
256
+ end
257
+
258
+ it 'raises an error' do
259
+ expect { subject.send(:format_query_params!) }.to raise_error(StandardError)
260
+ end
261
+ end
262
+ end
263
+
264
+ describe '#format_body_params' do
265
+ let(:mocked_params_configuration_tree) do
266
+ {
267
+ id: {
268
+ _config: {
269
+ type: 'integer'
270
+ }
271
+ },
272
+ task: {
273
+ _config: {
274
+ type: 'hash',
275
+ info: 'attributes for the task '
276
+ },
277
+ name: {
278
+ _config: {
279
+ type: 'string',
280
+ required: true,
281
+ info: 'name of the task '
282
+ }
283
+ },
284
+ subs: {
285
+ _config: {
286
+ type: 'hash',
287
+ info: 'sub tasks of the task'
288
+ },
289
+ name: {
290
+ _config: {
291
+ type: 'string',
292
+ required: true
293
+ }
294
+ },
295
+ },
296
+ checklist: {
297
+ _config: {
298
+ type: 'array',
299
+ item: 'hash'
300
+ },
301
+ name: {
302
+ _config: {
303
+ type: 'string'
304
+ }
305
+ },
306
+ },
307
+ },
308
+ creator: {
309
+ _config: {
310
+ type: 'hash',
311
+ info: 'attributes for the creator'
312
+ },
313
+ id: {
314
+ _config: {
315
+ type: 'integer',
316
+ info: 'ID of the creator'
317
+ }
318
+ },
319
+ },
320
+ assignees: {
321
+ _config: {
322
+ type: 'array',
323
+ info: 'attributes for the assignees'
324
+ },
325
+ id: {
326
+ _config: {
327
+ type: 'integer',
328
+ info: 'ID of the assignee'
329
+ }
330
+ },
331
+ active: {
332
+ _config: {
333
+ type: 'boolean',
334
+ info: 'activates the assignment'
335
+ }
336
+ }
337
+ }
338
+ }.with_indifferent_access
339
+ end
340
+
341
+ before do
342
+ mock(endpoint).params_configuration_tree { mocked_params_configuration_tree }
343
+ end
344
+
345
+ it 'adds nested fields to the query params' do
346
+ subject.send(:format_body_params!)
347
+
348
+ expect(subject.output).to eq([
349
+ {
350
+ 'in' => 'body',
351
+ 'required' => true,
352
+ 'name' => 'body',
353
+ 'schema' => {
354
+ 'type' => 'object',
355
+ 'properties' => {
356
+
357
+ 'task' => {
358
+ 'title' => 'task',
359
+ 'type' => 'object',
360
+ 'description' => 'Attributes for the task.',
361
+ 'properties' => {
362
+ 'name' => {
363
+ 'title' => 'name',
364
+ 'description' => 'Name of the task.',
365
+ 'type' => 'string'
366
+ },
367
+ 'subs' => {
368
+ 'title' => 'subs',
369
+ 'description' => 'Sub tasks of the task.',
370
+ 'type' => 'object',
371
+ 'properties' => {
372
+ 'name' => {
373
+ 'title' => 'name',
374
+ 'type' => 'string'
375
+ }
376
+ }
377
+ },
378
+ 'checklist' => {
379
+ 'title' => 'checklist',
380
+ 'type' => 'array',
381
+ 'items' => {
382
+ 'type' => 'object',
383
+ 'properties' => {
384
+ 'name' => {
385
+ 'title' => 'name',
386
+ 'type' => 'string'
387
+ }
388
+ }
389
+ }
390
+ }
391
+ }
392
+ },
393
+
394
+ 'creator' => {
395
+ 'title' => 'creator',
396
+ 'type' => 'object',
397
+ 'description' => 'Attributes for the creator.',
398
+ 'properties' => {
399
+ 'id' => {
400
+ 'title' => 'id',
401
+ 'description' => 'ID of the creator.',
402
+ 'type' => 'integer',
403
+ 'format' => 'int32'
404
+ },
405
+ }
406
+ },
407
+
408
+ 'assignees' => {
409
+ 'title' => 'assignees',
410
+ 'type' => 'array',
411
+ 'description' => 'Attributes for the assignees.',
412
+ 'items' => {
413
+ 'type' => 'object',
414
+ 'properties' => {
415
+ 'id' => {
416
+ 'title' => 'id',
417
+ 'description' => 'ID of the assignee.',
418
+ 'type' => 'integer',
419
+ 'format' => 'int32'
420
+ },
421
+ 'active' => {
422
+ 'title' => 'active',
423
+ 'description' => 'Activates the assignment.',
424
+ 'type' => 'boolean'
425
+ },
426
+ }
427
+ }
428
+ }
429
+ }
430
+ },
431
+ }
432
+ ])
433
+ end
434
+
435
+ context 'when type of the param is unknown' do
436
+ before do
437
+ mocked_params_configuration_tree[:task][:_config][:type] = 'invalid'
438
+ end
439
+
440
+ it 'raises an error' do
441
+ expect { subject.send(:format_body_params!) }.to raise_error(StandardError)
442
+ end
443
+ end
444
+
445
+ context 'when no body params are present' do
446
+ let(:mocked_params_configuration_tree) { {} }
447
+
448
+ it 'does not add any output' do
449
+ subject.send(:format_body_params!)
450
+
451
+ expect(subject.output).to be_empty
452
+ end
453
+ end
454
+ end
455
+
456
+ describe '#format_pagination_params!' do
457
+ it 'adds the page & per_page query params' do
458
+ subject.send(:format_pagination_params!)
459
+
460
+ expect(subject.output).to eq([
461
+ {
462
+ 'in' => 'query',
463
+ 'name' => 'page',
464
+ 'type' => 'integer',
465
+ 'format' => 'int32',
466
+ 'default' => 1
467
+ },
468
+ {
469
+ 'in' => 'query',
470
+ 'name' => 'per_page',
471
+ 'type' => 'integer',
472
+ 'format' => 'int32',
473
+ 'default' => 20,
474
+ 'maximum' => 200
475
+ }
476
+ ])
477
+ end
478
+ end
479
+
480
+ describe '#format_search_param!' do
481
+ before do
482
+ mock(presenter).searchable? { searchable }
483
+ end
484
+
485
+ context 'when presenter has search config' do
486
+ let(:searchable) { true }
487
+
488
+ it 'adds the search query params' do
489
+ subject.send(:format_search_param!)
490
+
491
+ expect(subject.output).to eq([
492
+ {
493
+ 'in' => 'query',
494
+ 'name' => 'search',
495
+ 'type' => 'string'
496
+ }
497
+ ])
498
+ end
499
+ end
500
+
501
+ context 'when presenter has no search config' do
502
+ let(:searchable) { false }
503
+
504
+ it 'adds the search query params' do
505
+ subject.send(:format_search_param!)
506
+
507
+ expect(subject.output).to eq([])
508
+ end
509
+ end
510
+ end
511
+
512
+ describe '#format_optional_params!' do
513
+ let(:optional_fields) { ['field_1', 'field_2'] }
514
+
515
+ before do
516
+ mock(presenter).optional_field_names { optional_fields }
517
+ end
518
+
519
+ it 'adds the optional fields query param' do
520
+ subject.send(:format_optional_params!)
521
+
522
+ expect(subject.output).to eq([
523
+ {
524
+ 'in' => 'query',
525
+ 'name' => 'optional_fields',
526
+ 'description' => 'Allows you to request one or more optional fields as an array.',
527
+ 'type' => 'array',
528
+ 'items' => {
529
+ 'type' => 'string',
530
+ 'enum' => optional_fields
531
+ }
532
+ }
533
+ ])
534
+ end
535
+ end
536
+
537
+ describe '#format_include_params!' do
538
+ let(:valid_associations) {
539
+ {
540
+ 'association_1' => OpenStruct.new(
541
+ name: 'association_1',
542
+ target_class: 'association_1_class',
543
+ description: 'Association_1 description.'
544
+ ),
545
+ 'association_2' => OpenStruct.new(
546
+ name: 'association_2',
547
+ target_class: 'association_2_class',
548
+ description: 'Association_2 description.',
549
+ options: { restrict_to_only: true }
550
+ )
551
+ }
552
+ }
553
+
554
+ before do
555
+ stub(presenter).valid_associations { valid_associations }
556
+ end
557
+
558
+ it 'adds the include filter as a query param' do
559
+ subject.send(:format_include_params!)
560
+
561
+ expect(subject.output.length).to eq(1)
562
+
563
+ param_def = subject.output[0]
564
+ expect(param_def.except('description')).to eq(
565
+ 'name' => 'include',
566
+ 'in' => 'query',
567
+ 'type' => 'string',
568
+ )
569
+ expect(param_def['description']).to include('e.g. `include=association1,association2`.')
570
+ expect(param_def['description']).to include('`association_1` (association_1_class) - Association_1 description')
571
+ association_2_desc = 'Association_2 description. Restricted to queries using the `only` parameter.'
572
+ expect(param_def['description']).to include("`association_2` (association_2_class) - #{association_2_desc}")
573
+ end
574
+ end
575
+
576
+ describe '#format_only_param!' do
577
+ it 'adds the only query params' do
578
+ subject.send(:format_only_param!)
579
+
580
+ expect(subject.output).to eq([
581
+ {
582
+ 'in' => 'query',
583
+ 'name' => 'only',
584
+ 'type' => 'string',
585
+ 'description' =>
586
+ "Allows you to request one or more resources directly by ID. Multiple IDs can be supplied\n" +
587
+ "in a comma separated list, like `GET /api/v1/workspaces.json?only=5,6,7`."
588
+ }
589
+ ])
590
+ end
591
+ end
592
+
593
+ describe '#format_sort_order_params!' do
594
+ before do
595
+ mock(presenter).default_sort_order { 'title:asc' }
596
+ mock(presenter).valid_sort_orders {
597
+ {
598
+ 'title' => { info: 'Order by title aphabetically' },
599
+ 'sprocket_name' => { info: 'Order by sprocket name aphabetically' },
600
+ }
601
+ }
602
+ end
603
+
604
+ it 'adds the sort order as query params' do
605
+ subject.send(:format_sort_order_params!)
606
+
607
+ expect(subject.output).to eq([
608
+ {
609
+ 'in' => 'query',
610
+ 'name' => 'order',
611
+ 'type' => 'string',
612
+ 'default' => 'title:asc',
613
+ 'description' => "Supply `order` with the name of a valid sort field for the endpoint and a direction.\n\n" +
614
+ "Valid values: `sprocket_name:asc`, `sprocket_name:desc`, `title:asc`, and `title:desc`."
615
+ }
616
+ ])
617
+ end
618
+ end
619
+
620
+ describe '#format_filters' do
621
+ let(:mocked_valid_filters) {
622
+ {
623
+ filter_1: { type: 'string', info: 'Filter by string' },
624
+ filter_2: { type: 'boolean', default: false },
625
+ filter_3: { type: 'array', item_type: 'string', items: ['Option 1', 'Option 2'], default: 'Option 1' }
626
+ }
627
+ }
628
+
629
+ before do
630
+ mock(presenter).valid_filters { mocked_valid_filters }
631
+ end
632
+
633
+ it 'adds filters to the output as query params' do
634
+ subject.send(:format_filter_params!)
635
+
636
+ expect(subject.output).to eq([
637
+ {
638
+ 'in' => 'query',
639
+ 'name' => 'filter_1',
640
+ 'type' => 'string',
641
+ 'description' => 'Filter by string.'
642
+ },
643
+ {
644
+ 'in' => 'query',
645
+ 'name' => 'filter_2',
646
+ 'type' => 'boolean',
647
+ 'default' => false
648
+ },
649
+ {
650
+ 'in' => 'query',
651
+ 'name' => 'filter_3',
652
+ 'type' => 'array',
653
+ 'items' => {
654
+ 'type' => 'string',
655
+ 'enum' => [
656
+ 'Option 1',
657
+ 'Option 2'
658
+ ],
659
+ 'default' => 'Option 1'
660
+ }
661
+ }
662
+ ])
663
+ end
664
+ end
665
+ end
666
+ end
667
+ end
668
+ end
669
+ end
670
+ end
671
+ end
672
+ end