1// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package analysis
16
17import (
18	"encoding/json"
19	"fmt"
20	"log"
21	"os"
22	"path/filepath"
23	"sort"
24	"strconv"
25	"testing"
26
27	"github.com/stretchr/testify/require"
28
29	"github.com/go-openapi/spec"
30	"github.com/go-openapi/swag"
31	"github.com/stretchr/testify/assert"
32)
33
34func schemeNames(schemes [][]SecurityRequirement) []string {
35	var names []string
36	for _, scheme := range schemes {
37		for _, v := range scheme {
38			names = append(names, v.Name)
39		}
40	}
41	sort.Strings(names)
42	return names
43}
44
45func makeFixturepec(pi, pi2 spec.PathItem, formatParam *spec.Parameter) *spec.Swagger {
46	return &spec.Swagger{
47		SwaggerProps: spec.SwaggerProps{
48			Consumes: []string{"application/json"},
49			Produces: []string{"application/json"},
50			Security: []map[string][]string{
51				{"apikey": nil},
52			},
53			SecurityDefinitions: map[string]*spec.SecurityScheme{
54				"basic":  spec.BasicAuth(),
55				"apiKey": spec.APIKeyAuth("api_key", "query"),
56				"oauth2": spec.OAuth2AccessToken("http://authorize.com", "http://token.com"),
57			},
58			Parameters: map[string]spec.Parameter{"format": *formatParam},
59			Paths: &spec.Paths{
60				Paths: map[string]spec.PathItem{
61					"/":      pi,
62					"/items": pi2,
63				},
64			},
65		},
66	}
67}
68
69func TestAnalyzer(t *testing.T) {
70	formatParam := spec.QueryParam("format").Typed("string", "")
71
72	limitParam := spec.QueryParam("limit").Typed("integer", "int32")
73	limitParam.Extensions = spec.Extensions(map[string]interface{}{})
74	limitParam.Extensions.Add("go-name", "Limit")
75
76	skipParam := spec.QueryParam("skip").Typed("integer", "int32")
77	pi := spec.PathItem{}
78	pi.Parameters = []spec.Parameter{*limitParam}
79
80	op := &spec.Operation{}
81	op.Consumes = []string{"application/x-yaml"}
82	op.Produces = []string{"application/x-yaml"}
83	op.Security = []map[string][]string{
84		{"oauth2": {}},
85		{"basic": nil},
86	}
87	op.ID = "someOperation"
88	op.Parameters = []spec.Parameter{*skipParam}
89	pi.Get = op
90
91	pi2 := spec.PathItem{}
92	pi2.Parameters = []spec.Parameter{*limitParam}
93	op2 := &spec.Operation{}
94	op2.ID = "anotherOperation"
95	op2.Parameters = []spec.Parameter{*skipParam}
96	pi2.Get = op2
97
98	spec := makeFixturepec(pi, pi2, formatParam)
99	analyzer := New(spec)
100
101	assert.Len(t, analyzer.consumes, 2)
102	assert.Len(t, analyzer.produces, 2)
103	assert.Len(t, analyzer.operations, 1)
104	assert.Equal(t, analyzer.operations["GET"]["/"], spec.Paths.Paths["/"].Get)
105
106	expected := []string{"application/x-yaml"}
107	sort.Strings(expected)
108	consumes := analyzer.ConsumesFor(spec.Paths.Paths["/"].Get)
109	sort.Strings(consumes)
110	assert.Equal(t, expected, consumes)
111
112	produces := analyzer.ProducesFor(spec.Paths.Paths["/"].Get)
113	sort.Strings(produces)
114	assert.Equal(t, expected, produces)
115
116	expected = []string{"application/json"}
117	sort.Strings(expected)
118	consumes = analyzer.ConsumesFor(spec.Paths.Paths["/items"].Get)
119	sort.Strings(consumes)
120	assert.Equal(t, expected, consumes)
121
122	produces = analyzer.ProducesFor(spec.Paths.Paths["/items"].Get)
123	sort.Strings(produces)
124	assert.Equal(t, expected, produces)
125
126	expectedSchemes := [][]SecurityRequirement{
127		{
128			{Name: "oauth2", Scopes: []string{}},
129			{Name: "basic", Scopes: nil},
130		},
131	}
132	schemes := analyzer.SecurityRequirementsFor(spec.Paths.Paths["/"].Get)
133	assert.Equal(t, schemeNames(expectedSchemes), schemeNames(schemes))
134
135	securityDefinitions := analyzer.SecurityDefinitionsFor(spec.Paths.Paths["/"].Get)
136	assert.Equal(t, *spec.SecurityDefinitions["basic"], securityDefinitions["basic"])
137	assert.Equal(t, *spec.SecurityDefinitions["oauth2"], securityDefinitions["oauth2"])
138
139	parameters := analyzer.ParamsFor("GET", "/")
140	assert.Len(t, parameters, 2)
141
142	operations := analyzer.OperationIDs()
143	assert.Len(t, operations, 2)
144
145	producers := analyzer.RequiredProduces()
146	assert.Len(t, producers, 2)
147	consumers := analyzer.RequiredConsumes()
148	assert.Len(t, consumers, 2)
149	authSchemes := analyzer.RequiredSecuritySchemes()
150	assert.Len(t, authSchemes, 3)
151
152	ops := analyzer.Operations()
153	assert.Len(t, ops, 1)
154	assert.Len(t, ops["GET"], 2)
155
156	op, ok := analyzer.OperationFor("get", "/")
157	assert.True(t, ok)
158	assert.NotNil(t, op)
159
160	op, ok = analyzer.OperationFor("delete", "/")
161	assert.False(t, ok)
162	assert.Nil(t, op)
163
164	// check for duplicates in sec. requirements for operation
165	pi.Get.Security = []map[string][]string{
166		{"oauth2": {}},
167		{"basic": nil},
168		{"basic": nil},
169	}
170	spec = makeFixturepec(pi, pi2, formatParam)
171	analyzer = New(spec)
172	securityDefinitions = analyzer.SecurityDefinitionsFor(spec.Paths.Paths["/"].Get)
173	assert.Len(t, securityDefinitions, 2)
174	assert.Equal(t, *spec.SecurityDefinitions["basic"], securityDefinitions["basic"])
175	assert.Equal(t, *spec.SecurityDefinitions["oauth2"], securityDefinitions["oauth2"])
176
177	// check for empty (optional) in sec. requirements for operation
178	pi.Get.Security = []map[string][]string{
179		{"oauth2": {}},
180		{"": nil},
181		{"basic": nil},
182	}
183	spec = makeFixturepec(pi, pi2, formatParam)
184	analyzer = New(spec)
185	securityDefinitions = analyzer.SecurityDefinitionsFor(spec.Paths.Paths["/"].Get)
186	assert.Len(t, securityDefinitions, 2)
187	assert.Equal(t, *spec.SecurityDefinitions["basic"], securityDefinitions["basic"])
188	assert.Equal(t, *spec.SecurityDefinitions["oauth2"], securityDefinitions["oauth2"])
189}
190
191func TestDefinitionAnalysis(t *testing.T) {
192	doc, err := loadSpec(filepath.Join("fixtures", "definitions.yml"))
193	if assert.NoError(t, err) {
194		analyzer := New(doc)
195		definitions := analyzer.allSchemas
196		// parameters
197		assertSchemaRefExists(t, definitions, "#/parameters/someParam/schema")
198		assertSchemaRefExists(t, definitions, "#/paths/~1some~1where~1{id}/parameters/1/schema")
199		assertSchemaRefExists(t, definitions, "#/paths/~1some~1where~1{id}/get/parameters/1/schema")
200		// responses
201		assertSchemaRefExists(t, definitions, "#/responses/someResponse/schema")
202		assertSchemaRefExists(t, definitions, "#/paths/~1some~1where~1{id}/get/responses/default/schema")
203		assertSchemaRefExists(t, definitions, "#/paths/~1some~1where~1{id}/get/responses/200/schema")
204		// definitions
205		assertSchemaRefExists(t, definitions, "#/definitions/tag")
206		assertSchemaRefExists(t, definitions, "#/definitions/tag/properties/id")
207		assertSchemaRefExists(t, definitions, "#/definitions/tag/properties/value")
208		assertSchemaRefExists(t, definitions, "#/definitions/tag/definitions/category")
209		assertSchemaRefExists(t, definitions, "#/definitions/tag/definitions/category/properties/id")
210		assertSchemaRefExists(t, definitions, "#/definitions/tag/definitions/category/properties/value")
211		assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalProps")
212		assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalProps/additionalProperties")
213		assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalItems")
214		assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalItems/items/0")
215		assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalItems/items/1")
216		assertSchemaRefExists(t, definitions, "#/definitions/withAdditionalItems/additionalItems")
217		assertSchemaRefExists(t, definitions, "#/definitions/withNot")
218		assertSchemaRefExists(t, definitions, "#/definitions/withNot/not")
219		assertSchemaRefExists(t, definitions, "#/definitions/withAnyOf")
220		assertSchemaRefExists(t, definitions, "#/definitions/withAnyOf/anyOf/0")
221		assertSchemaRefExists(t, definitions, "#/definitions/withAnyOf/anyOf/1")
222		assertSchemaRefExists(t, definitions, "#/definitions/withAllOf")
223		assertSchemaRefExists(t, definitions, "#/definitions/withAllOf/allOf/0")
224		assertSchemaRefExists(t, definitions, "#/definitions/withAllOf/allOf/1")
225		assertSchemaRefExists(t, definitions, "#/definitions/withOneOf/oneOf/0")
226		assertSchemaRefExists(t, definitions, "#/definitions/withOneOf/oneOf/1")
227		allOfs := analyzer.allOfs
228		assert.Len(t, allOfs, 1)
229		assert.Contains(t, allOfs, "#/definitions/withAllOf")
230	}
231}
232
233func loadSpec(path string) (*spec.Swagger, error) {
234	spec.PathLoader = func(path string) (json.RawMessage, error) {
235		ext := filepath.Ext(path)
236		if ext == ".yml" || ext == ".yaml" {
237			return swag.YAMLDoc(path)
238		}
239		data, err := swag.LoadFromFileOrHTTP(path)
240		if err != nil {
241			return nil, err
242		}
243		return json.RawMessage(data), nil
244	}
245	data, err := swag.YAMLDoc(path)
246	if err != nil {
247		return nil, err
248	}
249
250	var sw spec.Swagger
251	if err := json.Unmarshal(data, &sw); err != nil {
252		return nil, err
253	}
254	return &sw, nil
255}
256
257func TestReferenceAnalysis(t *testing.T) {
258	doc, err := loadSpec(filepath.Join("fixtures", "references.yml"))
259	if assert.NoError(t, err) {
260		an := New(doc)
261		definitions := an.references
262
263		// parameters
264		assertRefExists(t, definitions.parameters, "#/paths/~1some~1where~1{id}/parameters/0")
265		assertRefExists(t, definitions.parameters, "#/paths/~1some~1where~1{id}/get/parameters/0")
266
267		// path items
268		assertRefExists(t, definitions.pathItems, "#/paths/~1other~1place")
269
270		// responses
271		assertRefExists(t, definitions.responses, "#/paths/~1some~1where~1{id}/get/responses/404")
272
273		// definitions
274		assertRefExists(t, definitions.schemas, "#/responses/notFound/schema")
275		assertRefExists(t, definitions.schemas, "#/paths/~1some~1where~1{id}/get/responses/200/schema")
276		assertRefExists(t, definitions.schemas, "#/definitions/tag/properties/audit")
277
278		// items
279		// Supported non-swagger 2.0 constructs ($ref in simple schema items)
280		assertRefExists(t, definitions.allRefs, "#/paths/~1some~1where~1{id}/get/parameters/1/items")
281		assertRefExists(t, definitions.allRefs, "#/paths/~1some~1where~1{id}/get/parameters/2/items")
282		assertRefExists(t, definitions.allRefs,
283			"#/paths/~1some~1where~1{id}/get/responses/default/headers/x-array-header/items")
284
285		assert.Lenf(t, an.AllItemsReferences(), 3, "Expected 3 items references in this spec")
286
287		assertRefExists(t, definitions.parameterItems, "#/paths/~1some~1where~1{id}/get/parameters/1/items")
288		assertRefExists(t, definitions.parameterItems, "#/paths/~1some~1where~1{id}/get/parameters/2/items")
289		assertRefExists(t, definitions.headerItems,
290			"#/paths/~1some~1where~1{id}/get/responses/default/headers/x-array-header/items")
291	}
292}
293
294// nolint: unparam
295func assertRefExists(t testing.TB, data map[string]spec.Ref, key string) bool {
296	if _, ok := data[key]; !ok {
297		return assert.Fail(t, fmt.Sprintf("expected %q to exist in the ref bag", key))
298	}
299	return true
300}
301
302// nolint: unparam
303func assertSchemaRefExists(t testing.TB, data map[string]SchemaRef, key string) bool {
304	if _, ok := data[key]; !ok {
305		return assert.Fail(t, fmt.Sprintf("expected %q to exist in schema ref bag", key))
306	}
307	return true
308}
309
310func TestPatternAnalysis(t *testing.T) {
311	doc, err := loadSpec(filepath.Join("fixtures", "patterns.yml"))
312	if assert.NoError(t, err) {
313		an := New(doc)
314		pt := an.patterns
315
316		// parameters
317		assertPattern(t, pt.parameters, "#/parameters/idParam", "a[A-Za-Z0-9]+")
318		assertPattern(t, pt.parameters, "#/paths/~1some~1where~1{id}/parameters/1", "b[A-Za-z0-9]+")
319		assertPattern(t, pt.parameters, "#/paths/~1some~1where~1{id}/get/parameters/0", "[abc][0-9]+")
320
321		// responses
322		assertPattern(t, pt.headers, "#/responses/notFound/headers/ContentLength", "[0-9]+")
323		assertPattern(t, pt.headers,
324			"#/paths/~1some~1where~1{id}/get/responses/200/headers/X-Request-Id", "d[A-Za-z0-9]+")
325
326		// definitions
327		assertPattern(t, pt.schemas,
328			"#/paths/~1other~1place/post/parameters/0/schema/properties/value", "e[A-Za-z0-9]+")
329		assertPattern(t, pt.schemas, "#/paths/~1other~1place/post/responses/200/schema/properties/data", "[0-9]+[abd]")
330		assertPattern(t, pt.schemas, "#/definitions/named", "f[A-Za-z0-9]+")
331		assertPattern(t, pt.schemas, "#/definitions/tag/properties/value", "g[A-Za-z0-9]+")
332
333		// items
334		assertPattern(t, pt.items, "#/paths/~1some~1where~1{id}/get/parameters/1/items", "c[A-Za-z0-9]+")
335		assertPattern(t, pt.items, "#/paths/~1other~1place/post/responses/default/headers/Via/items", "[A-Za-z]+")
336
337		// patternProperties (beyond Swagger 2.0)
338		_, ok := an.spec.Definitions["withPatternProperties"]
339		assert.True(t, ok)
340		_, ok = an.allSchemas["#/definitions/withPatternProperties/patternProperties/^prop[0-9]+$"]
341		assert.True(t, ok)
342	}
343}
344
345// nolint: unparam
346func assertPattern(t testing.TB, data map[string]string, key, pattern string) bool {
347	if assert.Contains(t, data, key) {
348		return assert.Equal(t, pattern, data[key])
349	}
350	return false
351}
352
353func panickerParamsAsMap() {
354	s := prepareTestParamsInvalid("fixture-342.yaml")
355	if s == nil {
356		return
357	}
358	m := make(map[string]spec.Parameter)
359	if pi, ok := s.spec.Paths.Paths["/fixture"]; ok {
360		pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
361		s.paramsAsMap(pi.Parameters, m, nil)
362	}
363}
364
365func panickerParamsAsMap2() {
366	s := prepareTestParamsInvalid("fixture-342-2.yaml")
367	if s == nil {
368		return
369	}
370	m := make(map[string]spec.Parameter)
371	if pi, ok := s.spec.Paths.Paths["/fixture"]; ok {
372		pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
373		s.paramsAsMap(pi.Parameters, m, nil)
374	}
375}
376
377func panickerParamsAsMap3() {
378	s := prepareTestParamsInvalid("fixture-342-3.yaml")
379	if s == nil {
380		return
381	}
382	m := make(map[string]spec.Parameter)
383	if pi, ok := s.spec.Paths.Paths["/fixture"]; ok {
384		pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
385		s.paramsAsMap(pi.Parameters, m, nil)
386	}
387}
388
389func TestAnalyzer_paramsAsMap(pt *testing.T) {
390	s := prepareTestParamsValid()
391	if assert.NotNil(pt, s) {
392		m := make(map[string]spec.Parameter)
393		pi, ok := s.spec.Paths.Paths["/items"]
394		if assert.True(pt, ok) {
395			s.paramsAsMap(pi.Parameters, m, nil)
396			assert.Len(pt, m, 1)
397			p, ok := m["query#Limit"]
398			assert.True(pt, ok)
399			assert.Equal(pt, p.Name, "limit")
400		}
401	}
402
403	// An invalid spec, but passes this step (errors are figured out at a higher level)
404	s = prepareTestParamsInvalid("fixture-1289-param.yaml")
405	if assert.NotNil(pt, s) {
406		m := make(map[string]spec.Parameter)
407		pi, ok := s.spec.Paths.Paths["/fixture"]
408		if assert.True(pt, ok) {
409			pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
410			s.paramsAsMap(pi.Parameters, m, nil)
411			assert.Len(pt, m, 1)
412			p, ok := m["body#DespicableMe"]
413			assert.True(pt, ok)
414			assert.Equal(pt, p.Name, "despicableMe")
415		}
416	}
417}
418
419func TestAnalyzer_paramsAsMapWithCallback(pt *testing.T) {
420	s := prepareTestParamsInvalid("fixture-342.yaml")
421	if assert.NotNil(pt, s) {
422		// No bail out callback
423		m := make(map[string]spec.Parameter)
424		e := []string{}
425		pi, ok := s.spec.Paths.Paths["/fixture"]
426		if assert.True(pt, ok) {
427			pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
428			s.paramsAsMap(pi.Parameters, m, func(param spec.Parameter, err error) bool {
429				// pt.Logf("ERROR on %+v : %v", param, err)
430				e = append(e, err.Error())
431				return true // Continue
432			})
433		}
434		assert.Contains(pt, e, `resolved reference is not a parameter: "#/definitions/sample_info/properties/sid"`)
435		assert.Contains(pt, e, `invalid reference: "#/definitions/sample_info/properties/sids"`)
436
437		// bail out callback
438		m = make(map[string]spec.Parameter)
439		e = []string{}
440		pi, ok = s.spec.Paths.Paths["/fixture"]
441		if assert.True(pt, ok) {
442			pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
443			s.paramsAsMap(pi.Parameters, m, func(param spec.Parameter, err error) bool {
444				// pt.Logf("ERROR on %+v : %v", param, err)
445				e = append(e, err.Error())
446				return false // Bail out
447			})
448		}
449		// We got one then bail out
450		assert.Len(pt, e, 1)
451	}
452
453	// Bail out after ref failure: exercising another path
454	s = prepareTestParamsInvalid("fixture-342-2.yaml")
455	if assert.NotNil(pt, s) {
456		// bail out callback
457		m := make(map[string]spec.Parameter)
458		e := []string{}
459		pi, ok := s.spec.Paths.Paths["/fixture"]
460		if assert.True(pt, ok) {
461			pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
462			s.paramsAsMap(pi.Parameters, m, func(param spec.Parameter, err error) bool {
463				// pt.Logf("ERROR on %+v : %v", param, err)
464				e = append(e, err.Error())
465				return false // Bail out
466			})
467		}
468		// We got one then bail out
469		assert.Len(pt, e, 1)
470	}
471
472	// Bail out after ref failure: exercising another path
473	s = prepareTestParamsInvalid("fixture-342-3.yaml")
474	if assert.NotNil(pt, s) {
475		// bail out callback
476		m := make(map[string]spec.Parameter)
477		e := []string{}
478		pi, ok := s.spec.Paths.Paths["/fixture"]
479		if assert.True(pt, ok) {
480			pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
481			s.paramsAsMap(pi.Parameters, m, func(param spec.Parameter, err error) bool {
482				// pt.Logf("ERROR on %+v : %v", param, err)
483				e = append(e, err.Error())
484				return false // Bail out
485			})
486		}
487		// We got one then bail out
488		assert.Len(pt, e, 1)
489	}
490}
491
492func TestAnalyzer_paramsAsMap_Panic(pt *testing.T) {
493	assert.Panics(pt, panickerParamsAsMap)
494
495	// Specifically on invalid resolved type
496	assert.Panics(pt, panickerParamsAsMap2)
497
498	// Specifically on invalid ref
499	assert.Panics(pt, panickerParamsAsMap3)
500}
501
502func TestAnalyzer_SafeParamsFor(pt *testing.T) {
503	s := prepareTestParamsInvalid("fixture-342.yaml")
504	if assert.NotNil(pt, s) {
505		e := []string{}
506		pi, ok := s.spec.Paths.Paths["/fixture"]
507		if assert.True(pt, ok) {
508			pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
509			for range s.SafeParamsFor("Get", "/fixture", func(param spec.Parameter, err error) bool {
510				e = append(e, err.Error())
511				return true // Continue
512			}) {
513				assert.Fail(pt, "There should be no safe parameter in this testcase")
514			}
515		}
516		assert.Contains(pt, e, `resolved reference is not a parameter: "#/definitions/sample_info/properties/sid"`)
517		assert.Contains(pt, e, `invalid reference: "#/definitions/sample_info/properties/sids"`)
518
519	}
520}
521
522func panickerParamsFor() {
523	s := prepareTestParamsInvalid("fixture-342.yaml")
524	pi, ok := s.spec.Paths.Paths["/fixture"]
525	if ok {
526		pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
527		s.ParamsFor("Get", "/fixture")
528	}
529}
530
531func TestAnalyzer_ParamsFor(pt *testing.T) {
532	// Valid example
533	s := prepareTestParamsValid()
534	if assert.NotNil(pt, s) {
535
536		params := s.ParamsFor("Get", "/items")
537		assert.True(pt, len(params) > 0)
538	}
539
540	// Invalid example
541	assert.Panics(pt, panickerParamsFor)
542}
543
544func TestAnalyzer_SafeParametersFor(pt *testing.T) {
545	s := prepareTestParamsInvalid("fixture-342.yaml")
546	if assert.NotNil(pt, s) {
547		e := []string{}
548		pi, ok := s.spec.Paths.Paths["/fixture"]
549		if assert.True(pt, ok) {
550			pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
551			for range s.SafeParametersFor("fixtureOp", func(param spec.Parameter, err error) bool {
552				e = append(e, err.Error())
553				return true // Continue
554			}) {
555				assert.Fail(pt, "There should be no safe parameter in this testcase")
556			}
557		}
558		assert.Contains(pt, e, `resolved reference is not a parameter: "#/definitions/sample_info/properties/sid"`)
559		assert.Contains(pt, e, `invalid reference: "#/definitions/sample_info/properties/sids"`)
560	}
561}
562
563func panickerParametersFor() {
564	s := prepareTestParamsInvalid("fixture-342.yaml")
565	if s == nil {
566		return
567	}
568	pi, ok := s.spec.Paths.Paths["/fixture"]
569	if ok {
570		pi.Parameters = pi.PathItemProps.Get.OperationProps.Parameters
571		// func (s *Spec) ParametersFor(operationID string) []spec.Parameter {
572		s.ParametersFor("fixtureOp")
573	}
574}
575
576func TestAnalyzer_ParametersFor(pt *testing.T) {
577	// Valid example
578	s := prepareTestParamsValid()
579	params := s.ParamsFor("Get", "/items")
580	assert.True(pt, len(params) > 0)
581
582	// Invalid example
583	assert.Panics(pt, panickerParametersFor)
584}
585
586func prepareTestParamsValid() *Spec {
587	formatParam := spec.QueryParam("format").Typed("string", "")
588
589	limitParam := spec.QueryParam("limit").Typed("integer", "int32")
590	limitParam.Extensions = spec.Extensions(map[string]interface{}{})
591	limitParam.Extensions.Add("go-name", "Limit")
592
593	skipParam := spec.QueryParam("skip").Typed("integer", "int32")
594	pi := spec.PathItem{}
595	pi.Parameters = []spec.Parameter{*limitParam}
596
597	op := &spec.Operation{}
598	op.Consumes = []string{"application/x-yaml"}
599	op.Produces = []string{"application/x-yaml"}
600	op.Security = []map[string][]string{
601		{"oauth2": {}},
602		{"basic": nil},
603	}
604	op.ID = "someOperation"
605	op.Parameters = []spec.Parameter{*skipParam}
606	pi.Get = op
607
608	pi2 := spec.PathItem{}
609	pi2.Parameters = []spec.Parameter{*limitParam}
610	op2 := &spec.Operation{}
611	op2.ID = "anotherOperation"
612	op2.Parameters = []spec.Parameter{*skipParam}
613	pi2.Get = op2
614
615	spec := makeFixturepec(pi, pi2, formatParam)
616	analyzer := New(spec)
617	return analyzer
618}
619
620func prepareTestParamsInvalid(fixture string) *Spec {
621	cwd, _ := os.Getwd()
622	bp := filepath.Join(cwd, "fixtures", fixture)
623	spec, err := loadSpec(bp)
624	if err != nil {
625		log.Printf("Warning: fixture %s could not be loaded: %v", fixture, err)
626		return nil
627	}
628	analyzer := New(spec)
629	return analyzer
630}
631
632func TestSecurityDefinitionsFor(t *testing.T) {
633	spec := prepareTestParamsAuth()
634	pi1 := spec.spec.Paths.Paths["/"].Get
635	pi2 := spec.spec.Paths.Paths["/items"].Get
636
637	defs1 := spec.SecurityDefinitionsFor(pi1)
638	require.Contains(t, defs1, "oauth2")
639	require.Contains(t, defs1, "basic")
640	require.NotContains(t, defs1, "apiKey")
641
642	defs2 := spec.SecurityDefinitionsFor(pi2)
643	require.Contains(t, defs2, "oauth2")
644	require.Contains(t, defs2, "basic")
645	require.Contains(t, defs2, "apiKey")
646}
647
648func TestSecurityRequirements(t *testing.T) {
649	spec := prepareTestParamsAuth()
650	pi1 := spec.spec.Paths.Paths["/"].Get
651	pi2 := spec.spec.Paths.Paths["/items"].Get
652	scopes := []string{"the-scope"}
653
654	reqs1 := spec.SecurityRequirementsFor(pi1)
655	require.Len(t, reqs1, 2)
656	require.Len(t, reqs1[0], 1)
657	require.Equal(t, reqs1[0][0].Name, "oauth2")
658	require.Equal(t, reqs1[0][0].Scopes, scopes)
659	require.Len(t, reqs1[1], 1)
660	require.Equal(t, reqs1[1][0].Name, "basic")
661	require.Empty(t, reqs1[1][0].Scopes)
662
663	reqs2 := spec.SecurityRequirementsFor(pi2)
664	require.Len(t, reqs2, 3)
665	require.Len(t, reqs2[0], 1)
666	require.Equal(t, reqs2[0][0].Name, "oauth2")
667	require.Equal(t, reqs2[0][0].Scopes, scopes)
668	require.Len(t, reqs2[1], 1)
669	require.Empty(t, reqs2[1][0].Name)
670	require.Empty(t, reqs2[1][0].Scopes)
671	require.Len(t, reqs2[2], 2)
672	//
673	// require.Equal(t, reqs2[2][0].Name, "basic")
674	require.Contains(t, reqs2[2], SecurityRequirement{Name: "basic", Scopes: []string{}})
675	require.Empty(t, reqs2[2][0].Scopes)
676	// require.Equal(t, reqs2[2][1].Name, "apiKey")
677	require.Contains(t, reqs2[2], SecurityRequirement{Name: "apiKey", Scopes: []string{}})
678	require.Empty(t, reqs2[2][1].Scopes)
679}
680
681func TestSecurityRequirementsDefinitions(t *testing.T) {
682	spec := prepareTestParamsAuth()
683	pi1 := spec.spec.Paths.Paths["/"].Get
684	pi2 := spec.spec.Paths.Paths["/items"].Get
685
686	reqs1 := spec.SecurityRequirementsFor(pi1)
687	defs11 := spec.SecurityDefinitionsForRequirements(reqs1[0])
688	require.Contains(t, defs11, "oauth2")
689	defs12 := spec.SecurityDefinitionsForRequirements(reqs1[1])
690	require.Contains(t, defs12, "basic")
691	require.NotContains(t, defs12, "apiKey")
692
693	reqs2 := spec.SecurityRequirementsFor(pi2)
694	defs21 := spec.SecurityDefinitionsForRequirements(reqs2[0])
695	require.Len(t, defs21, 1)
696	require.Contains(t, defs21, "oauth2")
697	require.NotContains(t, defs21, "basic")
698	require.NotContains(t, defs21, "apiKey")
699	defs22 := spec.SecurityDefinitionsForRequirements(reqs2[1])
700	require.NotNil(t, defs22)
701	require.Empty(t, defs22)
702	defs23 := spec.SecurityDefinitionsForRequirements(reqs2[2])
703	require.Len(t, defs23, 2)
704	require.NotContains(t, defs23, "oauth2")
705	require.Contains(t, defs23, "basic")
706	require.Contains(t, defs23, "apiKey")
707
708}
709
710func prepareTestParamsAuth() *Spec {
711	formatParam := spec.QueryParam("format").Typed("string", "")
712
713	limitParam := spec.QueryParam("limit").Typed("integer", "int32")
714	limitParam.Extensions = spec.Extensions(map[string]interface{}{})
715	limitParam.Extensions.Add("go-name", "Limit")
716
717	skipParam := spec.QueryParam("skip").Typed("integer", "int32")
718	pi := spec.PathItem{}
719	pi.Parameters = []spec.Parameter{*limitParam}
720
721	op := &spec.Operation{}
722	op.Consumes = []string{"application/x-yaml"}
723	op.Produces = []string{"application/x-yaml"}
724	op.Security = []map[string][]string{
725		{"oauth2": {"the-scope"}},
726		{"basic": nil},
727	}
728	op.ID = "someOperation"
729	op.Parameters = []spec.Parameter{*skipParam}
730	pi.Get = op
731
732	pi2 := spec.PathItem{}
733	pi2.Parameters = []spec.Parameter{*limitParam}
734	op2 := &spec.Operation{}
735	op2.ID = "anotherOperation"
736	op2.Security = []map[string][]string{
737		{"oauth2": {"the-scope"}},
738		{},
739		{
740			"basic":  {},
741			"apiKey": {},
742		},
743	}
744	op2.Parameters = []spec.Parameter{*skipParam}
745	pi2.Get = op2
746
747	oauth := spec.OAuth2AccessToken("http://authorize.com", "http://token.com")
748	oauth.AddScope("the-scope", "the scope gives access to ...")
749	spec := &spec.Swagger{
750		SwaggerProps: spec.SwaggerProps{
751			Consumes: []string{"application/json"},
752			Produces: []string{"application/json"},
753			Security: []map[string][]string{
754				{"apikey": nil},
755			},
756			SecurityDefinitions: map[string]*spec.SecurityScheme{
757				"basic":  spec.BasicAuth(),
758				"apiKey": spec.APIKeyAuth("api_key", "query"),
759				"oauth2": oauth,
760			},
761			Parameters: map[string]spec.Parameter{"format": *formatParam},
762			Paths: &spec.Paths{
763				Paths: map[string]spec.PathItem{
764					"/":      pi,
765					"/items": pi2,
766				},
767			},
768		},
769	}
770	analyzer := New(spec)
771	return analyzer
772}
773
774func TestMoreParamAnalysis(t *testing.T) {
775	cwd, _ := os.Getwd()
776	bp := filepath.Join(cwd, "fixtures", "parameters", "fixture-parameters.yaml")
777	sp, err := loadSpec(bp)
778	if !assert.NoError(t, err) {
779		t.FailNow()
780		return
781	}
782
783	an := New(sp)
784
785	res := an.AllPatterns()
786	assert.Lenf(t, res, 6, "Expected 6 patterns in this spec")
787
788	res = an.SchemaPatterns()
789	assert.Lenf(t, res, 1, "Expected 1 schema pattern in this spec")
790
791	res = an.HeaderPatterns()
792	assert.Lenf(t, res, 2, "Expected 2 header pattern in this spec")
793
794	res = an.ItemsPatterns()
795	assert.Lenf(t, res, 2, "Expected 2 items pattern in this spec")
796
797	res = an.ParameterPatterns()
798	assert.Lenf(t, res, 1, "Expected 1 simple param pattern in this spec")
799
800	refs := an.AllRefs()
801	assert.Lenf(t, refs, 10, "Expected 10 reference usage in this spec")
802
803	references := an.AllReferences()
804	assert.Lenf(t, references, 14, "Expected 14 reference usage in this spec")
805
806	references = an.AllItemsReferences()
807	assert.Lenf(t, references, 0, "Expected 0 items reference in this spec")
808
809	references = an.AllPathItemReferences()
810	assert.Lenf(t, references, 1, "Expected 1 pathItem reference in this spec")
811
812	references = an.AllResponseReferences()
813	assert.Lenf(t, references, 3, "Expected 3 response references in this spec")
814
815	references = an.AllParameterReferences()
816	assert.Lenf(t, references, 6, "Expected 6 parameter references in this spec")
817
818	schemaRefs := an.AllDefinitions()
819	assert.Lenf(t, schemaRefs, 14, "Expected 14 schema definitions in this spec")
820	// for _, refs := range schemaRefs {
821	//	t.Logf("Schema Ref: %s (%s)", refs.Name, refs.Ref.String())
822	// }
823	schemaRefs = an.SchemasWithAllOf()
824	assert.Lenf(t, schemaRefs, 1, "Expected 1 schema with AllOf definition in this spec")
825
826	method, path, op, found := an.OperationForName("postSomeWhere")
827	assert.Equal(t, "POST", method)
828	assert.Equal(t, "/some/where", path)
829	if assert.NotNil(t, op) && assert.True(t, found) {
830		sec := an.SecurityRequirementsFor(op)
831		assert.Nil(t, sec)
832		secScheme := an.SecurityDefinitionsFor(op)
833		assert.Nil(t, secScheme)
834
835		bag := an.ParametersFor("postSomeWhere")
836		assert.Lenf(t, bag, 6, "Expected 6 parameters for this operation")
837	}
838
839	method, path, op, found = an.OperationForName("notFound")
840	assert.Equal(t, "", method)
841	assert.Equal(t, "", path)
842	assert.Nil(t, op)
843	assert.False(t, found)
844
845	// does not take ops under pathItem $ref
846	ops := an.OperationMethodPaths()
847	assert.Lenf(t, ops, 3, "Expected 3 ops")
848	ops = an.OperationIDs()
849	assert.Lenf(t, ops, 3, "Expected 3 ops")
850	assert.Contains(t, ops, "postSomeWhere")
851	assert.Contains(t, ops, "GET /some/where/else")
852	assert.Contains(t, ops, "GET /some/where")
853}
854
855func Test_EdgeCases(t *testing.T) {
856	// check return values are consistent in some nil/empty edge cases
857	sp := Spec{}
858	res1 := sp.AllPaths()
859	assert.Nil(t, res1)
860
861	res2 := sp.OperationIDs()
862	assert.Nil(t, res2)
863
864	res3 := sp.OperationMethodPaths()
865	assert.Nil(t, res3)
866
867	res4 := sp.structMapKeys(nil)
868	assert.Nil(t, res4)
869
870	res5 := sp.structMapKeys(make(map[string]struct{}, 10))
871	assert.Nil(t, res5)
872
873	// check AllRefs() skips empty $refs
874	sp.references.allRefs = make(map[string]spec.Ref, 3)
875	for i := 0; i < 3; i++ {
876		sp.references.allRefs["ref"+strconv.Itoa(i)] = spec.Ref{}
877	}
878	assert.Len(t, sp.references.allRefs, 3)
879	res6 := sp.AllRefs()
880	assert.Len(t, res6, 0)
881
882	// check AllRefs() skips duplicate $refs
883	sp.references.allRefs["refToOne"] = spec.MustCreateRef("#/ref1")
884	sp.references.allRefs["refToOneAgain"] = spec.MustCreateRef("#/ref1")
885	res7 := sp.AllRefs()
886	assert.NotNil(t, res7)
887	assert.Len(t, res7, 1)
888}
889
890func TestEnumAnalysis(t *testing.T) {
891	doc, err := loadSpec(filepath.Join("fixtures", "enums.yml"))
892	if assert.NoError(t, err) {
893		an := New(doc)
894		en := an.enums
895
896		// parameters
897		assertEnum(t, en.parameters, "#/parameters/idParam", []interface{}{"aA", "b9", "c3"})
898		assertEnum(t, en.parameters, "#/paths/~1some~1where~1{id}/parameters/1", []interface{}{"bA", "ba", "b9"})
899		assertEnum(t, en.parameters, "#/paths/~1some~1where~1{id}/get/parameters/0", []interface{}{"a0", "b1", "c2"})
900
901		// responses
902		assertEnum(t, en.headers, "#/responses/notFound/headers/ContentLength", []interface{}{"1234", "123"})
903		assertEnum(t, en.headers,
904			"#/paths/~1some~1where~1{id}/get/responses/200/headers/X-Request-Id", []interface{}{"dA", "d9"})
905
906		// definitions
907		assertEnum(t, en.schemas,
908			"#/paths/~1other~1place/post/parameters/0/schema/properties/value", []interface{}{"eA", "e9"})
909		assertEnum(t, en.schemas, "#/paths/~1other~1place/post/responses/200/schema/properties/data",
910			[]interface{}{"123a", "123b", "123d"})
911		assertEnum(t, en.schemas, "#/definitions/named", []interface{}{"fA", "f9"})
912		assertEnum(t, en.schemas, "#/definitions/tag/properties/value", []interface{}{"gA", "ga", "g9"})
913		assertEnum(t, en.schemas, "#/definitions/record",
914			[]interface{}{`{"createdAt": "2018-08-31"}`, `{"createdAt": "2018-09-30"}`})
915
916		// array enum
917		assertEnum(t, en.parameters, "#/paths/~1some~1where~1{id}/get/parameters/1",
918			[]interface{}{[]interface{}{"cA", "cz", "c9"}, []interface{}{"cA", "cz"}, []interface{}{"cz", "c9"}})
919
920		// items
921		assertEnum(t, en.items, "#/paths/~1some~1where~1{id}/get/parameters/1/items", []interface{}{"cA", "cz", "c9"})
922		assertEnum(t, en.items, "#/paths/~1other~1place/post/responses/default/headers/Via/items",
923			[]interface{}{"AA", "Ab"})
924
925		res := an.AllEnums()
926		assert.Lenf(t, res, 14, "Expected 14 enums in this spec, but got %d", len(res))
927
928		res = an.ParameterEnums()
929		assert.Lenf(t, res, 4, "Expected 4 enums in this spec, but got %d", len(res))
930
931		res = an.SchemaEnums()
932		assert.Lenf(t, res, 6, "Expected 6 schema enums in this spec, but got %d", len(res))
933
934		res = an.HeaderEnums()
935		assert.Lenf(t, res, 2, "Expected 2 header enums in this spec, but got %d", len(res))
936
937		res = an.ItemsEnums()
938		assert.Lenf(t, res, 2, "Expected 2 items enums in this spec, but got %d", len(res))
939	}
940}
941
942// nolint: unparam
943func assertEnum(t testing.TB, data map[string][]interface{}, key string, enum []interface{}) bool {
944	if assert.Contains(t, data, key) {
945		return assert.Equal(t, enum, data[key])
946	}
947	return false
948}
949