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