github.com/emreu/go-swagger@v0.22.1/scan/scanner_test.go (about) 1 // +build !go1.11 2 3 // Copyright 2015 go-swagger maintainers 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package scan 18 19 import ( 20 "fmt" 21 "go/ast" 22 goparser "go/parser" 23 "io/ioutil" 24 "log" 25 "os" 26 "regexp" 27 "sort" 28 "strings" 29 "testing" 30 31 "github.com/go-openapi/loads" 32 "github.com/go-openapi/spec" 33 "github.com/go-openapi/strfmt" 34 "github.com/go-openapi/validate" 35 "github.com/stretchr/testify/assert" 36 "golang.org/x/tools/go/loader" 37 38 _ "github.com/go-swagger/scan-repo-boundary/makeplans" 39 ) 40 41 var classificationProg *loader.Program 42 var noModelDefs map[string]spec.Schema 43 44 type namedParam struct { 45 Index int 46 Name string 47 } 48 49 type namedParams []namedParam 50 51 func (g namedParams) Len() int { return len(g) } 52 func (g namedParams) Swap(i, j int) { g[i], g[j] = g[j], g[i] } 53 func (g namedParams) Less(i, j int) bool { return g[i].Name < g[j].Name } 54 55 func init() { 56 classificationProg = classifierProgram() 57 docFile := "../fixtures/goparsing/classification/models/nomodel.go" 58 fileTree, err := goparser.ParseFile(classificationProg.Fset, docFile, nil, goparser.ParseComments) 59 if err != nil { 60 log.Fatal(err) 61 } 62 sp := newSchemaParser(classificationProg) 63 noModelDefs = make(map[string]spec.Schema) 64 err = sp.Parse(fileTree, noModelDefs) 65 if err != nil { 66 log.Fatal(err) 67 } 68 } 69 70 // only used within this group of tests but never used within actual code base. 71 func newSchemaAnnotationParser(goName string) *schemaAnnotationParser { 72 return &schemaAnnotationParser{GoName: goName, rx: rxModelOverride} 73 } 74 75 type schemaAnnotationParser struct { 76 GoName string 77 Name string 78 rx *regexp.Regexp 79 } 80 81 func (sap *schemaAnnotationParser) Matches(line string) bool { 82 return sap.rx.MatchString(line) 83 } 84 85 func (sap *schemaAnnotationParser) Parse(lines []string) error { 86 if sap.Name != "" { 87 return nil 88 } 89 90 if len(lines) > 0 { 91 for _, line := range lines { 92 matches := sap.rx.FindStringSubmatch(line) 93 if len(matches) > 1 && len(matches[1]) > 0 { 94 sap.Name = matches[1] 95 return nil 96 } 97 } 98 } 99 return nil 100 } 101 102 func extraModelsClassifier(t testing.TB) (*loader.Program, map[string]spec.Schema) { 103 prog := classifierProgram() 104 docFile := "../fixtures/goparsing/classification/models/extranomodel.go" 105 fileTree, err := goparser.ParseFile(prog.Fset, docFile, nil, goparser.ParseComments) 106 if err != nil { 107 t.Fatal(err) 108 } 109 sp := newSchemaParser(prog) 110 defs := make(map[string]spec.Schema) 111 err = sp.Parse(fileTree, defs) 112 if err != nil { 113 t.Fatal(err) 114 } 115 return prog, defs 116 } 117 118 func TestAppScanner_NewSpec(t *testing.T) { 119 scanner, err := newAppScanner(&Opts{BasePath: "../fixtures/goparsing/petstore/petstore-fixture"}) 120 assert.NoError(t, err) 121 assert.NotNil(t, scanner) 122 doc, err := scanner.Parse() 123 assert.NoError(t, err) 124 if assert.NotNil(t, doc) { 125 verifyParsedPetStore(t, doc) 126 } 127 } 128 129 func TestAppScanner_Definitions(t *testing.T) { 130 scanner, err := newAppScanner(&Opts{BasePath: "../fixtures/goparsing/bookings"}) 131 assert.NoError(t, err) 132 assert.NotNil(t, scanner) 133 doc, err := scanner.Parse() 134 assert.NoError(t, err) 135 if assert.NotNil(t, doc) { 136 _, ok := doc.Definitions["Booking"] 137 assert.True(t, ok, "Should include cross repo structs") 138 _, ok = doc.Definitions["Customer"] 139 assert.True(t, ok, "Should include package structs with swagger:model") 140 _, ok = doc.Definitions["DateRange"] 141 assert.True(t, ok, "Should include package structs that are used in responses") 142 _, ok = doc.Definitions["BookingResponse"] 143 assert.False(t, ok, "Should not include responses") 144 _, ok = doc.Definitions["IgnoreMe"] 145 assert.False(t, ok, "Should not include un-annotated/un-referenced structs") 146 } 147 } 148 149 func verifyParsedPetStore(t testing.TB, doc *spec.Swagger) { 150 assert.EqualValues(t, []string{"application/json"}, doc.Consumes) 151 assert.EqualValues(t, []string{"application/json"}, doc.Produces) 152 assert.EqualValues(t, []string{"http", "https"}, doc.Schemes) 153 assert.Equal(t, "localhost", doc.Host) 154 assert.Equal(t, "/v2", doc.BasePath) 155 156 verifyInfo(t, doc.Info) 157 158 if assert.NotNil(t, doc.Paths) { 159 assert.Len(t, doc.Paths.Paths, 4) 160 } 161 var keys []string 162 for k := range doc.Definitions { 163 keys = append(keys, k) 164 } 165 assert.Len(t, keys, 3) 166 assert.Len(t, doc.Responses, 3) 167 168 definitions := doc.Definitions 169 mod, ok := definitions["tag"] 170 assert.True(t, ok) 171 assert.Equal(t, spec.StringOrArray([]string{"object"}), mod.Type) 172 assert.Equal(t, "A Tag is an extra piece of data to provide more information about a pet.", mod.Title) 173 assert.Equal(t, "It is used to describe the animals available in the store.", mod.Description) 174 assert.Len(t, mod.Required, 2) 175 176 assertProperty(t, &mod, "integer", "id", "int64", "ID") 177 prop, ok := mod.Properties["id"] 178 assert.True(t, ok, "should have had an 'id' property") 179 assert.Equal(t, "The id of the tag.", prop.Description) 180 181 assertProperty(t, &mod, "string", "value", "", "Value") 182 prop, ok = mod.Properties["value"] 183 assert.True(t, ok) 184 assert.Equal(t, "The value of the tag.", prop.Description) 185 186 mod, ok = definitions["pet"] 187 assert.True(t, ok) 188 assert.Equal(t, spec.StringOrArray([]string{"object"}), mod.Type) 189 assert.Equal(t, "A Pet is the main product in the store.", mod.Title) 190 assert.Equal(t, "It is used to describe the animals available in the store.", mod.Description) 191 assert.Len(t, mod.Required, 2) 192 193 assertProperty(t, &mod, "integer", "id", "int64", "ID") 194 prop, ok = mod.Properties["id"] 195 assert.True(t, ok, "should have had an 'id' property") 196 assert.Equal(t, "The id of the pet.", prop.Description) 197 198 assertProperty(t, &mod, "string", "name", "", "Name") 199 prop, ok = mod.Properties["name"] 200 assert.True(t, ok) 201 assert.Equal(t, "The name of the pet.", prop.Description) 202 assert.EqualValues(t, 3, *prop.MinLength) 203 assert.EqualValues(t, 50, *prop.MaxLength) 204 assert.Equal(t, "\\w[\\w-]+", prop.Pattern) 205 206 assertArrayProperty(t, &mod, "string", "photoUrls", "", "PhotoURLs") 207 prop, ok = mod.Properties["photoUrls"] 208 assert.True(t, ok) 209 assert.Equal(t, "The photo urls for the pet.\nThis only accepts jpeg or png images.", prop.Description) 210 if assert.NotNil(t, prop.Items) && assert.NotNil(t, prop.Items.Schema) { 211 assert.Equal(t, "\\.(jpe?g|png)$", prop.Items.Schema.Pattern) 212 } 213 214 assertProperty(t, &mod, "string", "status", "", "Status") 215 prop, ok = mod.Properties["status"] 216 assert.True(t, ok) 217 assert.Equal(t, "The current status of the pet in the store.", prop.Description) 218 219 assertProperty(t, &mod, "string", "birthday", "date", "Birthday") 220 prop, ok = mod.Properties["birthday"] 221 assert.True(t, ok) 222 assert.Equal(t, "The pet's birthday", prop.Description) 223 224 assertArrayRef(t, &mod, "tags", "Tags", "#/definitions/tag") 225 prop, ok = mod.Properties["tags"] 226 assert.True(t, ok) 227 assert.Equal(t, "Extra bits of information attached to this pet.", prop.Description) 228 229 mod, ok = definitions["order"] 230 assert.True(t, ok) 231 assert.Len(t, mod.Properties, 4) 232 assert.Len(t, mod.Required, 3) 233 234 assertProperty(t, &mod, "integer", "id", "int64", "ID") 235 prop, ok = mod.Properties["id"] 236 assert.True(t, ok, "should have had an 'id' property") 237 assert.Equal(t, "the ID of the order", prop.Description) 238 239 assertProperty(t, &mod, "integer", "userId", "int64", "UserID") 240 prop, ok = mod.Properties["userId"] 241 assert.True(t, ok, "should have had an 'userId' property") 242 assert.Equal(t, "the id of the user who placed the order.", prop.Description) 243 244 assertProperty(t, &mod, "string", "orderedAt", "date-time", "OrderedAt") 245 prop, ok = mod.Properties["orderedAt"] 246 assert.Equal(t, "the time at which this order was made.", prop.Description) 247 assert.True(t, ok, "should have an 'orderedAt' property") 248 249 assertArrayProperty(t, &mod, "object", "items", "", "Items") 250 prop, ok = mod.Properties["items"] 251 assert.True(t, ok, "should have an 'items' slice") 252 assert.NotNil(t, prop.Items, "items should have had an items property") 253 assert.NotNil(t, prop.Items.Schema, "items.items should have had a schema property") 254 255 itprop := prop.Items.Schema 256 assert.Len(t, itprop.Properties, 2) 257 assert.Len(t, itprop.Required, 2) 258 259 assertProperty(t, itprop, "integer", "petId", "int64", "PetID") 260 iprop, ok := itprop.Properties["petId"] 261 assert.True(t, ok, "should have had a 'petId' property") 262 assert.Equal(t, "the id of the pet to order", iprop.Description) 263 264 assertProperty(t, itprop, "integer", "qty", "int32", "Quantity") 265 iprop, ok = itprop.Properties["qty"] 266 assert.True(t, ok, "should have had a 'qty' property") 267 assert.Equal(t, "the quantity of this pet to order", iprop.Description) 268 assert.EqualValues(t, 1, *iprop.Minimum) 269 270 // responses 271 resp, ok := doc.Responses["genericError"] 272 assert.True(t, ok) 273 assert.NotNil(t, resp.Schema) 274 assert.Len(t, resp.Schema.Properties, 2) 275 assertProperty(t, resp.Schema, "integer", "code", "int32", "Code") 276 assertProperty(t, resp.Schema, "string", "message", "", "Message") 277 278 resp, ok = doc.Responses["validationError"] 279 assert.True(t, ok) 280 assert.NotNil(t, resp.Schema) 281 assert.Len(t, resp.Schema.Properties, 3) 282 assertProperty(t, resp.Schema, "integer", "code", "int32", "Code") 283 assertProperty(t, resp.Schema, "string", "message", "", "Message") 284 assertProperty(t, resp.Schema, "string", "field", "", "Field") 285 286 paths := doc.Paths.Paths 287 288 // path /pets 289 op, ok := paths["/pets"] 290 assert.True(t, ok) 291 assert.NotNil(t, op) 292 293 // listPets 294 assert.NotNil(t, op.Get) 295 assert.Equal(t, "Lists the pets known to the store.", op.Get.Summary) 296 assert.Equal(t, "By default it will only lists pets that are available for sale.\nThis can be changed with the status flag.", op.Get.Description) 297 assert.Equal(t, "listPets", op.Get.ID) 298 assert.EqualValues(t, []string{"pets"}, op.Get.Tags) 299 var names namedParams 300 for i, v := range op.Get.Parameters { 301 names = append(names, namedParam{Index: i, Name: v.Name}) 302 } 303 sort.Sort(names) 304 sparam := op.Get.Parameters[names[1].Index] 305 assert.Equal(t, "Status", sparam.Description) 306 assert.Equal(t, "query", sparam.In) 307 assert.Equal(t, "string", sparam.Type) 308 assert.Equal(t, "", sparam.Format) 309 assert.False(t, sparam.Required) 310 assert.Equal(t, "Status", sparam.Extensions["x-go-name"]) 311 assert.Equal(t, "#/responses/genericError", op.Get.Responses.Default.Ref.String()) 312 assert.Len(t, op.Get.Parameters, 2) 313 sparam1 := op.Get.Parameters[names[0].Index] 314 assert.Equal(t, "Birthday", sparam1.Description) 315 assert.Equal(t, "query", sparam1.In) 316 assert.Equal(t, "string", sparam1.Type) 317 assert.Equal(t, "date", sparam1.Format) 318 assert.False(t, sparam1.Required) 319 assert.Equal(t, "Birthday", sparam1.Extensions["x-go-name"]) 320 rs, ok := op.Get.Responses.StatusCodeResponses[200] 321 assert.True(t, ok) 322 assert.NotNil(t, rs.Schema) 323 aprop := rs.Schema 324 assert.Equal(t, "array", aprop.Type[0]) 325 assert.NotNil(t, aprop.Items) 326 assert.NotNil(t, aprop.Items.Schema) 327 assert.Equal(t, "#/definitions/pet", aprop.Items.Schema.Ref.String()) 328 329 // createPet 330 assert.NotNil(t, op.Post) 331 assert.Equal(t, "Creates a new pet in the store.", op.Post.Summary) 332 assert.Equal(t, "", op.Post.Description) 333 assert.Equal(t, "createPet", op.Post.ID) 334 assert.EqualValues(t, []string{"pets"}, op.Post.Tags) 335 verifyRefParam(t, op.Post.Parameters[0], "The pet to submit.", "pet") 336 assert.Equal(t, "#/responses/genericError", op.Post.Responses.Default.Ref.String()) 337 rs, ok = op.Post.Responses.StatusCodeResponses[200] 338 assert.True(t, ok) 339 assert.NotNil(t, rs.Schema) 340 aprop = rs.Schema 341 assert.Equal(t, "#/definitions/pet", aprop.Ref.String()) 342 343 // path /pets/{id} 344 op, ok = paths["/pets/{id}"] 345 assert.True(t, ok) 346 assert.NotNil(t, op) 347 348 // getPetById 349 assert.NotNil(t, op.Get) 350 assert.Equal(t, "Gets the details for a pet.", op.Get.Summary) 351 assert.Equal(t, "", op.Get.Description) 352 assert.Equal(t, "getPetById", op.Get.ID) 353 assert.EqualValues(t, []string{"pets"}, op.Get.Tags) 354 verifyIDParam(t, op.Get.Parameters[0], "The ID of the pet") 355 assert.Equal(t, "#/responses/genericError", op.Get.Responses.Default.Ref.String()) 356 rs, ok = op.Get.Responses.StatusCodeResponses[200] 357 assert.True(t, ok) 358 assert.NotNil(t, rs.Schema) 359 aprop = rs.Schema 360 assert.Equal(t, "#/definitions/pet", aprop.Ref.String()) 361 362 // updatePet 363 assert.NotNil(t, op.Put) 364 assert.Equal(t, "Updates the details for a pet.", op.Put.Summary) 365 assert.Equal(t, "", op.Put.Description) 366 assert.Equal(t, "updatePet", op.Put.ID) 367 assert.EqualValues(t, []string{"pets"}, op.Put.Tags) 368 verifyIDParam(t, op.Put.Parameters[0], "The ID of the pet") 369 verifyRefParam(t, op.Put.Parameters[1], "The pet to submit.", "pet") 370 assert.Equal(t, "#/responses/genericError", op.Put.Responses.Default.Ref.String()) 371 rs, ok = op.Put.Responses.StatusCodeResponses[200] 372 assert.True(t, ok) 373 assert.NotNil(t, rs.Schema) 374 aprop = rs.Schema 375 assert.Equal(t, "#/definitions/pet", aprop.Ref.String()) 376 377 // deletePet 378 assert.NotNil(t, op.Delete) 379 assert.Equal(t, "Deletes a pet from the store.", op.Delete.Summary) 380 assert.Equal(t, "", op.Delete.Description) 381 assert.Equal(t, "deletePet", op.Delete.ID) 382 assert.EqualValues(t, []string{"pets"}, op.Delete.Tags) 383 verifyIDParam(t, op.Delete.Parameters[0], "The ID of the pet") 384 assert.Equal(t, "#/responses/genericError", op.Delete.Responses.Default.Ref.String()) 385 _, ok = op.Delete.Responses.StatusCodeResponses[204] 386 assert.True(t, ok) 387 388 // path /orders/{id} 389 op, ok = paths["/orders/{id}"] 390 assert.True(t, ok) 391 assert.NotNil(t, op) 392 393 // getOrderDetails 394 assert.NotNil(t, op.Get) 395 assert.Equal(t, "Gets the details for an order.", op.Get.Summary) 396 assert.Equal(t, "", op.Get.Description) 397 assert.Equal(t, "getOrderDetails", op.Get.ID) 398 assert.EqualValues(t, []string{"orders"}, op.Get.Tags) 399 verifyIDParam(t, op.Get.Parameters[0], "The ID of the order") 400 assert.Equal(t, "#/responses/genericError", op.Get.Responses.Default.Ref.String()) 401 rs, ok = op.Get.Responses.StatusCodeResponses[200] 402 assert.True(t, ok) 403 assert.Equal(t, "#/responses/orderResponse", rs.Ref.String()) 404 rsm := doc.Responses["orderResponse"] 405 assert.NotNil(t, rsm.Schema) 406 assert.Equal(t, "#/definitions/order", rsm.Schema.Ref.String()) 407 408 // cancelOrder 409 assert.NotNil(t, op.Delete) 410 assert.Equal(t, "Deletes an order.", op.Delete.Summary) 411 assert.Equal(t, "", op.Delete.Description) 412 assert.Equal(t, "cancelOrder", op.Delete.ID) 413 assert.EqualValues(t, []string{"orders"}, op.Delete.Tags) 414 verifyIDParam(t, op.Delete.Parameters[0], "The ID of the order") 415 assert.Equal(t, "#/responses/genericError", op.Delete.Responses.Default.Ref.String()) 416 _, ok = op.Delete.Responses.StatusCodeResponses[204] 417 assert.True(t, ok) 418 419 // updateOrder 420 assert.NotNil(t, op.Put) 421 assert.Equal(t, "Updates an order.", op.Put.Summary) 422 assert.Equal(t, "", op.Put.Description) 423 assert.Equal(t, "updateOrder", op.Put.ID) 424 assert.EqualValues(t, []string{"orders"}, op.Put.Tags) 425 verifyIDParam(t, op.Put.Parameters[0], "The ID of the order") 426 verifyRefParam(t, op.Put.Parameters[1], "The order to submit", "order") 427 assert.Equal(t, "#/responses/genericError", op.Put.Responses.Default.Ref.String()) 428 rs, ok = op.Put.Responses.StatusCodeResponses[200] 429 assert.True(t, ok) 430 assert.NotNil(t, rs.Schema) 431 aprop = rs.Schema 432 assert.Equal(t, "#/definitions/order", aprop.Ref.String()) 433 434 // path /orders 435 op, ok = paths["/orders"] 436 assert.True(t, ok) 437 assert.NotNil(t, op) 438 439 // createOrder 440 assert.NotNil(t, op.Post) 441 assert.Equal(t, "Creates an order.", op.Post.Summary) 442 assert.Equal(t, "", op.Post.Description) 443 assert.Equal(t, "createOrder", op.Post.ID) 444 assert.EqualValues(t, []string{"orders"}, op.Post.Tags) 445 verifyRefParam(t, op.Post.Parameters[0], "The order to submit", "order") 446 assert.Equal(t, "#/responses/genericError", op.Post.Responses.Default.Ref.String()) 447 rs, ok = op.Post.Responses.StatusCodeResponses[200] 448 assert.True(t, ok) 449 assert.Equal(t, "#/responses/orderResponse", rs.Ref.String()) 450 rsm = doc.Responses["orderResponse"] 451 assert.NotNil(t, rsm.Schema) 452 assert.Equal(t, "#/definitions/order", rsm.Schema.Ref.String()) 453 } 454 455 func verifyIDParam(t testing.TB, param spec.Parameter, description string) { 456 assert.Equal(t, description, param.Description) 457 assert.Equal(t, "path", param.In) 458 assert.Equal(t, "integer", param.Type) 459 assert.Equal(t, "int64", param.Format) 460 assert.True(t, param.Required) 461 assert.Equal(t, "ID", param.Extensions["x-go-name"]) 462 } 463 464 func verifyRefParam(t testing.TB, param spec.Parameter, description, refed string) { 465 assert.Equal(t, description, param.Description) 466 assert.Equal(t, "body", param.In) 467 assert.Equal(t, "#/definitions/"+refed, param.Schema.Ref.String()) 468 assert.True(t, param.Required) 469 } 470 471 func TestSectionedParser_TitleDescription(t *testing.T) { 472 text := `This has a title, separated by a whitespace line 473 474 In this example the punctuation for the title should not matter for swagger. 475 For go it will still make a difference though. 476 ` 477 text2 := `This has a title without whitespace. 478 The punctuation here does indeed matter. But it won't for go. 479 ` 480 481 text3 := `This has a title, and markdown in the description 482 483 See how markdown works now, we can have lists: 484 485 + first item 486 + second item 487 + third item 488 489 [Links works too](http://localhost) 490 ` 491 492 text4 := `This has whitespace sensitive markdown in the description 493 494 |+ first item 495 | + nested item 496 | + also nested item 497 498 Sample code block: 499 500 | fmt.Println("Hello World!") 501 502 ` 503 504 var err error 505 506 st := §ionedParser{} 507 st.setTitle = func(lines []string) {} 508 err = st.Parse(ascg(text)) 509 assert.NoError(t, err) 510 511 assert.EqualValues(t, []string{"This has a title, separated by a whitespace line"}, st.Title()) 512 assert.EqualValues(t, []string{"In this example the punctuation for the title should not matter for swagger.", "For go it will still make a difference though."}, st.Description()) 513 514 st = §ionedParser{} 515 st.setTitle = func(lines []string) {} 516 err = st.Parse(ascg(text2)) 517 assert.NoError(t, err) 518 519 assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title()) 520 assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description()) 521 522 st = §ionedParser{} 523 st.setTitle = func(lines []string) {} 524 err = st.Parse(ascg(text3)) 525 assert.NoError(t, err) 526 527 assert.EqualValues(t, []string{"This has a title, and markdown in the description"}, st.Title()) 528 assert.EqualValues(t, []string{"See how markdown works now, we can have lists:", "", "+ first item", "+ second item", "+ third item", "", "[Links works too](http://localhost)"}, st.Description()) 529 530 st = §ionedParser{} 531 st.setTitle = func(lines []string) {} 532 err = st.Parse(ascg(text4)) 533 assert.NoError(t, err) 534 535 assert.EqualValues(t, []string{"This has whitespace sensitive markdown in the description"}, st.Title()) 536 assert.EqualValues(t, []string{"+ first item", " + nested item", " + also nested item", "", "Sample code block:", "", " fmt.Println(\"Hello World!\")"}, st.Description()) 537 } 538 539 func dummyBuilder() schemaValidations { 540 return schemaValidations{new(spec.Schema)} 541 } 542 543 func TestSectionedParser_TagsDescription(t *testing.T) { 544 block := `This has a title without whitespace. 545 The punctuation here does indeed matter. But it won't for go. 546 minimum: 10 547 maximum: 20 548 ` 549 block2 := `This has a title without whitespace. 550 The punctuation here does indeed matter. But it won't for go. 551 552 minimum: 10 553 maximum: 20 554 ` 555 556 var err error 557 558 st := §ionedParser{} 559 st.setTitle = func(lines []string) {} 560 st.taggers = []tagParser{ 561 {"Maximum", false, false, nil, &setMaximum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMaximumFmt, ""))}}, 562 {"Minimum", false, false, nil, &setMinimum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMinimumFmt, ""))}}, 563 {"MultipleOf", false, false, nil, &setMultipleOf{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMultipleOfFmt, ""))}}, 564 } 565 566 err = st.Parse(ascg(block)) 567 assert.NoError(t, err) 568 assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title()) 569 assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description()) 570 assert.Len(t, st.matched, 2) 571 _, ok := st.matched["Maximum"] 572 assert.True(t, ok) 573 _, ok = st.matched["Minimum"] 574 assert.True(t, ok) 575 576 st = §ionedParser{} 577 st.setTitle = func(lines []string) {} 578 st.taggers = []tagParser{ 579 {"Maximum", false, false, nil, &setMaximum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMaximumFmt, ""))}}, 580 {"Minimum", false, false, nil, &setMinimum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMinimumFmt, ""))}}, 581 {"MultipleOf", false, false, nil, &setMultipleOf{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMultipleOfFmt, ""))}}, 582 } 583 584 err = st.Parse(ascg(block2)) 585 assert.NoError(t, err) 586 assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title()) 587 assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description()) 588 assert.Len(t, st.matched, 2) 589 _, ok = st.matched["Maximum"] 590 assert.True(t, ok) 591 _, ok = st.matched["Minimum"] 592 assert.True(t, ok) 593 } 594 595 func TestSectionedParser_Empty(t *testing.T) { 596 block := `swagger:response someResponse` 597 598 var err error 599 600 st := §ionedParser{} 601 st.setTitle = func(lines []string) {} 602 ap := newSchemaAnnotationParser("SomeResponse") 603 ap.rx = rxResponseOverride 604 st.annotation = ap 605 606 err = st.Parse(ascg(block)) 607 assert.NoError(t, err) 608 assert.Empty(t, st.Title()) 609 assert.Empty(t, st.Description()) 610 assert.Empty(t, st.taggers) 611 assert.Equal(t, "SomeResponse", ap.GoName) 612 assert.Equal(t, "someResponse", ap.Name) 613 } 614 615 func TestSectionedParser_SkipSectionAnnotation(t *testing.T) { 616 block := `swagger:model someModel 617 618 This has a title without whitespace. 619 The punctuation here does indeed matter. But it won't for go. 620 621 minimum: 10 622 maximum: 20 623 ` 624 var err error 625 626 st := §ionedParser{} 627 st.setTitle = func(lines []string) {} 628 ap := newSchemaAnnotationParser("SomeModel") 629 st.annotation = ap 630 st.taggers = []tagParser{ 631 {"Maximum", false, false, nil, &setMaximum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMaximumFmt, ""))}}, 632 {"Minimum", false, false, nil, &setMinimum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMinimumFmt, ""))}}, 633 {"MultipleOf", false, false, nil, &setMultipleOf{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMultipleOfFmt, ""))}}, 634 } 635 636 err = st.Parse(ascg(block)) 637 assert.NoError(t, err) 638 assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title()) 639 assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description()) 640 assert.Len(t, st.matched, 2) 641 _, ok := st.matched["Maximum"] 642 assert.True(t, ok) 643 _, ok = st.matched["Minimum"] 644 assert.True(t, ok) 645 assert.Equal(t, "SomeModel", ap.GoName) 646 assert.Equal(t, "someModel", ap.Name) 647 } 648 649 func TestSectionedParser_TerminateOnNewAnnotation(t *testing.T) { 650 block := `swagger:model someModel 651 652 This has a title without whitespace. 653 The punctuation here does indeed matter. But it won't for go. 654 655 minimum: 10 656 swagger:meta 657 maximum: 20 658 ` 659 var err error 660 661 st := §ionedParser{} 662 st.setTitle = func(lines []string) {} 663 ap := newSchemaAnnotationParser("SomeModel") 664 st.annotation = ap 665 st.taggers = []tagParser{ 666 {"Maximum", false, false, nil, &setMaximum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMaximumFmt, ""))}}, 667 {"Minimum", false, false, nil, &setMinimum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMinimumFmt, ""))}}, 668 {"MultipleOf", false, false, nil, &setMultipleOf{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMultipleOfFmt, ""))}}, 669 } 670 671 err = st.Parse(ascg(block)) 672 assert.NoError(t, err) 673 assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title()) 674 assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description()) 675 assert.Len(t, st.matched, 1) 676 _, ok := st.matched["Maximum"] 677 assert.False(t, ok) 678 _, ok = st.matched["Minimum"] 679 assert.True(t, ok) 680 assert.Equal(t, "SomeModel", ap.GoName) 681 assert.Equal(t, "someModel", ap.Name) 682 } 683 684 func ascg(txt string) *ast.CommentGroup { 685 var cg ast.CommentGroup 686 for _, line := range strings.Split(txt, "\n") { 687 var cmt ast.Comment 688 cmt.Text = "// " + line 689 cg.List = append(cg.List, &cmt) 690 } 691 return &cg 692 } 693 694 func TestSchemaValueExtractors(t *testing.T) { 695 strfmts := []string{ 696 "// swagger:strfmt ", 697 "* swagger:strfmt ", 698 "* swagger:strfmt ", 699 " swagger:strfmt ", 700 "swagger:strfmt ", 701 "// swagger:strfmt ", 702 "* swagger:strfmt ", 703 "* swagger:strfmt ", 704 " swagger:strfmt ", 705 "swagger:strfmt ", 706 } 707 models := []string{ 708 "// swagger:model ", 709 "* swagger:model ", 710 "* swagger:model ", 711 " swagger:model ", 712 "swagger:model ", 713 "// swagger:model ", 714 "* swagger:model ", 715 "* swagger:model ", 716 " swagger:model ", 717 "swagger:model ", 718 } 719 720 allOf := []string{ 721 "// swagger:allOf ", 722 "* swagger:allOf ", 723 "* swagger:allOf ", 724 " swagger:allOf ", 725 "swagger:allOf ", 726 "// swagger:allOf ", 727 "* swagger:allOf ", 728 "* swagger:allOf ", 729 " swagger:allOf ", 730 "swagger:allOf ", 731 } 732 733 parameters := []string{ 734 "// swagger:parameters ", 735 "* swagger:parameters ", 736 "* swagger:parameters ", 737 " swagger:parameters ", 738 "swagger:parameters ", 739 "// swagger:parameters ", 740 "* swagger:parameters ", 741 "* swagger:parameters ", 742 " swagger:parameters ", 743 "swagger:parameters ", 744 } 745 746 validParams := []string{ 747 "yada123", 748 "date", 749 "date-time", 750 "long-combo-1-with-combo-2-and-a-3rd-one-too", 751 } 752 invalidParams := []string{ 753 "1-yada-3", 754 "1-2-3", 755 "-yada-3", 756 "-2-3", 757 "*blah", 758 "blah*", 759 } 760 761 verifySwaggerOneArgSwaggerTag(t, rxStrFmt, strfmts, validParams, append(invalidParams, "", " ", " ")) 762 verifySwaggerOneArgSwaggerTag(t, rxModelOverride, models, append(validParams, "", " ", " "), invalidParams) 763 764 verifySwaggerOneArgSwaggerTag(t, rxAllOf, allOf, append(validParams, "", " ", " "), invalidParams) 765 766 verifySwaggerMultiArgSwaggerTag(t, rxParametersOverride, parameters, validParams, invalidParams) 767 768 verifyMinMax(t, rxf(rxMinimumFmt, ""), "min", []string{"", ">", "="}) 769 verifyMinMax(t, rxf(rxMinimumFmt, fmt.Sprintf(rxItemsPrefixFmt, 1)), "items.min", []string{"", ">", "="}) 770 verifyMinMax(t, rxf(rxMaximumFmt, ""), "max", []string{"", "<", "="}) 771 verifyMinMax(t, rxf(rxMaximumFmt, fmt.Sprintf(rxItemsPrefixFmt, 1)), "items.max", []string{"", "<", "="}) 772 verifyNumeric2Words(t, rxf(rxMultipleOfFmt, ""), "multiple", "of") 773 verifyNumeric2Words(t, rxf(rxMultipleOfFmt, fmt.Sprintf(rxItemsPrefixFmt, 1)), "items.multiple", "of") 774 775 verifyIntegerMinMaxManyWords(t, rxf(rxMinLengthFmt, ""), "min", []string{"len", "length"}) 776 // pattern 777 extraSpaces := []string{"", " ", " ", " "} 778 prefixes := []string{"//", "*", ""} 779 patArgs := []string{"^\\w+$", "[A-Za-z0-9-.]*"} 780 patNames := []string{"pattern", "Pattern"} 781 for _, pref := range prefixes { 782 for _, es1 := range extraSpaces { 783 for _, nm := range patNames { 784 for _, es2 := range extraSpaces { 785 for _, es3 := range extraSpaces { 786 for _, arg := range patArgs { 787 line := strings.Join([]string{pref, es1, nm, es2, ":", es3, arg}, "") 788 matches := rxf(rxPatternFmt, "").FindStringSubmatch(line) 789 assert.Len(t, matches, 2) 790 assert.Equal(t, arg, matches[1]) 791 } 792 } 793 } 794 } 795 } 796 } 797 798 verifyIntegerMinMaxManyWords(t, rxf(rxMinItemsFmt, ""), "min", []string{"items"}) 799 verifyBoolean(t, rxf(rxUniqueFmt, ""), []string{"unique"}, nil) 800 801 verifyBoolean(t, rxReadOnly, []string{"read"}, []string{"only"}) 802 verifyBoolean(t, rxRequired, []string{"required"}, nil) 803 } 804 805 func makeMinMax(lower string) (res []string) { 806 for _, a := range []string{"", "imum"} { 807 res = append(res, lower+a, strings.Title(lower)+a) 808 } 809 return 810 } 811 812 func verifyBoolean(t *testing.T, matcher *regexp.Regexp, names, names2 []string) { 813 extraSpaces := []string{"", " ", " ", " "} 814 prefixes := []string{"//", "*", ""} 815 validArgs := []string{"true", "false"} 816 invalidArgs := []string{"TRUE", "FALSE", "t", "f", "1", "0", "True", "False", "true*", "false*"} 817 var nms []string 818 for _, nm := range names { 819 nms = append(nms, nm, strings.Title(nm)) 820 } 821 822 var nms2 []string 823 for _, nm := range names2 { 824 nms2 = append(nms2, nm, strings.Title(nm)) 825 } 826 827 var rnms []string 828 if len(nms2) > 0 { 829 for _, nm := range nms { 830 for _, es := range append(extraSpaces, "-") { 831 for _, nm2 := range nms2 { 832 rnms = append(rnms, strings.Join([]string{nm, es, nm2}, "")) 833 } 834 } 835 } 836 } else { 837 rnms = nms 838 } 839 840 var cnt int 841 for _, pref := range prefixes { 842 for _, es1 := range extraSpaces { 843 for _, nm := range rnms { 844 for _, es2 := range extraSpaces { 845 for _, es3 := range extraSpaces { 846 for _, vv := range validArgs { 847 line := strings.Join([]string{pref, es1, nm, es2, ":", es3, vv}, "") 848 matches := matcher.FindStringSubmatch(line) 849 assert.Len(t, matches, 2) 850 assert.Equal(t, vv, matches[1]) 851 cnt++ 852 } 853 for _, iv := range invalidArgs { 854 line := strings.Join([]string{pref, es1, nm, es2, ":", es3, iv}, "") 855 matches := matcher.FindStringSubmatch(line) 856 assert.Empty(t, matches) 857 cnt++ 858 } 859 } 860 } 861 } 862 } 863 } 864 var nm2 string 865 if len(names2) > 0 { 866 nm2 = " " + names2[0] 867 } 868 var Debug = os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != "" 869 if Debug { 870 fmt.Printf("tested %d %s%s combinations\n", cnt, names[0], nm2) 871 } 872 } 873 874 func verifyIntegerMinMaxManyWords(t *testing.T, matcher *regexp.Regexp, name1 string, words []string) { 875 extraSpaces := []string{"", " ", " ", " "} 876 prefixes := []string{"//", "*", ""} 877 validNumericArgs := []string{"0", "1234"} 878 invalidNumericArgs := []string{"1A3F", "2e10", "*12", "12*", "-1235", "0.0", "1234.0394", "-2948.484"} 879 880 var names []string 881 for _, w := range words { 882 names = append(names, w, strings.Title(w)) 883 } 884 885 var cnt int 886 for _, pref := range prefixes { 887 for _, es1 := range extraSpaces { 888 for _, nm1 := range makeMinMax(name1) { 889 for _, es2 := range append(extraSpaces, "-") { 890 for _, nm2 := range names { 891 for _, es3 := range extraSpaces { 892 for _, es4 := range extraSpaces { 893 for _, vv := range validNumericArgs { 894 line := strings.Join([]string{pref, es1, nm1, es2, nm2, es3, ":", es4, vv}, "") 895 matches := matcher.FindStringSubmatch(line) 896 //fmt.Printf("matching %q, matches (%d): %v\n", line, len(matches), matches) 897 assert.Len(t, matches, 2) 898 assert.Equal(t, vv, matches[1]) 899 cnt++ 900 } 901 for _, iv := range invalidNumericArgs { 902 line := strings.Join([]string{pref, es1, nm1, es2, nm2, es3, ":", es4, iv}, "") 903 matches := matcher.FindStringSubmatch(line) 904 assert.Empty(t, matches) 905 cnt++ 906 } 907 } 908 } 909 } 910 } 911 } 912 } 913 } 914 var nm2 string 915 if len(words) > 0 { 916 nm2 = " " + words[0] 917 } 918 var Debug = os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != "" 919 if Debug { 920 fmt.Printf("tested %d %s%s combinations\n", cnt, name1, nm2) 921 922 } 923 } 924 925 func verifyNumeric2Words(t *testing.T, matcher *regexp.Regexp, name1, name2 string) { 926 extraSpaces := []string{"", " ", " ", " "} 927 prefixes := []string{"//", "*", ""} 928 validNumericArgs := []string{"0", "1234", "-1235", "0.0", "1234.0394", "-2948.484"} 929 invalidNumericArgs := []string{"1A3F", "2e10", "*12", "12*"} 930 931 var cnt int 932 for _, pref := range prefixes { 933 for _, es1 := range extraSpaces { 934 for _, es2 := range extraSpaces { 935 for _, es3 := range extraSpaces { 936 for _, es4 := range extraSpaces { 937 for _, vv := range validNumericArgs { 938 lines := []string{ 939 strings.Join([]string{pref, es1, name1, es2, name2, es3, ":", es4, vv}, ""), 940 strings.Join([]string{pref, es1, strings.Title(name1), es2, strings.Title(name2), es3, ":", es4, vv}, ""), 941 strings.Join([]string{pref, es1, strings.Title(name1), es2, name2, es3, ":", es4, vv}, ""), 942 strings.Join([]string{pref, es1, name1, es2, strings.Title(name2), es3, ":", es4, vv}, ""), 943 } 944 for _, line := range lines { 945 matches := matcher.FindStringSubmatch(line) 946 //fmt.Printf("matching %q, matches (%d): %v\n", line, len(matches), matches) 947 assert.Len(t, matches, 2) 948 assert.Equal(t, vv, matches[1]) 949 cnt++ 950 } 951 } 952 for _, iv := range invalidNumericArgs { 953 lines := []string{ 954 strings.Join([]string{pref, es1, name1, es2, name2, es3, ":", es4, iv}, ""), 955 strings.Join([]string{pref, es1, strings.Title(name1), es2, strings.Title(name2), es3, ":", es4, iv}, ""), 956 strings.Join([]string{pref, es1, strings.Title(name1), es2, name2, es3, ":", es4, iv}, ""), 957 strings.Join([]string{pref, es1, name1, es2, strings.Title(name2), es3, ":", es4, iv}, ""), 958 } 959 for _, line := range lines { 960 matches := matcher.FindStringSubmatch(line) 961 //fmt.Printf("matching %q, matches (%d): %v\n", line, len(matches), matches) 962 assert.Empty(t, matches) 963 cnt++ 964 } 965 } 966 } 967 } 968 } 969 } 970 } 971 var Debug = os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != "" 972 if Debug { 973 fmt.Printf("tested %d %s %s combinations\n", cnt, name1, name2) 974 } 975 } 976 977 func verifyMinMax(t *testing.T, matcher *regexp.Regexp, name string, operators []string) { 978 extraSpaces := []string{"", " ", " ", " "} 979 prefixes := []string{"//", "*", ""} 980 validNumericArgs := []string{"0", "1234", "-1235", "0.0", "1234.0394", "-2948.484"} 981 invalidNumericArgs := []string{"1A3F", "2e10", "*12", "12*"} 982 983 var cnt int 984 for _, pref := range prefixes { 985 for _, es1 := range extraSpaces { 986 for _, wrd := range makeMinMax(name) { 987 for _, es2 := range extraSpaces { 988 for _, es3 := range extraSpaces { 989 for _, op := range operators { 990 for _, es4 := range extraSpaces { 991 for _, vv := range validNumericArgs { 992 line := strings.Join([]string{pref, es1, wrd, es2, ":", es3, op, es4, vv}, "") 993 matches := matcher.FindStringSubmatch(line) 994 // fmt.Printf("matching %q with %q, matches (%d): %v\n", line, matcher, len(matches), matches) 995 assert.Len(t, matches, 3) 996 assert.Equal(t, vv, matches[2]) 997 cnt++ 998 } 999 for _, iv := range invalidNumericArgs { 1000 line := strings.Join([]string{pref, es1, wrd, es2, ":", es3, op, es4, iv}, "") 1001 matches := matcher.FindStringSubmatch(line) 1002 assert.Empty(t, matches) 1003 cnt++ 1004 } 1005 } 1006 } 1007 } 1008 } 1009 } 1010 } 1011 } 1012 var Debug = os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != "" 1013 if Debug { 1014 fmt.Printf("tested %d %s combinations\n", cnt, name) 1015 } 1016 } 1017 1018 func verifySwaggerOneArgSwaggerTag(t *testing.T, matcher *regexp.Regexp, prefixes, validParams, invalidParams []string) { 1019 for _, pref := range prefixes { 1020 for _, param := range validParams { 1021 line := pref + param 1022 matches := matcher.FindStringSubmatch(line) 1023 if assert.Len(t, matches, 2) { 1024 assert.Equal(t, strings.TrimSpace(param), matches[1]) 1025 } 1026 } 1027 } 1028 1029 for _, pref := range prefixes { 1030 for _, param := range invalidParams { 1031 line := pref + param 1032 matches := matcher.FindStringSubmatch(line) 1033 assert.Empty(t, matches) 1034 } 1035 } 1036 } 1037 1038 func verifySwaggerMultiArgSwaggerTag(t *testing.T, matcher *regexp.Regexp, prefixes, validParams, invalidParams []string) { 1039 var actualParams []string 1040 for i := 0; i < len(validParams); i++ { 1041 var vp []string 1042 for j := 0; j < (i + 1); j++ { 1043 vp = append(vp, validParams[j]) 1044 } 1045 actualParams = append(actualParams, strings.Join(vp, " ")) 1046 } 1047 for _, pref := range prefixes { 1048 for _, param := range actualParams { 1049 line := pref + param 1050 matches := matcher.FindStringSubmatch(line) 1051 // fmt.Printf("matching %q with %q, matches (%d): %v\n", line, matcher, len(matches), matches) 1052 assert.Len(t, matches, 2) 1053 assert.Equal(t, strings.TrimSpace(param), matches[1]) 1054 } 1055 } 1056 1057 for _, pref := range prefixes { 1058 for _, param := range invalidParams { 1059 line := pref + param 1060 matches := matcher.FindStringSubmatch(line) 1061 assert.Empty(t, matches) 1062 } 1063 } 1064 } 1065 1066 func TestEnhancement793(t *testing.T) { 1067 var err error 1068 scanner, err := newAppScanner(&Opts{ 1069 BasePath: "../fixtures/enhancements/793", 1070 ScanModels: true, 1071 }) 1072 assert.NoError(t, err) 1073 assert.NotNil(t, scanner) 1074 doc, err := scanner.Parse() 1075 assert.NoError(t, err) 1076 if assert.NotNil(t, doc) { 1077 bytes, err := doc.MarshalJSON() 1078 assert.NoError(t, err) 1079 assert.NotEmpty(t, bytes) 1080 1081 file, _ := ioutil.TempFile(os.TempDir(), "scanner") 1082 file.Write(bytes) 1083 file.Close() 1084 1085 doc, err := loads.Spec(file.Name()) 1086 if assert.NoError(t, err) { 1087 validator := validate.NewSpecValidator(doc.Schema(), strfmt.Default) 1088 res, _ := validator.Validate(doc) 1089 assert.Empty(t, res.Errors) 1090 assert.True(t, res.IsValid()) 1091 } 1092 } 1093 }