haveapi-go-client 0.29.12 → 0.30.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/.gitignore +1 -0
- data/README.md +5 -0
- data/docs/documentation.md +171 -0
- data/haveapi-go-client.gemspec +1 -1
- data/lib/haveapi/go_client/documentation.rb +516 -0
- data/lib/haveapi/go_client/generator.rb +16 -3
- data/lib/haveapi/go_client/version.rb +1 -1
- data/spec/documentation.Gemfile +6 -0
- data/spec/documentation_acceptance.rb +271 -0
- data/spec/documentation_build_check.rb +31 -0
- data/spec/documentation_wire_check.rb +94 -0
- data/spec/support/documentation_fixture.rb +70 -0
- data/spec/support/test_server.rb +1 -1
- data/spec/unit/documentation_spec.rb +286 -0
- metadata +11 -3
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
require 'haveapi/go_client/documentation'
|
|
2
|
+
require_relative '../support/documentation_fixture'
|
|
3
|
+
|
|
4
|
+
RSpec.describe HaveAPI::GoClient::Documentation do
|
|
5
|
+
let(:description) { GoDocumentationFixture.description }
|
|
6
|
+
let(:adapter) { described_class.new(description:, import_path: 'example.test/sdk/client', module_version: 'v1.2.3') }
|
|
7
|
+
let(:action) { description.dig(:resources, :client, :resources, :child, :actions, :client) }
|
|
8
|
+
let(:sample) { GoDocumentationFixture.sample }
|
|
9
|
+
let(:context) do
|
|
10
|
+
Struct.new(:version, :base_url, :description, :resource_path, :action_name, :action, :sample).new(version: '7', base_url: 'https://api.example.test/mounted/', description:,
|
|
11
|
+
resource_path: %w[client child], action_name: 'client', action:, sample:)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def render
|
|
15
|
+
adapter.render(kind: :example, context:, mode: :sync)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'resolves canonical generator members and aliases without generator or serving I/O' do
|
|
19
|
+
allow(HaveAPI::GoClient::Generator).to receive(:new).and_call_original
|
|
20
|
+
allow(HaveAPI::GoClient::Generator).to receive(:allocate).and_call_original
|
|
21
|
+
allow(File).to receive(:read).and_call_original
|
|
22
|
+
model = HaveAPI::GoClient::ApiVersion.new(GoDocumentationFixture.description)
|
|
23
|
+
resolved = adapter.resolve(resource_path: %w[client child], action_name: 'perform')
|
|
24
|
+
canonical = model.resources.find { |resource| resource.name == 'client' }.resources.first.actions.first
|
|
25
|
+
|
|
26
|
+
expect(resolved.action.go_member_names).to eq(canonical.go_member_names)
|
|
27
|
+
expect(resolved.resource.resource_path.map(&:go_member_name)).to eq(%w[Client Child])
|
|
28
|
+
expect(resolved.action.go_member_name).to eq('ClientAction')
|
|
29
|
+
expect(render[:status]).to eq(:code)
|
|
30
|
+
expect(HaveAPI::GoClient::Generator).not_to have_received(:new)
|
|
31
|
+
expect(HaveAPI::GoClient::Generator).not_to have_received(:allocate)
|
|
32
|
+
expect(File).not_to have_received(:read)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'uses the full saved model even when the visible description has fewer siblings' do
|
|
36
|
+
description[:resources][:set_timeout] = { resources: {}, actions: {} }
|
|
37
|
+
adapter
|
|
38
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
39
|
+
context.description[:resources].delete(:hidden)
|
|
40
|
+
context.description[:resources].delete(:set_timeout)
|
|
41
|
+
|
|
42
|
+
expect(adapter.resolve(resource_path: ['set_timeout'], action_name: 'missing')).to be_nil
|
|
43
|
+
expect(render[:code]).not_to include('hidden', 'secret', 'SetTimeout')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'selects no inputs before supplied setters, preserving null, omission, false and zero' do
|
|
47
|
+
result = render
|
|
48
|
+
expect(result).to be_frozen
|
|
49
|
+
expect(result[:code]).to include('client "example.test/sdk/client"', 'client.New("https://api.example.test")',
|
|
50
|
+
'api.Client.Child.ClientAction.Prepare()',
|
|
51
|
+
'invocation.SetPathParamInt("client_id", 42)',
|
|
52
|
+
'invocation.SetPathParamString("child_id", "a/b")',
|
|
53
|
+
'input := invocation.NewInput().SelectParameters()', 'input.SetNameNil(true)',
|
|
54
|
+
'input.SetEnabled(false)', 'input.SetCount(0)')
|
|
55
|
+
expect(result[:code]).not_to include('SetUnused')
|
|
56
|
+
expect(result[:code].index('SelectParameters()')).to be < result[:code].index('input.SetNameNil')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'preserves an explicitly empty request and escapes source literals' do
|
|
60
|
+
sample[:request] = {}
|
|
61
|
+
expect(render[:code]).to include('invocation.NewInput().SelectParameters()')
|
|
62
|
+
expect(render[:code]).not_to include('input :=')
|
|
63
|
+
sample[:request] = { name: "quote\"\\\n雪</script>" }
|
|
64
|
+
expect(render[:code]).to include('input.SetName("quote\\"\\\\\\n雪</script>")')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'rejects list input, Custom fields, invalid values, missing paths and stale contracts' do
|
|
68
|
+
sample[:request][:count] = 'wrong'
|
|
69
|
+
expect(render[:status]).to eq(:limitation)
|
|
70
|
+
sample[:request][:count] = 0
|
|
71
|
+
sample[:path_params].pop
|
|
72
|
+
expect(render[:reason]).to include('path parameters')
|
|
73
|
+
sample[:path_params] << 'child'
|
|
74
|
+
action[:input][:parameters][:name][:nullable] = false
|
|
75
|
+
expect(render[:status]).to eq(:limitation)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'reports a list-input limitation rather than discarding fields' do
|
|
79
|
+
action[:input][:layout] = 'hash_list'
|
|
80
|
+
sample[:request] = [{ name: 'a' }]
|
|
81
|
+
expect(render).to include(status: :limitation, reason: /list input/)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'reports unsupported Custom input and output without rendering its contents' do
|
|
85
|
+
action[:input][:parameters][:custom] = { type: 'Custom' }
|
|
86
|
+
sample[:request][:custom] = { secret_value: 'do not render' }
|
|
87
|
+
expect(render).to include(status: :limitation, reason: /Custom/)
|
|
88
|
+
expect(render.to_s).not_to include('secret_value', 'do not render')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'supports list output and refuses scalar null output fidelity claims' do
|
|
92
|
+
action[:output][:layout] = 'hash_list'
|
|
93
|
+
sample[:response] = [{ id: 1 }, { id: 2 }]
|
|
94
|
+
expect(render[:code]).to include('for _, item := range response.Output', 'fmt.Println(item.Id)')
|
|
95
|
+
sample[:response] = [{ name: nil }]
|
|
96
|
+
expect(render).to include(status: :limitation, reason: /scalar null/)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'renders transport errors and API failures separately without invented success output' do
|
|
100
|
+
sample[:status] = false
|
|
101
|
+
sample[:response] = nil
|
|
102
|
+
expect(render[:code]).to include('if err != nil', 'if !response.Status', 'response.Message, response.Errors')
|
|
103
|
+
expect(render[:code]).not_to include('response.Output')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'never exposes a saved action absent from the authorized description' do
|
|
107
|
+
adapter
|
|
108
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
109
|
+
context.description[:resources].delete(:client)
|
|
110
|
+
expect(render).to include(status: :limitation)
|
|
111
|
+
expect(render.to_s).not_to include('secret', 'hidden')
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'checks response schema drift only on fields in the filtered sample' do
|
|
115
|
+
adapter
|
|
116
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
117
|
+
context.description.dig(:resources, :client, :resources, :child, :actions, :client, :output, :parameters, :id)[:type] = 'String'
|
|
118
|
+
expect(render).to include(status: :limitation, reason: /response field/)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'only renders advertised authentication and uses environment placeholders' do
|
|
122
|
+
basic = adapter.render(kind: :auth, context:, mode: :sync, auth_method: :basic, auth_description: {})
|
|
123
|
+
expect(basic[:code]).to include('api.SetBasicAuthentication(os.Getenv("HAVEAPI_USER"), os.Getenv("HAVEAPI_PASSWORD"))')
|
|
124
|
+
oauth = adapter.render(kind: :auth, context:, mode: :sync, auth_method: :oauth2, auth_description: {})
|
|
125
|
+
expect(oauth[:code]).to include('api.SetExistingOAuth2Auth(os.Getenv("HAVEAPI_ACCESS_TOKEN"))')
|
|
126
|
+
token = adapter.render(kind: :auth, context:, mode: :sync, auth_method: :token, auth_description: {})
|
|
127
|
+
expect(token[:code]).to include('api.SetExistingTokenAuth(os.Getenv("HAVEAPI_TOKEN"))')
|
|
128
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
129
|
+
context.description[:authentication].delete(:token)
|
|
130
|
+
expect(adapter.render(kind: :auth, context:, mode: :sync, auth_method: :token, auth_description: {})).to include(status: :limitation)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'does not show operation helpers when action-state visibility is absent' do
|
|
134
|
+
action[:blocking] = true
|
|
135
|
+
action[:meta][:global][:output] = GoDocumentationFixture.io({ action_state_id: { type: 'Integer' } }, namespace: false)
|
|
136
|
+
adapter
|
|
137
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
138
|
+
context.description[:resources].delete(:action_state)
|
|
139
|
+
expect(render[:code]).not_to include('response.OperationStatus')
|
|
140
|
+
expect(render[:code]).to include('does not establish operation completion')
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'uses the origin with mounted versioned SDK paths and rejects a changed mount' do
|
|
144
|
+
expect(render[:code]).to include('client.New("https://api.example.test")')
|
|
145
|
+
expect(adapter.resolve(resource_path: %w[client child], action_name: 'client').action.path).to start_with('/mounted/v7/')
|
|
146
|
+
adapter
|
|
147
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
148
|
+
context.description.dig(:resources, :client, :resources, :child, :actions, :client)[:path] = '/other/v7/widgets'
|
|
149
|
+
expect(render).to include(status: :limitation, reason: /route/)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'preserves the URL constructor and its authentication and generation options' do
|
|
153
|
+
connection = instance_double(HaveAPI::Client::Communicator, describe_api: description, authenticate: nil)
|
|
154
|
+
allow(HaveAPI::Client::Communicator).to receive(:new).with('https://fixture.invalid').and_return(connection)
|
|
155
|
+
generator = HaveAPI::GoClient::Generator.new('https://fixture.invalid', '/unused',
|
|
156
|
+
version: '7', module: 'example.test/sdk', package: 'client',
|
|
157
|
+
basic_user: 'fixture-user', basic_password: 'fixture-password')
|
|
158
|
+
expect(connection).to have_received(:authenticate).with(:basic, user: 'fixture-user', password: 'fixture-password')
|
|
159
|
+
expect(connection).to have_received(:describe_api).with('7')
|
|
160
|
+
expect(generator.dst).to eq('/unused')
|
|
161
|
+
expect(generator.module).to eq('example.test/sdk')
|
|
162
|
+
expect(generator.package).to eq('client')
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'builds a fixture generator from saved input without fetching or writing' do
|
|
166
|
+
allow(HaveAPI::Client::Communicator).to receive(:new).and_call_original
|
|
167
|
+
allow(FileUtils).to receive(:mkpath).and_call_original
|
|
168
|
+
generator = HaveAPI::GoClient::Generator.from_description(description, '/unused', module: 'example.test/sdk', package: 'client')
|
|
169
|
+
expect(generator.dst).to eq('/unused')
|
|
170
|
+
expect(HaveAPI::Client::Communicator).not_to have_received(:new)
|
|
171
|
+
expect(FileUtils).not_to have_received(:mkpath)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'rejects malformed saved schemas at initialization' do
|
|
175
|
+
description[:resources][:client][:resources][:child][:actions][:client][:path] = nil
|
|
176
|
+
expect { adapter }.to raise_error(ArgumentError, /saved Go SDK action/)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'checks Go field and method name collisions' do
|
|
180
|
+
action[:input][:parameters][:select_parameters] = { type: 'String' }
|
|
181
|
+
expect(render).to include(status: :limitation, reason: /name collisions/)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'shows operation methods only for visible matching state actions and metadata' do
|
|
185
|
+
action[:blocking] = true
|
|
186
|
+
action[:meta][:global][:output] = GoDocumentationFixture.io({ action_state_id: { type: 'Integer' } }, namespace: false)
|
|
187
|
+
expect(render[:code]).to include('response.IsBlocking()', 'response.OperationStatus()')
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
%i[global object].each do |role|
|
|
191
|
+
[true, false].each do |success|
|
|
192
|
+
it "limits #{role} metadata required in the saved SDK for status #{success}" do
|
|
193
|
+
action[:meta][role] = { input: GoDocumentationFixture.io({ hidden_requirement: { type: 'Boolean', required: true, default: 'private default' } }) }
|
|
194
|
+
sample[:status] = success
|
|
195
|
+
adapter
|
|
196
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
197
|
+
visible = context.description.dig(:resources, :client, :resources, :child, :actions, :client)
|
|
198
|
+
visible[:meta].delete(role)
|
|
199
|
+
|
|
200
|
+
expect(render).to include(status: :limitation, reason: 'This action requires metadata that is absent from the sample.')
|
|
201
|
+
expect(render.to_s).not_to include('hidden_requirement', 'private default', 'SetHiddenRequirement')
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "limits authorized-live #{role} metadata newly required for status #{success}" do
|
|
205
|
+
adapter
|
|
206
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
207
|
+
visible = context.description.dig(:resources, :client, :resources, :child, :actions, :client)
|
|
208
|
+
visible[:meta][role] = { input: GoDocumentationFixture.io({ confirm: { type: 'Boolean', required: true } }) }
|
|
209
|
+
sample[:status] = success
|
|
210
|
+
|
|
211
|
+
expect(render).to include(status: :limitation, reason: 'This action requires metadata that is absent from the sample.')
|
|
212
|
+
expect(render.to_s).not_to include('confirm', 'SetConfirm')
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'preserves optional global guidance and allows optional object metadata without sample values' do
|
|
218
|
+
action[:meta][:global][:input][:parameters][:includes][:required] = false
|
|
219
|
+
action[:meta][:object] = { input: GoDocumentationFixture.io({ confirm: { type: 'Boolean', required: false } }) }
|
|
220
|
+
expect(render[:code]).to include('// metadata := invocation.NewMetaInput().SelectParameters()', '// metadata.SetIncludes(value)')
|
|
221
|
+
expect(render[:code]).not_to include('SetConfirm')
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'keeps metadata-free success and failure examples executable' do
|
|
225
|
+
action[:meta] = {}
|
|
226
|
+
expect(render[:status]).to eq(:code)
|
|
227
|
+
expect(render[:code]).not_to include('NewMetaInput')
|
|
228
|
+
sample[:status] = false
|
|
229
|
+
expect(render[:code]).to include('if !response.Status')
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'does not disclose hidden global metadata setters' do
|
|
233
|
+
adapter
|
|
234
|
+
context.description = Marshal.load(Marshal.dump(description))
|
|
235
|
+
context.description.dig(:resources, :client, :resources, :child, :actions, :client, :meta, :global, :input, :parameters).clear
|
|
236
|
+
expect(render[:code]).not_to include('NewMetaInput', 'SetIncludes')
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'rejects Custom output, invalid path values, and ambiguous aliases safely' do
|
|
240
|
+
action[:output][:parameters][:payload] = { type: 'Custom' }
|
|
241
|
+
sample[:response][:payload] = { private_name: 'private value' }
|
|
242
|
+
expect(render).to include(status: :limitation, reason: /Custom/)
|
|
243
|
+
expect(render.to_s).not_to include('private_name', 'private value')
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
it 'keeps alias resolution unambiguous and canonical names preferred' do
|
|
247
|
+
resource = description.dig(:resources, :client, :resources, :child)
|
|
248
|
+
resource[:actions][:other] = GoDocumentationFixture.action(aliases: %w[perform client])
|
|
249
|
+
expect(adapter.resolve(resource_path: %w[client child], action_name: 'perform')).to be_nil
|
|
250
|
+
expect(adapter.resolve(resource_path: %w[client child], action_name: 'client')).not_to be_nil
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it 'preserves partial and nullable Resource output while rejecting scalar association values' do
|
|
254
|
+
action[:output][:parameters][:owner] = { type: 'Resource', resource: ['owner'], value_id: 'id', value_label: 'label', nullable: true }
|
|
255
|
+
sample[:response][:owner] = { id: 9 }
|
|
256
|
+
expect(render[:code]).to include('if response.Output.Owner != nil', 'fmt.Println(response.Output.Owner.Id)')
|
|
257
|
+
expect(render[:code]).not_to include('Owner.Label')
|
|
258
|
+
sample[:response][:owner] = nil
|
|
259
|
+
expect(render[:code]).to include('fmt.Println(response.Output.Owner == nil)')
|
|
260
|
+
sample[:response][:owner] = 9
|
|
261
|
+
expect(render).to include(status: :limitation, reason: /requires an object/)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'does not print associated fields missing from another documented list row' do
|
|
265
|
+
action[:output][:layout] = 'hash_list'
|
|
266
|
+
action[:output][:parameters][:owner] = { type: 'Resource', resource: ['owner'], value_id: 'id', value_label: 'label' }
|
|
267
|
+
sample[:response] = [{ owner: { id: 1, label: 'one' } }, { owner: { id: 2 } }]
|
|
268
|
+
expect(render[:code]).to include('fmt.Println(item.Owner.Id)')
|
|
269
|
+
expect(render[:code]).not_to include('Owner.Label')
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it 'does not guess path argument types or wrap integers' do
|
|
273
|
+
sample[:path_params] = [2**64, 'child']
|
|
274
|
+
expect(render).to include(status: :limitation, reason: /int64 range/)
|
|
275
|
+
sample[:path_params] = [true, 'child']
|
|
276
|
+
expect(render).to include(status: :limitation, reason: /integer or string/)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'rejects malformed profile imports and accepts JSON descriptions' do
|
|
280
|
+
expect do
|
|
281
|
+
described_class.new(description:, import_path: "bad\nsource", module_version: 'v1.0.0')
|
|
282
|
+
end.to raise_error(ArgumentError)
|
|
283
|
+
json_adapter = described_class.new(description: JSON.parse(JSON.generate(description)), import_path: 'example.test/sdk', module_version: 'v1.0.0')
|
|
284
|
+
expect(json_adapter.resolve(resource_path: %w[client child], action_name: 'client')).not_to be_nil
|
|
285
|
+
end
|
|
286
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haveapi-go-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.30.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jakub Skokan
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.30.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.30.0
|
|
26
26
|
description: Go client generator
|
|
27
27
|
email:
|
|
28
28
|
- jakub.skokan@vpsfree.cz
|
|
@@ -36,6 +36,7 @@ files:
|
|
|
36
36
|
- README.md
|
|
37
37
|
- Rakefile
|
|
38
38
|
- bin/haveapi-go-client
|
|
39
|
+
- docs/documentation.md
|
|
39
40
|
- haveapi-go-client.gemspec
|
|
40
41
|
- lib/haveapi/go_client.rb
|
|
41
42
|
- lib/haveapi/go_client/action.rb
|
|
@@ -47,6 +48,7 @@ files:
|
|
|
47
48
|
- lib/haveapi/go_client/authentication/unsupported.rb
|
|
48
49
|
- lib/haveapi/go_client/authentication_methods.rb
|
|
49
50
|
- lib/haveapi/go_client/cli.rb
|
|
51
|
+
- lib/haveapi/go_client/documentation.rb
|
|
50
52
|
- lib/haveapi/go_client/erb_template.rb
|
|
51
53
|
- lib/haveapi/go_client/generator.rb
|
|
52
54
|
- lib/haveapi/go_client/i18n_messages.yml
|
|
@@ -61,9 +63,15 @@ files:
|
|
|
61
63
|
- lib/haveapi/go_client/resource.rb
|
|
62
64
|
- lib/haveapi/go_client/utils.rb
|
|
63
65
|
- lib/haveapi/go_client/version.rb
|
|
66
|
+
- spec/documentation.Gemfile
|
|
67
|
+
- spec/documentation_acceptance.rb
|
|
68
|
+
- spec/documentation_build_check.rb
|
|
69
|
+
- spec/documentation_wire_check.rb
|
|
64
70
|
- spec/integration/generator_spec.rb
|
|
65
71
|
- spec/spec_helper.rb
|
|
72
|
+
- spec/support/documentation_fixture.rb
|
|
66
73
|
- spec/support/test_server.rb
|
|
74
|
+
- spec/unit/documentation_spec.rb
|
|
67
75
|
- template/action.go.erb
|
|
68
76
|
- template/authentication.go.erb
|
|
69
77
|
- template/authentication/basic.go.erb
|