github.com/6543-forks/go-swagger@v0.26.0/generator/shared_test.go (about) 1 package generator 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "log" 7 "os" 8 "path" 9 "path/filepath" 10 "runtime" 11 "testing" 12 13 "github.com/go-openapi/loads" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 const ( 19 defaultAPIPackage = "operations" 20 defaultClientPackage = "client" 21 defaultModelPackage = "models" 22 defaultServerPackage = "restapi" 23 ) 24 25 // Perform common initialization of template repository before running tests. 26 // This allows to run tests unitarily (e.g. go test -run xxx ). 27 func TestMain(m *testing.M) { 28 // initializations to run tests in this package 29 log.SetFlags(log.LstdFlags | log.Lshortfile) 30 templates.LoadDefaults() 31 initSchemaValidationTest() 32 os.Exit(m.Run()) 33 } 34 35 func opts() *GenOpts { 36 var opts GenOpts 37 opts.IncludeValidator = true 38 opts.IncludeModel = true 39 if err := opts.EnsureDefaults(); err != nil { 40 panic(err) 41 } 42 return &opts 43 } 44 45 func testGenOpts() *GenOpts { 46 g := &GenOpts{} 47 g.Target = "." 48 g.APIPackage = defaultAPIPackage 49 g.ModelPackage = defaultModelPackage 50 g.ServerPackage = defaultServerPackage 51 g.ClientPackage = defaultClientPackage 52 g.Principal = "" 53 g.DefaultScheme = "http" 54 g.IncludeModel = true 55 g.IncludeValidator = true 56 g.IncludeModel = true 57 g.IncludeHandler = true 58 g.IncludeParameters = true 59 g.IncludeResponses = true 60 g.IncludeMain = false 61 g.IncludeSupport = true 62 g.ExcludeSpec = true 63 g.TemplateDir = "" 64 g.DumpData = false 65 66 if err := g.EnsureDefaults(); err != nil { 67 panic(err) 68 } 69 return g 70 } 71 72 // TODO: there is a catch, since these methods are sensitive 73 // to the CWD of the current swagger command (or go 74 // generate when working on resulting template) 75 // NOTE: 76 // Errors in CheckOpts are hard to simulate since 77 // they occur only on os.Getwd() errors 78 // Windows style path is difficult to test on unix 79 // since the filepath pkg is platform dependent 80 func TestShared_CheckOpts(t *testing.T) { 81 testPath := filepath.Join("a", "b", "b") 82 83 log.SetOutput(ioutil.Discard) 84 defer log.SetOutput(os.Stdout) 85 86 var opts = new(GenOpts) 87 _ = opts.EnsureDefaults() 88 cwd, _ := os.Getwd() 89 opts.Spec = "../fixtures/codegen/simplesearch.yml" 90 91 opts.Target = filepath.Join(".", "a", "b", "c") 92 opts.ServerPackage = filepath.Join(cwd, "a", "b", "c") 93 err := opts.CheckOpts() 94 assert.Error(t, err) 95 96 opts.Target = filepath.Join(cwd, "a", "b", "c") 97 opts.ServerPackage = testPath 98 opts.Spec = filepath.Join(cwd, "nowhere", "swagger.yaml") 99 err = opts.CheckOpts() 100 assert.Error(t, err) 101 102 opts.Target = filepath.Join(cwd, "a", "b", "c") 103 opts.ServerPackage = testPath 104 opts.Spec = "https://ab/c" 105 err = opts.CheckOpts() 106 assert.NoError(t, err) 107 108 opts.Target = filepath.Join(cwd, "a", "b", "c") 109 opts.ServerPackage = testPath 110 opts.Spec = "http://ab/c" 111 err = opts.CheckOpts() 112 assert.NoError(t, err) 113 114 opts.Target = filepath.Join("a", "b", "c") 115 opts.ServerPackage = testPath 116 opts.Spec = filepath.Join(cwd, "..", "fixtures", "codegen", "swagger-codegen-tests.json") 117 err = opts.CheckOpts() 118 assert.NoError(t, err) 119 120 opts.Target = filepath.Join("a", "b", "c") 121 opts.ServerPackage = testPath 122 opts.Spec = filepath.Join("..", "fixtures", "codegen", "swagger-codegen-tests.json") 123 err = opts.CheckOpts() 124 assert.NoError(t, err) 125 126 opts = nil 127 err = opts.CheckOpts() 128 assert.Error(t, err) 129 } 130 131 func TestShared_EnsureDefaults(t *testing.T) { 132 opts := &GenOpts{} 133 _ = opts.EnsureDefaults() 134 assert.True(t, opts.defaultsEnsured) 135 opts.DefaultConsumes = "https" 136 _ = opts.EnsureDefaults() 137 assert.Equal(t, "https", opts.DefaultConsumes) 138 } 139 140 // TargetPath and SpecPath are used in server.gotmpl 141 // as template variables: {{ .TestTargetPath }} and 142 // {{ .SpecPath }}, to construct the go generate 143 // directive. 144 func TestShared_TargetPath(t *testing.T) { 145 log.SetOutput(ioutil.Discard) 146 defer log.SetOutput(os.Stdout) 147 148 cwd, _ := os.Getwd() 149 150 // relative target 151 var opts = new(GenOpts) 152 _ = opts.EnsureDefaults() 153 opts.Target = filepath.Join(".", "a", "b", "c") 154 opts.ServerPackage = "y" 155 expected := filepath.Join("..", "..", "c") 156 result := opts.TargetPath() 157 assert.Equal(t, expected, result) 158 159 // relative target, server path 160 opts = new(GenOpts) 161 _ = opts.EnsureDefaults() 162 opts.Target = filepath.Join(".", "a", "b", "c") 163 opts.ServerPackage = "y/z" 164 expected = filepath.Join("..", "..", "..", "c") 165 result = opts.TargetPath() 166 assert.Equal(t, expected, result) 167 168 // absolute target 169 opts = new(GenOpts) 170 _ = opts.EnsureDefaults() 171 opts.Target = filepath.Join(cwd, "a", "b", "c") 172 opts.ServerPackage = "y" 173 expected = filepath.Join("..", "..", "c") 174 result = opts.TargetPath() 175 assert.Equal(t, expected, result) 176 177 // absolute target, server path 178 opts = new(GenOpts) 179 _ = opts.EnsureDefaults() 180 opts.Target = filepath.Join(cwd, "a", "b", "c") 181 opts.ServerPackage = path.Join("y", "z") 182 expected = filepath.Join("..", "..", "..", "c") 183 result = opts.TargetPath() 184 assert.Equal(t, expected, result) 185 } 186 187 // NOTE: file://url is not supported 188 func TestShared_SpecPath(t *testing.T) { 189 log.SetOutput(ioutil.Discard) 190 defer log.SetOutput(os.Stdout) 191 192 cwd, _ := os.Getwd() 193 194 // http URL spec 195 var opts = new(GenOpts) 196 _ = opts.EnsureDefaults() 197 opts.Spec = "http://a/b/c" 198 opts.ServerPackage = "y" 199 expected := opts.Spec 200 result := opts.SpecPath() 201 assert.Equal(t, expected, result) 202 203 // https URL spec 204 opts = new(GenOpts) 205 _ = opts.EnsureDefaults() 206 opts.Spec = "https://a/b/c" 207 opts.ServerPackage = "y" 208 expected = opts.Spec 209 result = opts.SpecPath() 210 assert.Equal(t, expected, result) 211 212 // relative spec 213 opts = new(GenOpts) 214 _ = opts.EnsureDefaults() 215 opts.Spec = filepath.Join(".", "a", "b", "c") 216 opts.Target = filepath.Join("d") 217 opts.ServerPackage = "y" 218 expected = filepath.Join("..", "..", "a", "b", "c") 219 result = opts.SpecPath() 220 assert.Equal(t, expected, result) 221 222 // relative spec, server path 223 opts = new(GenOpts) 224 _ = opts.EnsureDefaults() 225 opts.Spec = filepath.Join(".", "a", "b", "c") 226 opts.Target = filepath.Join("d", "e") 227 opts.ServerPackage = "y/z" 228 expected = filepath.Join("..", "..", "..", "..", "a", "b", "c") 229 result = opts.SpecPath() 230 assert.Equal(t, expected, result) 231 232 // relative spec, server path 233 opts = new(GenOpts) 234 _ = opts.EnsureDefaults() 235 opts.Spec = filepath.Join(".", "a", "b", "c") 236 opts.Target = filepath.Join(".", "a", "b") 237 opts.ServerPackage = "y/z" 238 expected = filepath.Join("..", "..", "c") 239 result = opts.SpecPath() 240 assert.Equal(t, expected, result) 241 242 // absolute spec 243 opts = new(GenOpts) 244 _ = opts.EnsureDefaults() 245 opts.Spec = filepath.Join(cwd, "a", "b", "c") 246 opts.ServerPackage = "y" 247 expected = filepath.Join("..", "a", "b", "c") 248 result = opts.SpecPath() 249 assert.Equal(t, expected, result) 250 251 // absolute spec, server path 252 opts = new(GenOpts) 253 _ = opts.EnsureDefaults() 254 opts.Spec = filepath.Join("..", "a", "b", "c") 255 opts.Target = "" 256 opts.ServerPackage = path.Join("y", "z") 257 expected = filepath.Join("..", "..", "..", "a", "b", "c") 258 result = opts.SpecPath() 259 assert.Equal(t, expected, result) 260 261 if runtime.GOOS == "windows" { 262 opts = new(GenOpts) 263 _ = opts.EnsureDefaults() 264 opts.Spec = filepath.Join("a", "b", "c") 265 opts.Target = filepath.Join("Z:", "e", "f", "f") 266 opts.ServerPackage = "y/z" 267 expected, _ = filepath.Abs(opts.Spec) 268 result = opts.SpecPath() 269 assert.Equal(t, expected, result) 270 } 271 } 272 273 // Low level testing: templates not found (higher level calls raise panic(), see above) 274 func TestShared_NotFoundTemplate(t *testing.T) { 275 log.SetOutput(ioutil.Discard) 276 defer log.SetOutput(os.Stdout) 277 278 opts := GenOpts{} 279 tplOpts := TemplateOpts{ 280 Name: "NotFound", 281 Source: "asset:notfound", 282 Target: ".", 283 FileName: "test_notfound.go", 284 SkipExists: false, 285 SkipFormat: false, 286 } 287 288 buf, err := opts.render(&tplOpts, nil) 289 assert.Error(t, err, "Error should be handled here") 290 assert.Nil(t, buf, "Upon error, GenOpts.render() should return nil buffer") 291 } 292 293 // Low level testing: invalid template => Get() returns not found (higher level calls raise panic(), see above) 294 // TODO: better error discrimination between absent definition and non-parsing template 295 func TestShared_GarbledTemplate(t *testing.T) { 296 log.SetOutput(ioutil.Discard) 297 defer log.SetOutput(os.Stdout) 298 299 garbled := "func x {{;;; garbled" 300 301 _ = templates.AddFile("garbled", garbled) 302 opts := GenOpts{} 303 tplOpts := TemplateOpts{ 304 Name: "Garbled", 305 Source: "asset:garbled", 306 Target: ".", 307 FileName: "test_garbled.go", 308 SkipExists: false, 309 SkipFormat: false, 310 } 311 312 buf, err := opts.render(&tplOpts, nil) 313 assert.Error(t, err, "Error should be handled here") 314 assert.Nil(t, buf, "Upon error, GenOpts.render() should return nil buffer") 315 } 316 317 // Template execution failure 318 type myTemplateData struct { 319 } 320 321 func (*myTemplateData) MyFaultyMethod() (string, error) { 322 return "", fmt.Errorf("myFaultyError") 323 } 324 325 func TestShared_ExecTemplate(t *testing.T) { 326 log.SetOutput(ioutil.Discard) 327 defer log.SetOutput(os.Stdout) 328 329 // Not a failure: no value data 330 execfailure1 := "func x {{ .NotInData }}" 331 332 _ = templates.AddFile("execfailure1", execfailure1) 333 opts := new(GenOpts) 334 tplOpts := TemplateOpts{ 335 Name: "execFailure1", 336 Source: "asset:execfailure1", 337 Target: ".", 338 FileName: "test_execfailure1.go", 339 SkipExists: false, 340 SkipFormat: false, 341 } 342 343 buf1, err := opts.render(&tplOpts, nil) 344 assert.NoError(t, err, "Template rendering should put <no value> instead of missing data, and report no error") 345 assert.Equal(t, string(buf1), "func x <no value>") 346 347 execfailure2 := "func {{ .MyFaultyMethod }}" 348 349 _ = templates.AddFile("execfailure2", execfailure2) 350 opts = new(GenOpts) 351 tplOpts2 := TemplateOpts{ 352 Name: "execFailure2", 353 Source: "asset:execfailure2", 354 Target: ".", 355 FileName: "test_execfailure2.go", 356 SkipExists: false, 357 SkipFormat: false, 358 } 359 360 data := new(myTemplateData) 361 buf2, err := opts.render(&tplOpts2, data) 362 assert.Error(t, err, "Error should be handled here: missing func in template yields an error") 363 assert.Contains(t, err.Error(), "template execution failed") 364 assert.Nil(t, buf2, "Upon error, GenOpts.render() should return nil buffer") 365 } 366 367 // Test correctly parsed templates, with bad formatting 368 func TestShared_BadFormatTemplate(t *testing.T) { 369 log.SetOutput(ioutil.Discard) 370 371 defer func() { 372 _ = os.Remove("test_badformat.gol") 373 _ = os.Remove("test_badformat2.gol") 374 log.SetOutput(os.Stdout) 375 Debug = false 376 }() 377 378 // Not skipping format 379 badFormat := "func x {;;; garbled" 380 381 Debug = true 382 _ = templates.AddFile("badformat", badFormat) 383 384 opts := GenOpts{} 385 opts.LanguageOpts = GoLangOpts() 386 tplOpts := TemplateOpts{ 387 Name: "badformat", 388 Source: "asset:badformat", 389 Target: ".", 390 // Extension ".gol" won't mess with go if cleanup is not performed 391 FileName: "test_badformat.gol", 392 SkipExists: false, 393 SkipFormat: false, 394 } 395 396 data := appGenerator{ 397 Name: "badtest", 398 Package: "wrongpkg", 399 } 400 401 err := opts.write(&tplOpts, data) 402 403 // The badly formatted file has been dumped for debugging purposes 404 _, exists := os.Stat(tplOpts.FileName) 405 assert.True(t, !os.IsNotExist(exists), "The template file has not been generated as expected") 406 _ = os.Remove(tplOpts.FileName) 407 408 assert.NotNil(t, err) 409 assert.Contains(t, err.Error(), "source formatting on generated source") 410 411 // Skipping format 412 opts = GenOpts{} 413 opts.LanguageOpts = GoLangOpts() 414 tplOpts2 := TemplateOpts{ 415 Name: "badformat2", 416 Source: "asset:badformat", 417 Target: ".", 418 FileName: "test_badformat2.gol", 419 SkipExists: false, 420 SkipFormat: true, 421 } 422 423 err2 := opts.write(&tplOpts2, data) 424 425 // The unformatted file has been dumped without format checks 426 _, exists2 := os.Stat(tplOpts2.FileName) 427 assert.True(t, !os.IsNotExist(exists2), "The template file has not been generated as expected") 428 _ = os.Remove(tplOpts2.FileName) 429 430 assert.Nil(t, err2) 431 432 // os.RemoveAll(filepath.Join(filepath.FromSlash(dr),"restapi")) 433 } 434 435 // Test dir creation 436 func TestShared_DirectoryTemplate(t *testing.T) { 437 log.SetOutput(ioutil.Discard) 438 439 defer func() { 440 _ = os.RemoveAll("TestGenDir") 441 log.SetOutput(os.Stdout) 442 }() 443 444 // Not skipping format 445 content := "func x {}" 446 447 _ = templates.AddFile("gendir", content) 448 449 opts := GenOpts{} 450 opts.LanguageOpts = GoLangOpts() 451 tplOpts := TemplateOpts{ 452 Name: "gendir", 453 Source: "asset:gendir", 454 Target: "TestGenDir", 455 // Extension ".gol" won't mess with go if cleanup is not performed 456 FileName: "test_gendir.gol", 457 SkipExists: false, 458 SkipFormat: true, 459 } 460 461 data := appGenerator{ 462 Name: "gentest", 463 Package: "stubpkg", 464 } 465 466 err := opts.write(&tplOpts, data) 467 468 // The badly formatted file has been dumped for debugging purposes 469 _, exists := os.Stat(filepath.Join(tplOpts.Target, tplOpts.FileName)) 470 assert.True(t, !os.IsNotExist(exists), "The template file has not been generated as expected") 471 _ = os.RemoveAll(tplOpts.Target) 472 473 assert.Nil(t, err) 474 } 475 476 // Test templates which are not assets (open in file) 477 // Low level testing: templates loaded from file 478 func TestShared_LoadTemplate(t *testing.T) { 479 log.SetOutput(ioutil.Discard) 480 defer log.SetOutput(os.Stdout) 481 482 opts := GenOpts{} 483 tplOpts := TemplateOpts{ 484 Name: "File", 485 Source: "File", 486 Target: ".", 487 FileName: "file.go", 488 SkipExists: false, 489 SkipFormat: false, 490 } 491 492 buf, err := opts.render(&tplOpts, nil) 493 assert.Error(t, err, "Error should be handled here") 494 assert.Contains(t, err.Error(), "open File") 495 assert.Contains(t, err.Error(), "error while opening") 496 assert.Nil(t, buf, "Upon error, GenOpts.render() should return nil buffer") 497 498 opts.TemplateDir = filepath.Join(".", "myTemplateDir") 499 buf, err = opts.render(&tplOpts, nil) 500 assert.Error(t, err, "Error should be handled here") 501 assert.Contains(t, err.Error(), "open "+filepath.Join("myTemplateDir", "File")) 502 assert.Contains(t, err.Error(), "error while opening") 503 assert.Nil(t, buf, "Upon error, GenOpts.render() should return nil buffer") 504 505 } 506 507 func TestShared_AppNameOrDefault(t *testing.T) { 508 specPath := filepath.Join("..", "fixtures", "codegen", "shipyard.yml") 509 specDoc, err := loads.Spec(specPath) 510 require.NoError(t, err) 511 require.NotNil(t, specDoc.Spec().Info) 512 specDoc.Spec().Info.Title = " " 513 assert.Equal(t, "Xyz", appNameOrDefault(specDoc, " ", "xyz")) 514 specDoc.Spec().Info.Title = "test" 515 assert.Equal(t, "Xyz", appNameOrDefault(specDoc, " ", "xyz")) 516 } 517 518 func TestShared_GatherModel(t *testing.T) { 519 specPath := filepath.Join("..", "fixtures", "codegen", "shipyard.yml") 520 521 specDoc, err := loads.Spec(specPath) 522 require.NoError(t, err) 523 524 _, err = gatherModels(specDoc, []string{"unknown"}) 525 assert.Error(t, err) 526 527 res, err := gatherModels(specDoc, []string{"Image", "Application"}) 528 require.NoError(t, err) 529 assert.Len(t, res, 2) 530 531 res, err = gatherModels(specDoc, []string{"Image", "Application"}) 532 require.NoError(t, err) 533 assert.Len(t, res, 2) 534 535 res, err = gatherModels(specDoc, []string{}) 536 require.NoError(t, err) 537 assert.Len(t, res, 4) 538 } 539 540 func TestShared_DumpWrongData(t *testing.T) { 541 assert.Error(t, dumpData(struct { 542 A func() string 543 B string 544 }{ 545 A: func() string { return "" }, 546 B: "xyz", 547 })) 548 549 assert.NoError(t, dumpData(struct { 550 A func() string `json:"-"` 551 B string 552 }{ 553 A: func() string { return "" }, 554 B: "xyz", 555 })) 556 557 assert.NoError(t, dumpData(struct { 558 a func() string 559 B string 560 }{ 561 a: func() string { return "" }, 562 B: "xyz", 563 })) 564 } 565 566 func TestResolvePrincipal(t *testing.T) { 567 for _, toPin := range []struct { 568 Title string 569 Principal string 570 Expected []string 571 }{ 572 { 573 Title: "defaults", Principal: "", 574 Expected: []string{"", "interface{}", ""}, 575 }, 576 { 577 Title: "with base import", Principal: "auth.Principal", 578 Expected: []string{"auth", "auth.Principal", "auth"}, 579 }, 580 { 581 Title: "with full import", Principal: "github.com/myproject/auth.Principal", 582 Expected: []string{"auth", "auth.Principal", "github.com/myproject/auth"}, 583 }, 584 { 585 Title: "with name conflict", Principal: "github.com/myproject/middleware.Principal", 586 Expected: []string{"auth", "auth.Principal", "github.com/myproject/middleware"}, 587 }, 588 { 589 Title: "with name conflict (2)", Principal: "github.com/myproject/principal.Principal", 590 Expected: []string{"auth", "auth.Principal", "github.com/myproject/principal"}, 591 }, 592 } { 593 fixture := toPin 594 t.Run(fixture.Title, func(t *testing.T) { 595 t.Parallel() 596 opts := &GenOpts{Principal: fixture.Principal} 597 err := opts.EnsureDefaults() 598 require.NoError(t, err) 599 alias, principal, target := opts.resolvePrincipal() 600 require.Equal(t, fixture.Expected[0], alias) 601 require.Equal(t, fixture.Expected[1], principal) 602 require.Equal(t, fixture.Expected[2], target) 603 }) 604 } 605 } 606 607 func TestDefaultImports(t *testing.T) { 608 for i, toPin := range []struct { 609 Title string 610 Opts *GenOpts 611 Expected map[string]string 612 }{ 613 { 614 Title: "defaults", 615 Opts: &GenOpts{}, 616 Expected: map[string]string{ 617 "models": "github.com/go-swagger/go-swagger/generator/models", 618 }, 619 }, 620 { 621 Title: "with base import", 622 Opts: &GenOpts{ 623 Principal: "ext.Principal", 624 }, 625 Expected: map[string]string{ 626 "ext": "github.com/go-swagger/go-swagger/generator/ext", 627 "models": "github.com/go-swagger/go-swagger/generator/models", 628 }, 629 }, 630 { 631 Title: "with full import", 632 Opts: &GenOpts{ 633 Principal: "github.com/myproject/identity.Principal", 634 }, 635 Expected: map[string]string{ 636 "identity": "github.com/myproject/identity", 637 "models": "github.com/go-swagger/go-swagger/generator/models", 638 }, 639 }, 640 { 641 Title: "with name conflict", 642 Opts: &GenOpts{ 643 Principal: "github.com/myproject/middleware.Principal", 644 }, 645 Expected: map[string]string{ 646 "auth": "github.com/myproject/middleware", 647 "models": "github.com/go-swagger/go-swagger/generator/models", 648 }, 649 }, 650 { 651 Title: "with name conflict (2)", 652 Opts: &GenOpts{ 653 Principal: "github.com/myproject/principal.Principal", 654 }, 655 Expected: map[string]string{ 656 "auth": "github.com/myproject/principal", 657 "models": "github.com/go-swagger/go-swagger/generator/models", 658 }, 659 }, 660 { 661 Title: "alternate target for models", 662 Opts: &GenOpts{ 663 ModelPackage: "target/bespoke", 664 }, 665 Expected: map[string]string{ 666 "bespoke": "github.com/go-swagger/go-swagger/generator/target/bespoke", 667 }, 668 }, 669 { 670 Title: "with existing models", 671 Opts: &GenOpts{ 672 ExistingModels: "github.com/myproject/target/bespoke", 673 }, 674 Expected: map[string]string{ 675 "models": "github.com/myproject/target/bespoke", 676 }, 677 }, 678 } { 679 fixture := toPin 680 t.Run(fixture.Title, func(t *testing.T) { 681 t.Parallel() 682 err := fixture.Opts.EnsureDefaults() 683 require.NoError(t, err) 684 imports := fixture.Opts.defaultImports() 685 require.EqualValuesf(t, fixture.Expected, imports, "unexpected imports generated with fixture %q[%d]", fixture.Title, i) 686 }) 687 } 688 }