1 package analysis
2
3 import (
4 "path/filepath"
5 "strings"
6 "testing"
7
8 "github.com/go-openapi/analysis/internal/antest"
9 "github.com/go-openapi/analysis/internal/flatten/operations"
10 "github.com/go-openapi/analysis/internal/flatten/sortref"
11 "github.com/go-openapi/jsonpointer"
12 "github.com/go-openapi/spec"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15 )
16
17 func TestName_FromRef(t *testing.T) {
18 t.Parallel()
19
20 values := []struct{ Source, Expected string }{
21 {"#/definitions/errorModel", "errorModel"},
22 {"http://somewhere.com/definitions/errorModel", "errorModel"},
23 {"http://somewhere.com/definitions/errorModel.json", "errorModel"},
24 {"/definitions/errorModel", "errorModel"},
25 {"/definitions/errorModel.json", "errorModel"},
26 {"http://somewhere.com", "somewhereCom"},
27 {"#", ""},
28 }
29
30 for _, v := range values {
31 assert.Equal(t, v.Expected, nameFromRef(spec.MustCreateRef(v.Source), &FlattenOpts{}))
32 }
33 }
34
35 func TestName_FromRefMangle(t *testing.T) {
36 t.Parallel()
37
38 values := []struct{ Source, Expected, ExpectedKeepName string }{
39 {"#/definitions/ErrorModel", "errorModel", "ErrorModel"},
40 {"#/definitions/Error_Model", "errorModel", "Error_Model"},
41 {"http://somewhere.com/definitions/errorModel", "errorModel", "errorModel"},
42 {"http://somewhere.com/definitions/ErrorModel.json", "errorModel", "ErrorModel"},
43 {"/definitions/ErrorModel", "errorModel", "ErrorModel"},
44 {"/definitions/ErrorModel.json", "errorModel", "ErrorModel"},
45 {"http://somewhere.com", "somewhereCom", "somewhere com"},
46 {"#", "", ""},
47 }
48
49 for _, v := range values {
50 assert.Equal(t, v.Expected, nameFromRef(spec.MustCreateRef(v.Source), &FlattenOpts{}))
51 assert.Equal(t, v.ExpectedKeepName, nameFromRef(spec.MustCreateRef(v.Source), &FlattenOpts{KeepNames: true}))
52 }
53 }
54
55 func TestName_Definition(t *testing.T) {
56 values := []struct {
57 Source, Expected string
58 Definitions spec.Definitions
59 }{
60 {"#/definitions/errorModel", "errorModel", map[string]spec.Schema(nil)},
61 {"http://somewhere.com/definitions/errorModel", "errorModel", map[string]spec.Schema(nil)},
62 {"#/definitions/errorModel", "errorModel", map[string]spec.Schema{"apples": *spec.StringProperty()}},
63 {"#/definitions/errorModel", "errorModelOAIGen", map[string]spec.Schema{"errorModel": *spec.StringProperty()}},
64 {"#/definitions/errorModel", "errorModelOAIGen1",
65 map[string]spec.Schema{"errorModel": *spec.StringProperty(), "errorModelOAIGen": *spec.StringProperty()}},
66 {"#", "oaiGen", nil},
67 }
68
69 for _, v := range values {
70 u, _ := uniqifyName(v.Definitions, nameFromRef(spec.MustCreateRef(v.Source), &FlattenOpts{}))
71 assert.Equal(t, v.Expected, u)
72 }
73 }
74
75 func TestName_SplitKey(t *testing.T) {
76 type KeyFlag uint64
77
78 const (
79 isOperation KeyFlag = 1 << iota
80 isDefinition
81 isSharedOperationParam
82 isOperationParam
83 isOperationResponse
84 isDefaultResponse
85 isStatusCodeResponse
86 )
87
88 values := []struct {
89 Key string
90 Flags KeyFlag
91 PathItemRef spec.Ref
92 PathRef spec.Ref
93 Name string
94 }{
95 {
96 "#/paths/~1some~1where~1{id}/parameters/1/schema",
97 isOperation | isSharedOperationParam,
98 spec.Ref{},
99 spec.MustCreateRef("#/paths/~1some~1where~1{id}"),
100 "",
101 },
102 {
103 "#/paths/~1some~1where~1{id}/get/parameters/2/schema",
104 isOperation | isOperationParam,
105 spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"),
106 spec.MustCreateRef("#/paths/~1some~1where~1{id}"),
107 "",
108 },
109 {
110 "#/paths/~1some~1where~1{id}/get/responses/default/schema",
111 isOperation | isOperationResponse | isDefaultResponse,
112 spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"),
113 spec.MustCreateRef("#/paths/~1some~1where~1{id}"),
114 "Default",
115 },
116 {
117 "#/paths/~1some~1where~1{id}/get/responses/200/schema",
118 isOperation | isOperationResponse | isStatusCodeResponse,
119 spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"),
120 spec.MustCreateRef("#/paths/~1some~1where~1{id}"),
121 "OK",
122 },
123 {
124 "#/definitions/namedAgain",
125 isDefinition,
126 spec.Ref{},
127 spec.Ref{},
128 "namedAgain",
129 },
130 {
131 "#/definitions/datedRecords/items/1",
132 isDefinition,
133 spec.Ref{},
134 spec.Ref{},
135 "datedRecords",
136 },
137 {
138 "#/definitions/datedRecords/items/1",
139 isDefinition,
140 spec.Ref{},
141 spec.Ref{},
142 "datedRecords",
143 },
144 {
145 "#/definitions/datedTaggedRecords/items/1",
146 isDefinition,
147 spec.Ref{},
148 spec.Ref{},
149 "datedTaggedRecords",
150 },
151 {
152 "#/definitions/datedTaggedRecords/additionalItems",
153 isDefinition,
154 spec.Ref{},
155 spec.Ref{},
156 "datedTaggedRecords",
157 },
158 {
159 "#/definitions/otherRecords/items",
160 isDefinition,
161 spec.Ref{},
162 spec.Ref{},
163 "otherRecords",
164 },
165 {
166 "#/definitions/tags/additionalProperties",
167 isDefinition,
168 spec.Ref{},
169 spec.Ref{},
170 "tags",
171 },
172 {
173 "#/definitions/namedThing/properties/name",
174 isDefinition,
175 spec.Ref{},
176 spec.Ref{},
177 "namedThing",
178 },
179 }
180
181 for i, v := range values {
182 parts := sortref.KeyParts(v.Key)
183 pref := parts.PathRef()
184 piref := parts.PathItemRef()
185 assert.Equal(t, v.PathRef.String(), pref.String(), "pathRef: %s at %d", v.Key, i)
186 assert.Equal(t, v.PathItemRef.String(), piref.String(), "pathItemRef: %s at %d", v.Key, i)
187
188 if v.Flags&isOperation != 0 {
189 assert.True(t, parts.IsOperation(), "isOperation: %s at %d", v.Key, i)
190 } else {
191 assert.False(t, parts.IsOperation(), "isOperation: %s at %d", v.Key, i)
192 }
193
194 if v.Flags&isDefinition != 0 {
195 assert.True(t, parts.IsDefinition(), "isDefinition: %s at %d", v.Key, i)
196 assert.Equal(t, v.Name, parts.DefinitionName(), "definition name: %s at %d", v.Key, i)
197 } else {
198 assert.False(t, parts.IsDefinition(), "isDefinition: %s at %d", v.Key, i)
199 if v.Name != "" {
200 assert.Equal(t, v.Name, parts.ResponseName(), "response name: %s at %d", v.Key, i)
201 }
202 }
203
204 if v.Flags&isOperationParam != 0 {
205 assert.True(t, parts.IsOperationParam(), "isOperationParam: %s at %d", v.Key, i)
206 } else {
207 assert.False(t, parts.IsOperationParam(), "isOperationParam: %s at %d", v.Key, i)
208 }
209
210 if v.Flags&isSharedOperationParam != 0 {
211 assert.True(t, parts.IsSharedOperationParam(), "isSharedOperationParam: %s at %d", v.Key, i)
212 } else {
213 assert.False(t, parts.IsSharedOperationParam(), "isSharedOperationParam: %s at %d", v.Key, i)
214 }
215
216 if v.Flags&isOperationResponse != 0 {
217 assert.True(t, parts.IsOperationResponse(), "isOperationResponse: %s at %d", v.Key, i)
218 } else {
219 assert.False(t, parts.IsOperationResponse(), "isOperationResponse: %s at %d", v.Key, i)
220 }
221
222 if v.Flags&isDefaultResponse != 0 {
223 assert.True(t, parts.IsDefaultResponse(), "isDefaultResponse: %s at %d", v.Key, i)
224 } else {
225 assert.False(t, parts.IsDefaultResponse(), "isDefaultResponse: %s at %d", v.Key, i)
226 }
227
228 if v.Flags&isStatusCodeResponse != 0 {
229 assert.True(t, parts.IsStatusCodeResponse(), "isStatusCodeResponse: %s at %d", v.Key, i)
230 } else {
231 assert.False(t, parts.IsStatusCodeResponse(), "isStatusCodeResponse: %s at %d", v.Key, i)
232 }
233 }
234 }
235
236 func TestName_NamesFromKey(t *testing.T) {
237 bp := filepath.Join("fixtures", "inline_schemas.yml")
238 sp := antest.LoadOrFail(t, bp)
239
240 values := []struct {
241 Key string
242 Names []string
243 }{
244 {"#/paths/~1some~1where~1{id}/parameters/1/schema",
245 []string{"GetSomeWhereID params body", "PostSomeWhereID params body"}},
246 {"#/paths/~1some~1where~1{id}/get/parameters/2/schema", []string{"GetSomeWhereID params body"}},
247 {"#/paths/~1some~1where~1{id}/get/responses/default/schema", []string{"GetSomeWhereID Default body"}},
248 {"#/paths/~1some~1where~1{id}/get/responses/200/schema", []string{"GetSomeWhereID OK body"}},
249 {"#/definitions/namedAgain", []string{"namedAgain"}},
250 {"#/definitions/datedTag/allOf/1", []string{"datedTag allOf 1"}},
251 {"#/definitions/datedRecords/items/1", []string{"datedRecords tuple 1"}},
252 {"#/definitions/datedTaggedRecords/items/1", []string{"datedTaggedRecords tuple 1"}},
253 {"#/definitions/datedTaggedRecords/additionalItems", []string{"datedTaggedRecords tuple additionalItems"}},
254 {"#/definitions/otherRecords/items", []string{"otherRecords items"}},
255 {"#/definitions/tags/additionalProperties", []string{"tags additionalProperties"}},
256 {"#/definitions/namedThing/properties/name", []string{"namedThing name"}},
257 }
258
259 for i, v := range values {
260 ptr, err := jsonpointer.New(definitionPtr(v.Key)[1:])
261 require.NoError(t, err)
262
263 vv, _, err := ptr.Get(sp)
264 require.NoError(t, err)
265
266 switch tv := vv.(type) {
267 case *spec.Schema:
268 aschema, err := Schema(SchemaOpts{Schema: tv, Root: sp, BasePath: bp})
269 require.NoError(t, err)
270 names := namesFromKey(sortref.KeyParts(v.Key), aschema, operations.AllOpRefsByRef(New(sp), nil))
271 assert.Equal(t, v.Names, names, "for %s at %d", v.Key, i)
272 case spec.Schema:
273 aschema, err := Schema(SchemaOpts{Schema: &tv, Root: sp, BasePath: bp})
274 require.NoError(t, err)
275 names := namesFromKey(sortref.KeyParts(v.Key), aschema, operations.AllOpRefsByRef(New(sp), nil))
276 assert.Equal(t, v.Names, names, "for %s at %d", v.Key, i)
277 default:
278 assert.Fail(t, "unknown type", "got %T", vv)
279 }
280 }
281 }
282
283 func TestName_BuildNameWithReservedKeyWord(t *testing.T) {
284 s := sortref.SplitKey([]string{"definitions", "fullview", "properties", "properties"})
285 startIdx := 2
286 segments := []string{"fullview"}
287 newName := s.BuildName(segments, startIdx, partAdder(nil))
288 assert.Equal(t, "fullview properties", newName)
289
290 s = sortref.SplitKey([]string{"definitions", "fullview",
291 "properties", "properties", "properties", "properties", "properties", "properties"})
292 newName = s.BuildName(segments, startIdx, partAdder(nil))
293 assert.Equal(t, "fullview"+strings.Repeat(" properties", 3), newName)
294 }
295
296 func TestName_InlinedSchemas(t *testing.T) {
297 values := []struct {
298 Key string
299 Location string
300 Ref spec.Ref
301 }{
302 {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2/properties/name",
303 "#/definitions/getSomeWhereIdParamsBodyRecordItems2/properties/name",
304 spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2Name"),
305 },
306 {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/1",
307 "#/definitions/getSomeWhereIdParamsBodyRecord/items/1",
308 spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems1"),
309 },
310
311 {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2",
312 "#/definitions/getSomeWhereIdParamsBodyRecord/items/2",
313 spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2"),
314 },
315
316 {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2/properties/name",
317 "#/definitions/getSomeWhereIdOKBodyRecordItems2/properties/name",
318 spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems2Name"),
319 },
320
321 {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/1",
322 "#/definitions/getSomeWhereIdOKBodyRecord/items/1",
323 spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems1"),
324 },
325
326 {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2",
327 "#/definitions/getSomeWhereIdOKBodyRecord/items/2",
328 spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems2"),
329 },
330
331 {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record",
332 "#/definitions/getSomeWhereIdOKBody/properties/record",
333 spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecord"),
334 },
335
336 {"#/paths/~1some~1where~1{id}/get/responses/200/schema",
337 "#/paths/~1some~1where~1{id}/get/responses/200/schema",
338 spec.MustCreateRef("#/definitions/getSomeWhereIdOKBody"),
339 },
340
341 {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2/properties/name",
342 "#/definitions/getSomeWhereIdDefaultBodyRecordItems2/properties/name",
343 spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems2Name"),
344 },
345
346 {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/1",
347 "#/definitions/getSomeWhereIdDefaultBodyRecord/items/1",
348 spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems1"),
349 },
350
351 {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2",
352 "#/definitions/getSomeWhereIdDefaultBodyRecord/items/2",
353 spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems2"),
354 },
355
356 {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record",
357 "#/definitions/getSomeWhereIdDefaultBody/properties/record",
358 spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecord"),
359 },
360
361 {"#/paths/~1some~1where~1{id}/get/responses/default/schema",
362 "#/paths/~1some~1where~1{id}/get/responses/default/schema",
363 spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBody"),
364 },
365
366
367
368
369
370
371
372
373
374
375 {"#/definitions/nestedThing/properties/record/items/2/properties/name",
376 "#/definitions/nestedThingRecordItems2/properties/name",
377 spec.MustCreateRef("#/definitions/nestedThingRecordItems2Name"),
378 },
379
380 {"#/definitions/nestedThing/properties/record/items/1",
381 "#/definitions/nestedThingRecord/items/1",
382 spec.MustCreateRef("#/definitions/nestedThingRecordItems1"),
383 },
384
385 {"#/definitions/nestedThing/properties/record/items/2",
386 "#/definitions/nestedThingRecord/items/2",
387 spec.MustCreateRef("#/definitions/nestedThingRecordItems2"),
388 },
389
390 {"#/definitions/datedRecords/items/1",
391 "#/definitions/datedRecords/items/1",
392 spec.MustCreateRef("#/definitions/datedRecordsItems1"),
393 },
394
395 {"#/definitions/datedTaggedRecords/items/1",
396 "#/definitions/datedTaggedRecords/items/1",
397 spec.MustCreateRef("#/definitions/datedTaggedRecordsItems1"),
398 },
399
400 {"#/definitions/namedThing/properties/name",
401 "#/definitions/namedThing/properties/name",
402 spec.MustCreateRef("#/definitions/namedThingName"),
403 },
404
405 {"#/definitions/nestedThing/properties/record",
406 "#/definitions/nestedThing/properties/record",
407 spec.MustCreateRef("#/definitions/nestedThingRecord"),
408 },
409
410 {"#/definitions/records/items/0",
411 "#/definitions/records/items/0",
412 spec.MustCreateRef("#/definitions/recordsItems0"),
413 },
414
415 {"#/definitions/datedTaggedRecords/additionalItems",
416 "#/definitions/datedTaggedRecords/additionalItems",
417 spec.MustCreateRef("#/definitions/datedTaggedRecordsItemsAdditionalItems"),
418 },
419
420 {"#/definitions/otherRecords/items",
421 "#/definitions/otherRecords/items",
422 spec.MustCreateRef("#/definitions/otherRecordsItems"),
423 },
424
425 {"#/definitions/tags/additionalProperties",
426 "#/definitions/tags/additionalProperties",
427 spec.MustCreateRef("#/definitions/tagsAdditionalProperties"),
428 },
429 }
430
431 bp := filepath.Join("fixtures", "nested_inline_schemas.yml")
432 sp := antest.LoadOrFail(t, bp)
433
434 require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{
435 RelativeBase: bp,
436 SkipSchemas: true,
437 }))
438
439 require.NoError(t, nameInlinedSchemas(&FlattenOpts{
440 Spec: New(sp),
441 BasePath: bp,
442 }))
443
444 for i, v := range values {
445 ptr, err := jsonpointer.New(v.Location[1:])
446 require.NoErrorf(t, err, "at %d for %s", i, v.Key)
447
448 vv, _, err := ptr.Get(sp)
449 require.NoErrorf(t, err, "at %d for %s", i, v.Key)
450
451 switch tv := vv.(type) {
452 case *spec.Schema:
453 assert.Equal(t, v.Ref.String(), tv.Ref.String(), "at %d for %s", i, v.Key)
454 case spec.Schema:
455 assert.Equal(t, v.Ref.String(), tv.Ref.String(), "at %d for %s", i, v.Key)
456 case *spec.SchemaOrBool:
457 var sRef spec.Ref
458 if tv != nil && tv.Schema != nil {
459 sRef = tv.Schema.Ref
460 }
461 assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key)
462 case *spec.SchemaOrArray:
463 var sRef spec.Ref
464 if tv != nil && tv.Schema != nil {
465 sRef = tv.Schema.Ref
466 }
467 assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key)
468 default:
469 assert.Fail(t, "unknown type", "got %T", vv)
470 }
471 }
472
473 for k, rr := range New(sp).allSchemas {
474 if strings.HasPrefix(k, "#/responses") || strings.HasPrefix(k, "#/parameters") {
475 continue
476 }
477 if rr.Schema == nil || rr.Schema.Ref.String() != "" || rr.TopLevel {
478 continue
479 }
480 asch, err := Schema(SchemaOpts{Schema: rr.Schema, Root: sp, BasePath: bp})
481 require.NoErrorf(t, err, "for key: %s", k)
482
483 if !asch.IsSimpleSchema && !asch.IsArray && !asch.IsMap {
484 assert.Fail(t, "not a top level schema", "for key: %s", k)
485 }
486 }
487 }
488
489 func TestFlattenSchema_UnitGuards(t *testing.T) {
490 t.Parallel()
491
492 parts := sortref.KeyParts("#/nowhere/arbitrary/pointer")
493 res := GenLocation(parts)
494 assert.Equal(t, "", res)
495 }
496
497 func definitionPtr(key string) string {
498 if !strings.HasPrefix(key, "#/definitions") {
499 return key
500 }
501
502 return strings.Join(strings.Split(key, "/")[:3], "/")
503 }
504
View as plain text