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