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