github.com/go-swagger/go-swagger@v0.31.0/generator/operation_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 generator 16 17 import ( 18 "bytes" 19 "errors" 20 "os" 21 "path/filepath" 22 "testing" 23 24 "github.com/go-openapi/analysis" 25 "github.com/go-openapi/loads" 26 "github.com/go-openapi/spec" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 ) 30 31 func TestUniqueOperationNameMangling(t *testing.T) { 32 doc, err := loads.Spec("../fixtures/bugs/2213/fixture-2213.yaml") 33 require.NoError(t, err) 34 analyzed := analysis.New(doc.Spec()) 35 ops := gatherOperations(analyzed, nil) 36 assert.Contains(t, ops, "GetFoo") 37 assert.Contains(t, ops, "GetAFoo") 38 } 39 40 func TestUniqueOperationNames(t *testing.T) { 41 doc, err := loads.Spec("../fixtures/codegen/todolist.simple.yml") 42 require.NoError(t, err) 43 44 sp := doc.Spec() 45 sp.Paths.Paths["/tasks"].Post.ID = "saveTask" 46 sp.Paths.Paths["/tasks"].Post.AddExtension("origName", "createTask") 47 sp.Paths.Paths["/tasks/{id}"].Put.ID = "saveTask" 48 sp.Paths.Paths["/tasks/{id}"].Put.AddExtension("origName", "updateTask") 49 analyzed := analysis.New(sp) 50 51 ops := gatherOperations(analyzed, nil) 52 assert.Len(t, ops, 6) 53 _, exists := ops["saveTask"] 54 assert.True(t, exists) 55 _, exists = ops["PutTasksID"] 56 assert.True(t, exists) 57 } 58 59 func TestEmptyOperationNames(t *testing.T) { 60 doc, err := loads.Spec("../fixtures/codegen/todolist.simple.yml") 61 require.NoError(t, err) 62 63 sp := doc.Spec() 64 sp.Paths.Paths["/tasks"].Post.ID = "" 65 sp.Paths.Paths["/tasks"].Post.AddExtension("origName", "createTask") 66 sp.Paths.Paths["/tasks/{id}"].Put.ID = "" 67 sp.Paths.Paths["/tasks/{id}"].Put.AddExtension("origName", "updateTask") 68 analyzed := analysis.New(sp) 69 70 ops := gatherOperations(analyzed, nil) 71 assert.Len(t, ops, 6) 72 _, exists := ops["PostTasks"] 73 assert.True(t, exists) 74 _, exists = ops["PutTasksID"] 75 assert.True(t, exists) 76 } 77 78 func TestMakeResponseHeader(t *testing.T) { 79 b, err := opBuilder("getTasks", "") 80 require.NoError(t, err) 81 82 hdr := findResponseHeader(&b.Operation, 200, "X-Rate-Limit") 83 gh, er := b.MakeHeader("a", "X-Rate-Limit", *hdr) 84 require.NoError(t, er) 85 86 assert.True(t, gh.IsPrimitive) 87 assert.Equal(t, "int32", gh.GoType) 88 assert.Equal(t, "X-Rate-Limit", gh.Name) 89 } 90 91 func TestMakeResponseHeaderDefaultValues(t *testing.T) { 92 b, err := opBuilder("getTasks", "") 93 require.NoError(t, err) 94 95 testCases := []struct { 96 name string // input 97 typeStr string // expected type 98 defaultValue interface{} // expected result 99 }{ 100 {"Access-Control-Allow-Origin", "string", "*"}, 101 {"X-Rate-Limit", "int32", nil}, 102 {"X-Rate-Limit-Remaining", "int32", float64(42)}, 103 {"X-Rate-Limit-Reset", "int32", "1449875311"}, 104 {"X-Rate-Limit-Reset-Human", "string", "3 days"}, 105 {"X-Rate-Limit-Reset-Human-Number", "string", float64(3)}, 106 } 107 108 for _, tc := range testCases { 109 hdr := findResponseHeader(&b.Operation, 200, tc.name) 110 require.NotNil(t, hdr) 111 112 gh, er := b.MakeHeader("a", tc.name, *hdr) 113 require.NoError(t, er) 114 115 assert.True(t, gh.IsPrimitive) 116 assert.Equal(t, tc.typeStr, gh.GoType) 117 assert.Equal(t, tc.name, gh.Name) 118 assert.Exactly(t, tc.defaultValue, gh.Default) 119 } 120 } 121 122 func TestMakeResponse(t *testing.T) { 123 b, err := opBuilder("getTasks", "") 124 require.NoError(t, err) 125 126 resolver := &typeResolver{ModelsPackage: b.ModelsPackage, Doc: b.Doc} 127 resolver.KnownDefs = make(map[string]struct{}) 128 for k := range b.Doc.Spec().Definitions { 129 resolver.KnownDefs[k] = struct{}{} 130 } 131 gO, err := b.MakeResponse("a", "getTasksSuccess", true, resolver, 200, b.Operation.Responses.StatusCodeResponses[200]) 132 require.NoError(t, err) 133 134 assert.Len(t, gO.Headers, 6) 135 assert.NotNil(t, gO.Schema) 136 assert.True(t, gO.Schema.IsArray) 137 assert.NotNil(t, gO.Schema.Items) 138 assert.False(t, gO.Schema.IsAnonymous) 139 assert.Equal(t, "[]*models.Task", gO.Schema.GoType) 140 } 141 142 func TestMakeResponse_WithAllOfSchema(t *testing.T) { 143 b, err := methodPathOpBuilder("get", "/media/search", "../fixtures/codegen/instagram.yml") 144 require.NoError(t, err) 145 146 resolver := &typeResolver{ModelsPackage: b.ModelsPackage, Doc: b.Doc} 147 resolver.KnownDefs = make(map[string]struct{}) 148 for k := range b.Doc.Spec().Definitions { 149 resolver.KnownDefs[k] = struct{}{} 150 } 151 gO, err := b.MakeResponse("a", "get /media/search", true, resolver, 200, b.Operation.Responses.StatusCodeResponses[200]) 152 require.NoError(t, err) 153 154 require.NotNil(t, gO.Schema) 155 assert.Equal(t, "GetMediaSearchBody", gO.Schema.GoType) 156 157 require.NotEmpty(t, b.ExtraSchemas) 158 body := b.ExtraSchemas["GetMediaSearchBody"] 159 require.NotEmpty(t, body.Properties) 160 161 prop := body.Properties[0] 162 assert.Equal(t, "data", prop.Name) 163 // is in models only when definition is flattened: otherwise, ExtraSchema is rendered in operations package 164 assert.Equal(t, "[]*GetMediaSearchBodyDataItems0", prop.GoType) 165 166 items := b.ExtraSchemas["GetMediaSearchBodyDataItems0"] 167 require.NotEmpty(t, items.AllOf) 168 169 media := items.AllOf[0] 170 // expect #definitions/media to be captured and reused by ExtraSchema 171 assert.Equal(t, "models.Media", media.GoType) 172 } 173 174 func TestMakeOperationParam(t *testing.T) { 175 b, err := opBuilder("getTasks", "") 176 require.NoError(t, err) 177 178 resolver := &typeResolver{ModelsPackage: b.ModelsPackage, Doc: b.Doc} 179 gO, err := b.MakeParameter("a", resolver, b.Operation.Parameters[0], nil) 180 require.NoError(t, err) 181 182 assert.Equal(t, "size", gO.Name) 183 assert.True(t, gO.IsPrimitive) 184 } 185 186 func TestMakeOperationParamItem(t *testing.T) { 187 b, err := opBuilder("arrayQueryParams", "../fixtures/codegen/todolist.arrayquery.yml") 188 require.NoError(t, err) 189 resolver := &typeResolver{ModelsPackage: b.ModelsPackage, Doc: b.Doc} 190 gO, err := b.MakeParameterItem("a", "siString", "ii", "siString", "a.SiString", "query", resolver, b.Operation.Parameters[1].Items, nil) 191 require.NoError(t, err) 192 assert.Nil(t, gO.Parent) 193 assert.True(t, gO.IsPrimitive) 194 } 195 196 func TestMakeOperation(t *testing.T) { 197 b, err := opBuilder("getTasks", "") 198 require.NoError(t, err) 199 gO, err := b.MakeOperation() 200 require.NoError(t, err) 201 assert.Equal(t, "getTasks", gO.Name) 202 assert.Equal(t, "GET", gO.Method) 203 assert.Equal(t, "/tasks", gO.Path) 204 assert.Len(t, gO.Params, 2) 205 assert.Len(t, gO.Responses, 1) 206 assert.NotNil(t, gO.DefaultResponse) 207 assert.NotNil(t, gO.SuccessResponse) 208 } 209 210 func TestRenderOperation_InstagramSearch(t *testing.T) { 211 defer discardOutput()() 212 213 b, err := methodPathOpBuilder("get", "/media/search", "../fixtures/codegen/instagram.yml") 214 require.NoError(t, err) 215 216 gO, err := b.MakeOperation() 217 require.NoError(t, err) 218 219 buf := bytes.NewBuffer(nil) 220 opt := opts() 221 222 require.NoError(t, opt.templates.MustGet("serverOperation").Execute(buf, gO)) 223 224 ff, err := opt.LanguageOpts.FormatContent("operation.go", buf.Bytes()) 225 require.NoErrorf(t, err, buf.String()) 226 227 res := string(ff) 228 assertInCode(t, "type GetMediaSearchOKBody struct {", res) 229 // codegen does not assumes objects are only in models 230 // this is inlined 231 assertInCode(t, "Data []*GetMediaSearchOKBodyDataItems0 `json:\"data\"`", res) 232 assertInCode(t, "type GetMediaSearchOKBodyDataItems0 struct {", res) 233 // this is a definition: expect this definition to be reused from the models pkg 234 assertInCode(t, "models.Media", res) 235 236 buf = bytes.NewBuffer(nil) 237 require.NoError(t, opt.templates.MustGet("serverResponses").Execute(buf, gO)) 238 239 ff, err = opt.LanguageOpts.FormatContent("response.go", buf.Bytes()) 240 require.NoErrorf(t, err, buf.String()) 241 242 res = string(ff) 243 // codegen does not assumes objects are only in models 244 assertInCode(t, "type GetMediaSearchOK struct {", res) 245 assertInCode(t, "GetMediaSearchOKBody", res) 246 247 b, err = methodPathOpBuilderWithFlatten("get", "/media/search", "../fixtures/codegen/instagram.yml") 248 require.NoError(t, err) 249 250 gO, err = b.MakeOperation() 251 require.NoError(t, err) 252 253 buf = bytes.NewBuffer(nil) 254 opt = opts() 255 require.NoError(t, opt.templates.MustGet("serverOperation").Execute(buf, gO)) 256 257 ff, err = opt.LanguageOpts.FormatContent("operation.go", buf.Bytes()) 258 require.NoErrorf(t, err, buf.String()) 259 260 res = string(ff) 261 assertNotInCode(t, "DataItems0", res) 262 assertNotInCode(t, "models", res) 263 264 buf = bytes.NewBuffer(nil) 265 require.NoError(t, opt.templates.MustGet("serverResponses").Execute(buf, gO)) 266 267 ff, err = opt.LanguageOpts.FormatContent("operation.go", buf.Bytes()) 268 require.NoErrorf(t, err, buf.String()) 269 270 res = string(ff) 271 assertInCode(t, "Payload *models.GetMediaSearchOKBody", res) 272 } 273 274 func methodPathOpBuilder(method, path, fname string) (codeGenOpBuilder, error) { 275 defer discardOutput()() 276 277 if fname == "" { 278 fname = "../fixtures/codegen/todolist.simple.yml" 279 } 280 o := opts() 281 o.Spec = fname 282 specDoc, analyzed, err := o.analyzeSpec() 283 if err != nil { 284 return codeGenOpBuilder{}, err 285 } 286 op, ok := analyzed.OperationFor(method, path) 287 if !ok { 288 return codeGenOpBuilder{}, errors.New("No operation could be found for " + method + " " + path) 289 } 290 291 return codeGenOpBuilder{ 292 Name: method + " " + path, 293 Method: method, 294 Path: path, 295 APIPackage: "restapi", 296 ModelsPackage: "models", 297 Principal: "models.User", 298 Target: ".", 299 Operation: *op, 300 Doc: specDoc, 301 PristineDefs: specDoc.Pristine(), 302 Analyzed: analyzed, 303 Authed: false, 304 ExtraSchemas: make(map[string]GenSchema), 305 GenOpts: o, 306 }, nil 307 } 308 309 // methodPathOpBuilderWithFlatten prepares an operation build based on method and path, with spec full flattening 310 func methodPathOpBuilderWithFlatten(method, path, fname string) (codeGenOpBuilder, error) { 311 defer discardOutput()() 312 313 if fname == "" { 314 fname = "../fixtures/codegen/todolist.simple.yml" 315 } 316 317 o := opBuildGetOpts(fname, true, false) // flatten: true, minimal: false 318 o.Spec = fname 319 specDoc, analyzed, err := o.analyzeSpec() 320 if err != nil { 321 return codeGenOpBuilder{}, err 322 } 323 op, ok := analyzed.OperationFor(method, path) 324 if !ok { 325 return codeGenOpBuilder{}, errors.New("No operation could be found for " + method + " " + path) 326 } 327 328 return codeGenOpBuilder{ 329 Name: method + " " + path, 330 Method: method, 331 Path: path, 332 APIPackage: "restapi", 333 ModelsPackage: "models", 334 Principal: "models.User", 335 Target: ".", 336 Operation: *op, 337 Doc: specDoc, 338 Analyzed: analyzed, 339 Authed: false, 340 ExtraSchemas: make(map[string]GenSchema), 341 GenOpts: opts(), 342 }, nil 343 } 344 345 // opBuilderWithOpts prepares the making of an operation with spec flattening options 346 func opBuilderWithOpts(name, fname string, o *GenOpts) (codeGenOpBuilder, error) { 347 defer discardOutput()() 348 349 if fname == "" { 350 // default fixture 351 fname = "../fixtures/codegen/todolist.simple.yml" 352 } 353 354 o.Spec = fname 355 specDoc, analyzed, err := o.analyzeSpec() 356 if err != nil { 357 return codeGenOpBuilder{}, err 358 } 359 360 method, path, op, ok := analyzed.OperationForName(name) 361 if !ok { 362 return codeGenOpBuilder{}, errors.New("No operation could be found for " + name) 363 } 364 365 return codeGenOpBuilder{ 366 Name: name, 367 Method: method, 368 Path: path, 369 BasePath: specDoc.BasePath(), 370 APIPackage: "restapi", 371 ModelsPackage: "models", 372 Principal: "models.User", 373 Target: ".", 374 Operation: *op, 375 Doc: specDoc, 376 PristineDefs: specDoc.Pristine(), 377 Analyzed: analyzed, 378 Authed: false, 379 ExtraSchemas: make(map[string]GenSchema), 380 GenOpts: o, 381 }, nil 382 } 383 384 func opBuildGetOpts(specName string, withFlatten bool, withMinimalFlatten bool) (opts *GenOpts) { 385 opts = &GenOpts{} 386 opts.ValidateSpec = true 387 opts.FlattenOpts = &analysis.FlattenOpts{ 388 Expand: !withFlatten, 389 Minimal: withMinimalFlatten, 390 } 391 opts.Spec = specName 392 if err := opts.EnsureDefaults(); err != nil { 393 panic("Cannot initialize GenOpts") 394 } 395 return 396 } 397 398 // opBuilderWithFlatten prepares the making of an operation with spec full flattening prior to rendering 399 func opBuilderWithFlatten(name, fname string) (codeGenOpBuilder, error) { 400 o := opBuildGetOpts(fname, true, false) // flatten: true, minimal: false 401 return opBuilderWithOpts(name, fname, o) 402 } 403 404 /* 405 // opBuilderWithMinimalFlatten prepares the making of an operation with spec minimal flattening prior to rendering 406 func opBuilderWithMinimalFlatten(name, fname string) (codeGenOpBuilder, error) { 407 o := opBuildGetOpts(fname, true, true) // flatten: true, minimal: true 408 return opBuilderWithOpts(name, fname, o) 409 } 410 */ 411 412 // opBuilderWithExpand prepares the making of an operation with spec expansion prior to rendering 413 func opBuilderWithExpand(name, fname string) (codeGenOpBuilder, error) { 414 o := opBuildGetOpts(fname, false, false) // flatten: false => expand 415 return opBuilderWithOpts(name, fname, o) 416 } 417 418 // opBuilder prepares the making of an operation with spec minimal flattening (default for CLI) 419 func opBuilder(name, fname string) (codeGenOpBuilder, error) { 420 o := opBuildGetOpts(fname, true, true) // flatten:true, minimal: true 421 // some fixtures do not fully validate - skip this 422 o.ValidateSpec = false 423 return opBuilderWithOpts(name, fname, o) 424 } 425 426 func findResponseHeader(op *spec.Operation, code int, name string) *spec.Header { 427 resp := op.Responses.Default 428 if code > 0 { 429 bb, ok := op.Responses.StatusCodeResponses[code] 430 if ok { 431 resp = &bb 432 } 433 } 434 435 if resp == nil { 436 return nil 437 } 438 439 hdr, ok := resp.Headers[name] 440 if !ok { 441 return nil 442 } 443 444 return &hdr 445 } 446 447 func TestDateFormat_Spec1(t *testing.T) { 448 b, err := opBuilder("putTesting", "../fixtures/bugs/193/spec1.json") 449 require.NoError(t, err) 450 451 op, err := b.MakeOperation() 452 require.NoError(t, err) 453 454 buf := bytes.NewBuffer(nil) 455 opts := opts() 456 opts.defaultsEnsured = false 457 opts.IsClient = true 458 require.NoError(t, opts.EnsureDefaults()) 459 460 require.NoError(t, opts.templates.MustGet("clientParameter").Execute(buf, op)) 461 462 ff, err := opts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes()) 463 require.NoErrorf(t, err, buf.String()) 464 465 assertInCode(t, "frTestingThis.String()", string(ff)) 466 } 467 468 func TestDateFormat_Spec2(t *testing.T) { 469 b, err := opBuilder("putTesting", "../fixtures/bugs/193/spec2.json") 470 require.NoError(t, err) 471 472 op, err := b.MakeOperation() 473 require.NoError(t, err) 474 475 buf := bytes.NewBuffer(nil) 476 opts := opts() 477 opts.defaultsEnsured = false 478 opts.IsClient = true 479 require.NoError(t, opts.EnsureDefaults()) 480 481 require.NoError(t, opts.templates.MustGet("clientParameter").Execute(buf, op)) 482 483 ff, err := opts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes()) 484 require.NoErrorf(t, err, buf.String()) 485 486 res := string(ff) 487 assertInCode(t, "o.TestingThis != nil {", res) 488 assertInCode(t, "joinedTestingThis := o.bindParamTestingThis(reg)", res) 489 assertInCode(t, `if err := r.SetFormParam("testingThis", joinedTestingThis...); err != nil {`, res) 490 assertInCode(t, "func (o *PutTestingParams) bindParamTestingThis(formats strfmt.Registry) []string {", res) 491 assertInCode(t, "testingThisIR := o.TestingThis", res) 492 assertInCode(t, "var testingThisIC []string", res) 493 assertInCode(t, "for _, testingThisIIR := range testingThisIR {", res) 494 assertInCode(t, "testingThisIIV := testingThisIIR.String()", res) 495 assertInCode(t, "testingThisIC = append(testingThisIC, testingThisIIV)", res) 496 assertInCode(t, `testingThisIS := swag.JoinByFormat(testingThisIC, "")`, res) 497 assertInCode(t, "return testingThisIS", res) 498 } 499 500 func TestBuilder_Issue1703(t *testing.T) { 501 defer discardOutput()() 502 503 dr := testCwd(t) 504 505 opts := &GenOpts{ 506 GenOptsCommon: GenOptsCommon{ 507 Spec: filepath.FromSlash("../fixtures/codegen/existing-model.yml"), 508 IncludeModel: true, 509 IncludeHandler: true, 510 IncludeParameters: true, 511 IncludeResponses: true, 512 IncludeMain: true, 513 APIPackage: "restapi", 514 ModelPackage: "model", 515 ServerPackage: "server", 516 ClientPackage: "client", 517 Target: dr, 518 }, 519 } 520 require.NoError(t, opts.EnsureDefaults()) 521 522 appGen, err := newAppGenerator("x-go-type-import-bug", nil, nil, opts) 523 require.NoError(t, err) 524 525 op, err := appGen.makeCodegenApp() 526 require.NoError(t, err) 527 528 for _, o := range op.Operations { 529 buf := bytes.NewBuffer(nil) 530 require.NoError(t, opts.templates.MustGet("serverResponses").Execute(buf, o)) 531 532 ff, err := appGen.GenOpts.LanguageOpts.FormatContent("response.go", buf.Bytes()) 533 require.NoErrorf(t, err, buf.String()) 534 535 assertInCode(t, "jwk \"github.com/user/package\"", string(ff)) 536 } 537 } 538 539 func TestBuilder_Issue287(t *testing.T) { 540 defer discardOutput()() 541 542 dr := testCwd(t) 543 544 opts := &GenOpts{ 545 GenOptsCommon: GenOptsCommon{ 546 Spec: filepath.FromSlash("../fixtures/bugs/287/swagger.yml"), 547 IncludeModel: true, 548 IncludeHandler: true, 549 IncludeParameters: true, 550 IncludeResponses: true, 551 IncludeMain: true, 552 APIPackage: "restapi", 553 ModelPackage: "model", 554 ServerPackage: "server", 555 ClientPackage: "client", 556 Target: dr, 557 }, 558 } 559 require.NoError(t, opts.EnsureDefaults()) 560 561 appGen, err := newAppGenerator("plainTexter", nil, nil, opts) 562 require.NoError(t, err) 563 564 op, err := appGen.makeCodegenApp() 565 require.NoError(t, err) 566 567 buf := bytes.NewBuffer(nil) 568 require.NoError(t, opts.templates.MustGet("serverBuilder").Execute(buf, op)) 569 570 ff, err := appGen.GenOpts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes()) 571 require.NoErrorf(t, err, buf.String()) 572 573 assertInCode(t, "case \"text/plain\":", string(ff)) 574 } 575 576 func TestBuilder_Issue465(t *testing.T) { 577 defer discardOutput()() 578 579 dr := testCwd(t) 580 581 opts := &GenOpts{ 582 GenOptsCommon: GenOptsCommon{ 583 Spec: filepath.FromSlash("../fixtures/bugs/465/swagger.yml"), 584 IncludeModel: true, 585 IncludeHandler: true, 586 IncludeParameters: true, 587 IncludeResponses: true, 588 IncludeMain: true, 589 APIPackage: "restapi", 590 ModelPackage: "model", 591 ServerPackage: "server", 592 ClientPackage: "client", 593 Target: dr, 594 IsClient: true, 595 }, 596 } 597 require.NoError(t, opts.EnsureDefaults()) 598 599 appGen, err := newAppGenerator("plainTexter", nil, nil, opts) 600 require.NoError(t, err) 601 602 op, err := appGen.makeCodegenApp() 603 require.NoError(t, err) 604 605 buf := bytes.NewBuffer(nil) 606 require.NoError(t, opts.templates.MustGet("clientFacade").Execute(buf, op)) 607 608 ff, err := appGen.GenOpts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes()) 609 require.NoErrorf(t, err, buf.String()) 610 611 assertInCode(t, "/v1/fancyAPI", string(ff)) 612 } 613 614 func TestBuilder_Issue500(t *testing.T) { 615 defer discardOutput()() 616 617 dr := testCwd(t) 618 619 opts := &GenOpts{ 620 GenOptsCommon: GenOptsCommon{ 621 Spec: filepath.FromSlash("../fixtures/bugs/500/swagger.yml"), 622 IncludeModel: true, 623 IncludeHandler: true, 624 IncludeParameters: true, 625 IncludeResponses: true, 626 IncludeMain: true, 627 APIPackage: "restapi", 628 ModelPackage: "model", 629 ServerPackage: "server", 630 ClientPackage: "client", 631 Target: dr, 632 }, 633 } 634 require.NoError(t, opts.EnsureDefaults()) 635 636 appGen, err := newAppGenerator("multiTags", nil, nil, opts) 637 require.NoError(t, err) 638 639 op, err := appGen.makeCodegenApp() 640 require.NoError(t, err) 641 642 buf := bytes.NewBuffer(nil) 643 require.NoError(t, opts.templates.MustGet("serverBuilder").Execute(buf, op)) 644 645 ff, err := appGen.GenOpts.LanguageOpts.FormatContent("put_testing.go", buf.Bytes()) 646 require.NoErrorf(t, err, buf.String()) 647 648 res := string(ff) 649 assertNotInCode(t, `o.handlers["GET"]["/payment/{invoice_id}/payments/{payment_id}"] = NewGetPaymentByID(o.context, o.GetPaymentByIDHandler)`, res) 650 assertInCode(t, `o.handlers["GET"]["/payment/{invoice_id}/payments/{payment_id}"] = invoices.NewGetPaymentByID(o.context, o.InvoicesGetPaymentByIDHandler)`, res) 651 } 652 653 func TestGenClient_IllegalBOM(t *testing.T) { 654 b, err := methodPathOpBuilder("get", "/v3/attachments/{attachmentId}", "../fixtures/bugs/727/swagger.json") 655 require.NoError(t, err) 656 657 op, err := b.MakeOperation() 658 require.NoError(t, err) 659 660 buf := bytes.NewBuffer(nil) 661 opts := opts() 662 opts.defaultsEnsured = false 663 opts.IsClient = true 664 require.NoError(t, opts.EnsureDefaults()) 665 666 require.NoError(t, opts.templates.MustGet("clientResponse").Execute(buf, op)) 667 } 668 669 func TestGenClient_CustomFormatPath(t *testing.T) { 670 b, err := methodPathOpBuilder("get", "/mosaic/experimental/series/{SeriesId}/mosaics", "../fixtures/bugs/789/swagger.yml") 671 require.NoError(t, err) 672 673 op, err := b.MakeOperation() 674 require.NoError(t, err) 675 676 buf := bytes.NewBuffer(nil) 677 opts := opts() 678 opts.defaultsEnsured = false 679 opts.IsClient = true 680 require.NoError(t, opts.EnsureDefaults()) 681 682 require.NoError(t, opts.templates.MustGet("clientParameter").Execute(buf, op)) 683 684 assertInCode(t, `if err := r.SetPathParam("SeriesId", o.SeriesID.String()); err != nil`, buf.String()) 685 } 686 687 func TestGenClient_Issue733(t *testing.T) { 688 b, err := opBuilder("get_characters_character_id_mail_mail_id", "../fixtures/bugs/733/swagger.json") 689 require.NoError(t, err) 690 691 op, err := b.MakeOperation() 692 require.NoError(t, err) 693 694 buf := bytes.NewBuffer(nil) 695 opts := opts() 696 opts.defaultsEnsured = false 697 opts.IsClient = true 698 require.NoError(t, opts.EnsureDefaults()) 699 700 require.NoError(t, opts.templates.MustGet("clientResponse").Execute(buf, op)) 701 702 assertInCode(t, "Labels []*int64 `json:\"labels\"`", buf.String()) 703 } 704 705 func TestGenServerIssue890_ValidationTrueFlatteningTrue(t *testing.T) { 706 defer discardOutput()() 707 708 dr := testCwd(t) 709 710 opts := &GenOpts{ 711 GenOptsCommon: GenOptsCommon{ 712 Spec: filepath.FromSlash("../fixtures/bugs/890/swagger.yaml"), 713 IncludeModel: true, 714 IncludeHandler: true, 715 IncludeParameters: true, 716 IncludeResponses: true, 717 IncludeMain: true, 718 ValidateSpec: true, 719 APIPackage: "restapi", 720 ModelPackage: "model", 721 ServerPackage: "server", 722 ClientPackage: "client", 723 Target: dr, 724 IsClient: true, 725 }, 726 } 727 728 // Testing Server Generation 729 require.NoError(t, opts.EnsureDefaults()) 730 731 // Full flattening 732 opts.FlattenOpts.Expand = false 733 opts.FlattenOpts.Minimal = false 734 appGen, err := newAppGenerator("JsonRefOperation", nil, nil, opts) 735 require.NoError(t, err) 736 737 op, err := appGen.makeCodegenApp() 738 require.NoError(t, err) 739 740 buf := bytes.NewBuffer(nil) 741 require.NoError(t, opts.templates.MustGet("serverOperation").Execute(buf, op.Operations[0])) 742 743 filecontent, err := appGen.GenOpts.LanguageOpts.FormatContent("operation.go", buf.Bytes()) 744 require.NoErrorf(t, err, buf.String()) 745 746 res := string(filecontent) 747 assertInCode(t, "GetHealthCheck", res) 748 } 749 750 func TestGenClientIssue890_ValidationTrueFlatteningTrue(t *testing.T) { 751 defer discardOutput()() 752 753 dr := testCwd(t) 754 defer func() { 755 _ = os.RemoveAll(filepath.Join(filepath.FromSlash(dr), "restapi")) 756 }() 757 758 opts := testGenOpts() 759 opts.Spec = "../fixtures/bugs/890/swagger.yaml" 760 opts.ValidateSpec = true 761 opts.FlattenOpts.Minimal = false 762 763 // Testing this is enough as there is only one operation which is specified as $ref. 764 // If this doesn't get resolved then there will be an error definitely. 765 require.NoError(t, GenerateClient("foo", nil, nil, opts)) 766 } 767 768 func TestGenServerIssue890_ValidationFalseFlattenTrue(t *testing.T) { 769 defer discardOutput()() 770 771 dr := testCwd(t) 772 773 opts := &GenOpts{ 774 GenOptsCommon: GenOptsCommon{ 775 Spec: filepath.FromSlash("../fixtures/bugs/890/swagger.yaml"), 776 IncludeModel: true, 777 IncludeHandler: true, 778 IncludeParameters: true, 779 IncludeResponses: true, 780 IncludeMain: true, 781 APIPackage: "restapi", 782 ModelPackage: "model", 783 ServerPackage: "server", 784 ClientPackage: "client", 785 Target: dr, 786 IsClient: true, 787 }, 788 } 789 790 // Testing Server Generation 791 require.NoError(t, opts.EnsureDefaults()) 792 793 // full flattening 794 opts.FlattenOpts.Minimal = false 795 appGen, err := newAppGenerator("JsonRefOperation", nil, nil, opts) 796 require.NoError(t, err) 797 798 op, err := appGen.makeCodegenApp() 799 require.NoError(t, err) 800 801 buf := bytes.NewBuffer(nil) 802 err = opts.templates.MustGet("serverOperation").Execute(buf, op.Operations[0]) 803 require.NoError(t, err) 804 805 filecontent, err := appGen.GenOpts.LanguageOpts.FormatContent("operation.go", buf.Bytes()) 806 require.NoErrorf(t, err, buf.String()) 807 808 res := string(filecontent) 809 assertInCode(t, "GetHealthCheck", res) 810 } 811 812 func TestGenClientIssue890_ValidationFalseFlatteningTrue(t *testing.T) { 813 defer discardOutput()() 814 815 dr := testCwd(t) 816 defer func() { 817 _ = os.RemoveAll(filepath.Join(filepath.FromSlash(dr), "restapi")) 818 }() 819 820 opts := testGenOpts() 821 opts.Spec = "../fixtures/bugs/890/swagger.yaml" 822 opts.ValidateSpec = false 823 // full flattening 824 opts.FlattenOpts.Minimal = false 825 // Testing this is enough as there is only one operation which is specified as $ref. 826 // If this doesn't get resolved then there will be an error definitely. 827 require.NoError(t, GenerateClient("foo", nil, nil, opts)) 828 } 829 830 func TestGenServerIssue890_ValidationFalseFlattenFalse(t *testing.T) { 831 defer discardOutput()() 832 833 dr := testCwd(t) 834 835 opts := &GenOpts{ 836 GenOptsCommon: GenOptsCommon{ 837 Spec: filepath.FromSlash("../fixtures/bugs/890/swagger.yaml"), 838 IncludeModel: true, 839 IncludeHandler: true, 840 IncludeParameters: true, 841 IncludeResponses: true, 842 IncludeMain: true, 843 ValidateSpec: false, 844 APIPackage: "restapi", 845 ModelPackage: "model", 846 ServerPackage: "server", 847 ClientPackage: "client", 848 Target: dr, 849 IsClient: true, 850 }, 851 } 852 853 // Testing Server Generation 854 require.NoError(t, opts.EnsureDefaults()) 855 856 // minimal flattening 857 opts.FlattenOpts.Minimal = true 858 _, err := newAppGenerator("JsonRefOperation", nil, nil, opts) 859 // if flatten is not set, expand takes over so this would resume normally 860 require.NoError(t, err) 861 } 862 863 func TestGenClientIssue890_ValidationFalseFlattenFalse(t *testing.T) { 864 defer discardOutput()() 865 866 dr := testCwd(t) 867 defer func() { 868 _ = os.RemoveAll(filepath.Join(filepath.FromSlash(dr), "restapi")) 869 }() 870 871 opts := testGenOpts() 872 opts.Spec = "../fixtures/bugs/890/swagger.yaml" 873 opts.ValidateSpec = false 874 // minimal flattening 875 opts.FlattenOpts.Minimal = true 876 // Testing this is enough as there is only one operation which is specified as $ref. 877 // If this doesn't get resolved then there will be an error definitely. 878 // New: Now if flatten is false, expand takes over so server generation should resume normally 879 require.NoError(t, GenerateClient("foo", nil, nil, opts)) 880 } 881 882 func TestGenServerIssue890_ValidationTrueFlattenFalse(t *testing.T) { 883 defer discardOutput()() 884 885 dr := testCwd(t) 886 887 opts := &GenOpts{ 888 GenOptsCommon: GenOptsCommon{ 889 Spec: filepath.FromSlash("../fixtures/bugs/890/swagger.yaml"), 890 IncludeModel: true, 891 IncludeHandler: true, 892 IncludeParameters: true, 893 IncludeResponses: true, 894 IncludeMain: true, 895 ValidateSpec: true, 896 APIPackage: "restapi", 897 ModelPackage: "model", 898 ServerPackage: "server", 899 ClientPackage: "client", 900 Target: dr, 901 IsClient: true, 902 }, 903 } 904 905 // Testing Server Generation 906 require.NoError(t, opts.EnsureDefaults()) 907 908 // minimal flattening 909 opts.FlattenOpts.Minimal = true 910 911 _, err := newAppGenerator("JsonRefOperation", nil, nil, opts) 912 // now if flatten is false, expand takes over so server generation should resume normally 913 require.NoError(t, err) 914 } 915 916 func TestGenServerWithTemplate(t *testing.T) { 917 defer discardOutput()() 918 919 dr := testCwd(t) 920 921 tests := []struct { 922 name string 923 opts *GenOpts 924 wantError bool 925 }{ 926 { 927 name: "None_existing_contributor_template", 928 opts: &GenOpts{ 929 GenOptsCommon: GenOptsCommon{ 930 Spec: filepath.FromSlash("../fixtures/bugs/890/swagger.yaml"), 931 IncludeModel: true, 932 IncludeHandler: true, 933 IncludeParameters: true, 934 IncludeResponses: true, 935 IncludeMain: true, 936 ValidateSpec: true, 937 APIPackage: "restapi", 938 ModelPackage: "model", 939 ServerPackage: "server", 940 ClientPackage: "client", 941 Target: dr, 942 IsClient: true, 943 Template: "InvalidTemplate", 944 }, 945 }, 946 wantError: true, 947 }, 948 { 949 name: "Existing_contributor", 950 opts: &GenOpts{ 951 GenOptsCommon: GenOptsCommon{ 952 Spec: filepath.FromSlash("../fixtures/bugs/890/swagger.yaml"), 953 IncludeModel: true, 954 IncludeHandler: true, 955 IncludeParameters: true, 956 IncludeResponses: true, 957 IncludeMain: true, 958 ValidateSpec: true, 959 APIPackage: "restapi", 960 ModelPackage: "model", 961 ServerPackage: "server", 962 ClientPackage: "client", 963 Target: dr, 964 IsClient: true, 965 Template: "stratoscale", 966 }, 967 }, 968 wantError: false, 969 }, 970 } 971 972 t.Run("codegen operations", func(t *testing.T) { 973 for _, toPin := range tests { 974 tt := toPin 975 t.Run(tt.name, func(t *testing.T) { 976 t.Parallel() 977 978 // Testing Server Generation 979 require.NoError(t, tt.opts.EnsureDefaults()) 980 981 // minimal flattening 982 tt.opts.FlattenOpts.Minimal = true 983 _, err := newAppGenerator("JsonRefOperation", nil, nil, tt.opts) 984 if tt.wantError { 985 require.Error(t, err) 986 } else { 987 require.NoError(t, err) 988 } 989 }) 990 } 991 }) 992 } 993 994 func TestGenClientIssue890_ValidationTrueFlattenFalse(t *testing.T) { 995 defer discardOutput()() 996 997 dr := testCwd(t) 998 defer func() { 999 _ = os.RemoveAll(filepath.Join(filepath.FromSlash(dr), "restapi")) 1000 }() 1001 1002 opts := testGenOpts() 1003 opts.Spec = filepath.FromSlash("../fixtures/bugs/890/swagger.yaml") 1004 opts.ValidateSpec = true 1005 // Testing this is enough as there is only one operation which is specified as $ref. 1006 // If this doesn't get resolved then there will be an error definitely. 1007 // same here: now if flatten doesn't resume, expand takes over 1008 require.NoError(t, GenerateClient("foo", nil, nil, opts)) 1009 } 1010 1011 // This tests that securityDefinitions generate stable code 1012 func TestBuilder_Issue1214(t *testing.T) { 1013 defer discardOutput()() 1014 1015 dr := testCwd(t) 1016 const matchAny = `(.|\n)+` 1017 1018 opts := &GenOpts{ 1019 GenOptsCommon: GenOptsCommon{ 1020 Spec: filepath.FromSlash("../fixtures/bugs/1214/fixture-1214.yaml"), 1021 IncludeModel: true, 1022 IncludeHandler: true, 1023 IncludeParameters: true, 1024 IncludeResponses: true, 1025 IncludeMain: true, 1026 APIPackage: "restapi", 1027 ModelPackage: "model", 1028 ServerPackage: "server", 1029 ClientPackage: "client", 1030 Target: dr, 1031 IsClient: false, 1032 }, 1033 } 1034 require.NoError(t, opts.EnsureDefaults()) 1035 1036 appGen, e := newAppGenerator("fixture-1214", nil, nil, opts) 1037 require.NoError(t, e) 1038 1039 op, e := appGen.makeCodegenApp() 1040 require.NoError(t, e) 1041 1042 for i := 0; i < 5; i++ { 1043 buf := bytes.NewBuffer(nil) 1044 err := templates.MustGet("serverConfigureapi").Execute(buf, op) 1045 require.NoError(t, err) 1046 1047 ff, err := appGen.GenOpts.LanguageOpts.FormatContent("fixture_1214_configure_api.go", buf.Bytes()) 1048 require.NoErrorf(t, err, buf.String()) 1049 1050 res := string(ff) 1051 assertRegexpInCode(t, matchAny+ 1052 `api\.AAuth = func\(user string, pass string\)`+matchAny+ 1053 `api\.BAuth = func\(token string\)`+matchAny+ 1054 `api\.CAuth = func\(token string\)`+matchAny+ 1055 `api\.DAuth = func\(token string\)`+matchAny+ 1056 `api\.EAuth = func\(token string, scopes \[\]string\)`+matchAny, res) 1057 1058 buf = bytes.NewBuffer(nil) 1059 require.NoError(t, opts.templates.MustGet("serverBuilder").Execute(buf, op)) 1060 1061 ff, err = appGen.GenOpts.LanguageOpts.FormatContent("fixture_1214_server.go", buf.Bytes()) 1062 require.NoErrorf(t, err, buf.String()) 1063 1064 res = string(ff) 1065 assertRegexpInCode(t, matchAny+ 1066 `AAuth: func\(user string, pass string\) \(interface{}, error\) {`+matchAny+ 1067 `BAuth: func\(token string\) \(interface{}, error\) {`+matchAny+ 1068 `CAuth: func\(token string\) \(interface{}, error\) {`+matchAny+ 1069 `DAuth: func\(token string\) \(interface{}, error\) {`+matchAny+ 1070 `EAuth: func\(token string, scopes \[\]string\) \(interface{}, error\) {`+matchAny+ 1071 1072 `AAuth func\(string, string\) \(interface{}, error\)`+matchAny+ 1073 `BAuth func\(string\) \(interface{}, error\)`+matchAny+ 1074 `CAuth func\(string\) \(interface{}, error\)`+matchAny+ 1075 `DAuth func\(string\) \(interface{}, error\)`+matchAny+ 1076 `EAuth func\(string, \[\]string\) \(interface{}, error\)`+matchAny+ 1077 1078 `if o\.AAuth == nil {`+matchAny+ 1079 `unregistered = append\(unregistered, "AAuth"\)`+matchAny+ 1080 `if o\.BAuth == nil {`+matchAny+ 1081 `unregistered = append\(unregistered, "K1Auth"\)`+matchAny+ 1082 `if o\.CAuth == nil {`+matchAny+ 1083 `unregistered = append\(unregistered, "K2Auth"\)`+matchAny+ 1084 `if o\.DAuth == nil {`+matchAny+ 1085 `unregistered = append\(unregistered, "K3Auth"\)`+matchAny+ 1086 `if o\.EAuth == nil {`+matchAny+ 1087 `unregistered = append\(unregistered, "EAuth"\)`+matchAny+ 1088 1089 `case "A":`+matchAny+ 1090 `case "B":`+matchAny+ 1091 `case "C":`+matchAny+ 1092 `case "D":`+matchAny+ 1093 `case "E":`+matchAny, res) 1094 } 1095 } 1096 1097 func TestGenSecurityRequirements(t *testing.T) { 1098 for i := 0; i < 5; i++ { 1099 operation := "asecOp" 1100 b, err := opBuilder(operation, "../fixtures/bugs/1214/fixture-1214.yaml") 1101 require.NoError(t, err) 1102 1103 b.Security = b.Analyzed.SecurityRequirementsFor(&b.Operation) 1104 genRequirements := b.makeSecurityRequirements("o") 1105 assert.Len(t, genRequirements, 2) 1106 assert.Equal(t, []GenSecurityRequirements{ 1107 { 1108 GenSecurityRequirement{ 1109 Name: "A", 1110 Scopes: []string{}, 1111 }, 1112 GenSecurityRequirement{ 1113 Name: "B", 1114 Scopes: []string{}, 1115 }, 1116 GenSecurityRequirement{ 1117 Name: "E", 1118 Scopes: []string{"s0", "s1", "s2", "s3", "s4"}, 1119 }, 1120 }, 1121 { 1122 GenSecurityRequirement{ 1123 Name: "C", 1124 Scopes: []string{}, 1125 }, 1126 GenSecurityRequirement{ 1127 Name: "D", 1128 Scopes: []string{}, 1129 }, 1130 GenSecurityRequirement{ 1131 Name: "E", 1132 Scopes: []string{"s5", "s6", "s7", "s8", "s9"}, 1133 }, 1134 }, 1135 }, genRequirements) 1136 1137 operation = "bsecOp" 1138 b, err = opBuilder(operation, "../fixtures/bugs/1214/fixture-1214.yaml") 1139 require.NoError(t, err) 1140 1141 b.Security = b.Analyzed.SecurityRequirementsFor(&b.Operation) 1142 genRequirements = b.makeSecurityRequirements("o") 1143 assert.Len(t, genRequirements, 2) 1144 assert.Equal(t, []GenSecurityRequirements{ 1145 { 1146 GenSecurityRequirement{ 1147 Name: "A", 1148 Scopes: []string{}, 1149 }, 1150 GenSecurityRequirement{ 1151 Name: "E", 1152 Scopes: []string{"s0", "s1", "s2", "s3", "s4"}, 1153 }, 1154 }, 1155 { 1156 GenSecurityRequirement{ 1157 Name: "D", 1158 Scopes: []string{}, 1159 }, 1160 GenSecurityRequirement{ 1161 Name: "E", 1162 Scopes: []string{"s5", "s6", "s7", "s8", "s9"}, 1163 }, 1164 }, 1165 }, genRequirements) 1166 } 1167 1168 operation := "csecOp" 1169 b, err := opBuilder(operation, "../fixtures/bugs/1214/fixture-1214.yaml") 1170 require.NoError(t, err) 1171 1172 b.Security = b.Analyzed.SecurityRequirementsFor(&b.Operation) 1173 genRequirements := b.makeSecurityRequirements("o") 1174 assert.NotNil(t, genRequirements) 1175 assert.Empty(t, genRequirements) 1176 1177 operation = "nosecOp" 1178 b, err = opBuilder(operation, "../fixtures/bugs/1214/fixture-1214-2.yaml") 1179 require.NoError(t, err) 1180 1181 b.Security = b.Analyzed.SecurityRequirementsFor(&b.Operation) 1182 genRequirements = b.makeSecurityRequirements("o") 1183 assert.Nil(t, genRequirements) 1184 } 1185 1186 func TestGenerateServerOperation(t *testing.T) { 1187 defer discardOutput()() 1188 1189 fname := "../fixtures/codegen/todolist.simple.yml" 1190 1191 tgt, _ := os.MkdirTemp(filepath.Dir(fname), "generated") 1192 defer func() { 1193 _ = os.RemoveAll(tgt) 1194 }() 1195 o := &GenOpts{ 1196 GenOptsCommon: GenOptsCommon{ 1197 ValidateSpec: false, 1198 IncludeModel: true, 1199 IncludeHandler: true, 1200 IncludeParameters: true, 1201 IncludeResponses: true, 1202 ModelPackage: "models", 1203 Spec: fname, 1204 Target: tgt, 1205 }, 1206 } 1207 require.NoError(t, o.EnsureDefaults()) 1208 1209 require.Error(t, GenerateServerOperation([]string{"createTask"}, nil)) 1210 1211 d := o.TemplateDir 1212 o.TemplateDir = "./nowhere" 1213 require.Error(t, GenerateServerOperation([]string{"notFound"}, o)) 1214 1215 o.TemplateDir = d 1216 d = o.Spec 1217 o.Spec = "nowhere.yaml" 1218 require.Error(t, GenerateServerOperation([]string{"notFound"}, o)) 1219 1220 o.Spec = d 1221 require.Error(t, GenerateServerOperation([]string{"notFound"}, o)) 1222 1223 require.NoError(t, GenerateServerOperation([]string{"createTask"}, o)) 1224 1225 // check expected files are generated and that's it 1226 _, err := os.Stat(filepath.Join(tgt, "tasks", "create_task.go")) 1227 require.NoError(t, err) 1228 _, err = os.Stat(filepath.Join(tgt, "tasks", "create_task_parameters.go")) 1229 require.NoError(t, err) 1230 _, err = os.Stat(filepath.Join(tgt, "tasks", "create_task_responses.go")) 1231 require.NoError(t, err) 1232 1233 origStdout := os.Stdout 1234 defer func() { 1235 os.Stdout = origStdout 1236 }() 1237 os.Stdout, _ = os.Create(filepath.Join(tgt, "stdout")) 1238 o.DumpData = true 1239 // just checks this does not fail 1240 err = GenerateServerOperation([]string{"createTask"}, o) 1241 require.NoError(t, err) 1242 _, err = os.Stat(filepath.Join(tgt, "stdout")) 1243 require.NoError(t, err) 1244 } 1245 1246 // This tests that mimetypes generate stable code 1247 func TestBuilder_Issue1646(t *testing.T) { 1248 defer discardOutput()() 1249 1250 dr := testCwd(t) 1251 1252 opts := &GenOpts{ 1253 GenOptsCommon: GenOptsCommon{ 1254 Spec: filepath.FromSlash("../fixtures/bugs/1646/fixture-1646.yaml"), 1255 IncludeModel: true, 1256 IncludeHandler: true, 1257 IncludeParameters: true, 1258 IncludeResponses: true, 1259 IncludeMain: true, 1260 APIPackage: "restapi", 1261 ModelPackage: "model", 1262 ServerPackage: "server", 1263 ClientPackage: "client", 1264 Target: dr, 1265 IsClient: false, 1266 }, 1267 } 1268 err := opts.EnsureDefaults() 1269 require.NoError(t, err) 1270 appGen, err := newAppGenerator("fixture-1646", nil, nil, opts) 1271 require.NoError(t, err) 1272 1273 preCons, preConj := appGen.makeConsumes() 1274 preProds, preProdj := appGen.makeProduces() 1275 assert.True(t, preConj) 1276 assert.True(t, preProdj) 1277 for i := 0; i < 5; i++ { 1278 cons, conj := appGen.makeConsumes() 1279 prods, prodj := appGen.makeProduces() 1280 assert.True(t, conj) 1281 assert.True(t, prodj) 1282 assert.Equal(t, preConj, conj) 1283 assert.Equal(t, preProdj, prodj) 1284 assert.Equal(t, preCons, cons) 1285 assert.Equal(t, preProds, prods) 1286 } 1287 } 1288 1289 func TestGenServer_StrictAdditionalProperties(t *testing.T) { 1290 defer discardOutput()() 1291 1292 dr := testCwd(t) 1293 1294 opts := &GenOpts{ 1295 GenOptsCommon: GenOptsCommon{ 1296 Spec: filepath.FromSlash("../fixtures/codegen/strict-additional-properties.yml"), 1297 IncludeModel: true, 1298 IncludeHandler: true, 1299 IncludeParameters: true, 1300 IncludeResponses: true, 1301 IncludeMain: true, 1302 APIPackage: "restapi", 1303 ModelPackage: "model", 1304 ServerPackage: "server", 1305 ClientPackage: "client", 1306 Target: dr, 1307 IsClient: false, 1308 }, 1309 } 1310 err := opts.EnsureDefaults() 1311 require.NoError(t, err) 1312 1313 opts.StrictAdditionalProperties = true 1314 1315 appGen, err := newAppGenerator("StrictAdditionalProperties", nil, nil, opts) 1316 require.NoError(t, err) 1317 1318 op, err := appGen.makeCodegenApp() 1319 require.NoError(t, err) 1320 1321 buf := bytes.NewBuffer(nil) 1322 err = templates.MustGet("serverOperation").Execute(buf, op.Operations[0]) 1323 require.NoError(t, err) 1324 1325 ff, err := appGen.GenOpts.LanguageOpts.FormatContent("strictAdditionalProperties.go", buf.Bytes()) 1326 require.NoErrorf(t, err, buf.String()) 1327 1328 res := string(ff) 1329 for _, tt := range []struct { 1330 name string 1331 assertion func(testing.TB, string, string) bool 1332 }{ 1333 {"PostTestBody", assertInCode}, 1334 {"PostTestParamsBodyExplicit", assertInCode}, 1335 {"PostTestParamsBodyImplicit", assertInCode}, 1336 {"PostTestParamsBodyDisabled", assertNotInCode}, 1337 } { 1338 fn := funcBody(res, "*"+tt.name+") UnmarshalJSON(data []byte) error") 1339 require.NotEmpty(t, fn, "Method UnmarshalJSON should be defined for type *"+tt.name) 1340 tt.assertion(t, "dec.DisallowUnknownFields()", fn) 1341 } 1342 } 1343 1344 func makeClientTimeoutNameTest() []struct { 1345 seenIDs map[string]interface{} 1346 name string 1347 expected string 1348 } { 1349 return []struct { 1350 seenIDs map[string]interface{} 1351 name string 1352 expected string 1353 }{ 1354 { 1355 seenIDs: nil, 1356 name: "witness", 1357 expected: "witness", 1358 }, 1359 { 1360 seenIDs: map[string]interface{}{ 1361 "id": true, 1362 }, 1363 name: "timeout", 1364 expected: "timeout", 1365 }, 1366 { 1367 seenIDs: map[string]interface{}{ 1368 "timeout": true, 1369 "requesttimeout": true, 1370 }, 1371 name: "timeout", 1372 expected: "httpRequestTimeout", 1373 }, 1374 { 1375 seenIDs: map[string]interface{}{ 1376 "timeout": true, 1377 "requesttimeout": true, 1378 "httprequesttimeout": true, 1379 "swaggertimeout": true, 1380 "operationtimeout": true, 1381 "optimeout": true, 1382 }, 1383 name: "timeout", 1384 expected: "operTimeout", 1385 }, 1386 { 1387 seenIDs: map[string]interface{}{ 1388 "timeout": true, 1389 "requesttimeout": true, 1390 "httprequesttimeout": true, 1391 "swaggertimeout": true, 1392 "operationtimeout": true, 1393 "optimeout": true, 1394 "opertimeout": true, 1395 "opertimeout1": true, 1396 }, 1397 name: "timeout", 1398 expected: "operTimeout11", 1399 }, 1400 } 1401 } 1402 1403 func TestRenameTimeout(t *testing.T) { 1404 for idx, toPin := range makeClientTimeoutNameTest() { 1405 i := idx 1406 testCase := toPin 1407 t.Run(testCase.name, func(t *testing.T) { 1408 t.Parallel() 1409 assert.Equalf(t, testCase.expected, renameTimeout(testCase.seenIDs, testCase.name), "unexpected deconflicting value [%d]", i) 1410 }) 1411 } 1412 } 1413 1414 func testInvalidParams() map[string]spec.Parameter { 1415 return map[string]spec.Parameter{ 1416 "query#param1": *spec.QueryParam("param1"), 1417 "path#param1": *spec.PathParam("param1"), 1418 "body#param1": *spec.BodyParam("param1", &spec.Schema{}), 1419 } 1420 } 1421 1422 func TestParamMappings(t *testing.T) { 1423 // Test deconfliction of duplicate param names across param locations 1424 mappings, _ := paramMappings(testInvalidParams()) 1425 require.Contains(t, mappings, "query") 1426 require.Contains(t, mappings, "path") 1427 require.Contains(t, mappings, "body") 1428 q := mappings["query"] 1429 p := mappings["path"] 1430 b := mappings["body"] 1431 require.Len(t, q, 1) 1432 require.Len(t, p, 1) 1433 require.Len(t, b, 1) 1434 require.Containsf(t, q, "param1", "unexpected content of %#v", q) 1435 require.Containsf(t, p, "param1", "unexpected content of %#v", p) 1436 require.Containsf(t, b, "param1", "unexpected content of %#v", b) 1437 assert.Equalf(t, "QueryParam1", q["param1"], "unexpected content of %#v", q["param1"]) 1438 assert.Equalf(t, "PathParam1", p["param1"], "unexpected content of %#v", p["param1"]) 1439 assert.Equalf(t, "BodyParam1", b["param1"], "unexpected content of %#v", b["param1"]) 1440 } 1441 1442 func TestDeconflictTag(t *testing.T) { 1443 assert.Equal(t, "runtimeops", deconflictTag(nil, "runtime")) 1444 assert.Equal(t, "apiops", deconflictTag([]string{"tag1"}, "api")) 1445 assert.Equal(t, "apiops1", deconflictTag([]string{"tag1", "apiops"}, "api")) 1446 assert.Equal(t, "tlsops", deconflictTag([]string{"tag1"}, "tls")) 1447 assert.Equal(t, "mytag", deconflictTag([]string{"tag1", "apiops"}, "mytag")) 1448 1449 assert.Equal(t, "operationsops", renameOperationPackage([]string{"tag1"}, "operations")) 1450 assert.Equal(t, "operationsops11", renameOperationPackage([]string{"tag1", "operationsops1", "operationsops"}, "operations")) 1451 } 1452 1453 func TestGenServer_2161_panic(t *testing.T) { 1454 t.Parallel() 1455 defer discardOutput()() 1456 1457 generated, err := os.MkdirTemp(testCwd(t), "generated_2161") 1458 require.NoError(t, err) 1459 1460 defer func() { 1461 _ = os.RemoveAll(generated) 1462 }() 1463 1464 opts := &GenOpts{ 1465 GenOptsCommon: GenOptsCommon{ 1466 Spec: filepath.FromSlash("../fixtures/bugs/2161/fixture-2161-panic.json"), 1467 IncludeModel: true, 1468 IncludeHandler: true, 1469 IncludeParameters: true, 1470 IncludeResponses: true, 1471 IncludeMain: true, 1472 APIPackage: "restapi", 1473 ModelPackage: "model", 1474 ServerPackage: "server", 1475 ClientPackage: "client", 1476 Target: generated, 1477 IsClient: false, 1478 StrictAdditionalProperties: true, 1479 }, 1480 } 1481 require.NoError(t, opts.EnsureDefaults()) 1482 1483 appGen, err := newAppGenerator("inlinedSubtype", nil, nil, opts) 1484 require.NoError(t, err) 1485 1486 op, err := appGen.makeCodegenApp() 1487 require.NoError(t, err) 1488 1489 buf := bytes.NewBuffer(nil) 1490 var selectedOp int 1491 for i := range op.Operations { 1492 if op.Operations[i].Name == "configuration_update_configuration_module" { 1493 selectedOp = i 1494 } 1495 } 1496 require.NotEmpty(t, selectedOp, "dev error: invalid test vs fixture") 1497 1498 require.NoError(t, opts.templates.MustGet("serverOperation").Execute(buf, op.Operations[selectedOp])) 1499 1500 _, err = appGen.GenOpts.LanguageOpts.FormatContent(op.Operations[selectedOp].Name+".go", buf.Bytes()) 1501 require.NoErrorf(t, err, buf.String()) 1502 // NOTE(fred): I know that the generated model is wrong from this spec at the moment. 1503 // The test with this fix simply asserts that there is no panic / internal error with building this. 1504 } 1505 1506 func TestGenServer_1659_Principal(t *testing.T) { 1507 defer discardOutput()() 1508 1509 dr := testCwd(t) 1510 1511 for _, toPin := range []struct { 1512 Title string 1513 Opts *GenOpts 1514 Expected map[string][]string 1515 NotExpected map[string][]string 1516 }{ 1517 { 1518 Title: "default", 1519 Opts: &GenOpts{ 1520 GenOptsCommon: GenOptsCommon{ 1521 Spec: filepath.FromSlash("../fixtures/enhancements/1659/fixture-1659.yaml"), 1522 IncludeHandler: true, 1523 IncludeParameters: true, 1524 IncludeResponses: true, 1525 IncludeMain: false, 1526 APIPackage: "restapi", 1527 ModelPackage: "models", 1528 ServerPackage: "server", 1529 ClientPackage: "client", 1530 Target: dr, 1531 IsClient: false, 1532 }, 1533 }, 1534 Expected: map[string][]string{ 1535 "configure": { 1536 `if api.ApikeyAuth == nil {`, 1537 `api.ApikeyAuth = func(token string) (interface{}, error) {`, 1538 `if api.BasicAuth == nil {`, 1539 `api.BasicAuth = func(user string, pass string) (interface{}, error) {`, 1540 `if api.PetstoreAuthAuth == nil {`, 1541 `api.PetstoreAuthAuth = func(token string, scopes []string) (interface{}, error) {`, 1542 `api.GetHandler =restapi.GetHandlerFunc(func(params restapi.GetParams, principal interface{}) middleware.Responder {`, 1543 `api.PostHandler =restapi.PostHandlerFunc(func(params restapi.PostParams) middleware.Responder {`, 1544 }, 1545 "get": { 1546 `type GetHandlerFunc func(GetParams, interface{}) middleware.Responder`, 1547 `func (fn GetHandlerFunc) Handle(params GetParams, principal interface{}) middleware.Responder {`, 1548 `return fn(params, principal)`, 1549 `type GetHandler interface {`, 1550 `Handle(GetParams, interface{}) middleware.Responder`, 1551 `uprinc, aCtx, err := o.Context.Authorize(r, route)`, 1552 `if uprinc != nil {`, 1553 `principal = uprinc.(interface{})`, 1554 `res := o.Handler.Handle(Params, principal)`, 1555 }, 1556 "post": { 1557 `type PostHandlerFunc func(PostParams) middleware.Responder`, 1558 `return fn(params)`, 1559 `type PostHandler interface {`, 1560 `Handle(PostParams) middleware.Responder`, 1561 `res := o.Handler.Handle(Params)`, 1562 }, 1563 }, 1564 NotExpected: map[string][]string{ 1565 "post": { 1566 `uprinc, aCtx, err := o.Context.Authorize(r, route)`, 1567 `principal = uprinc.(interface{})`, 1568 }, 1569 }, 1570 }, 1571 { 1572 Title: "principal is struct", 1573 Opts: &GenOpts{ 1574 GenOptsCommon: GenOptsCommon{ 1575 Spec: filepath.FromSlash("../fixtures/enhancements/1659/fixture-1659.yaml"), 1576 IncludeHandler: true, 1577 IncludeParameters: true, 1578 IncludeResponses: true, 1579 IncludeMain: false, 1580 APIPackage: "restapi", 1581 ModelPackage: "models", 1582 ServerPackage: "server", 1583 ClientPackage: "client", 1584 Target: dr, 1585 Principal: "github.com/example/security.Principal", 1586 IsClient: false, 1587 }, 1588 }, 1589 Expected: map[string][]string{ 1590 "configure": { 1591 `auth "github.com/example/security"`, 1592 `if api.ApikeyAuth == nil {`, 1593 `api.ApikeyAuth = func(token string) (*auth.Principal, error) {`, 1594 `if api.BasicAuth == nil {`, 1595 `api.BasicAuth = func(user string, pass string) (*auth.Principal, error) {`, 1596 `if api.PetstoreAuthAuth == nil {`, 1597 `api.PetstoreAuthAuth = func(token string, scopes []string) (*auth.Principal, error) {`, 1598 `api.GetHandler =restapi.GetHandlerFunc(func(params restapi.GetParams, principal *auth.Principal) middleware.Responder {`, 1599 `api.PostHandler =restapi.PostHandlerFunc(func(params restapi.PostParams) middleware.Responder {`, 1600 }, 1601 "get": { 1602 `type GetHandlerFunc func(GetParams, *auth.Principal) middleware.Responder`, 1603 `func (fn GetHandlerFunc) Handle(params GetParams, principal *auth.Principal) middleware.Responder {`, 1604 `return fn(params, principal)`, 1605 `type GetHandler interface {`, 1606 `Handle(GetParams, *auth.Principal) middleware.Responder`, 1607 `uprinc, aCtx, err := o.Context.Authorize(r, route)`, 1608 `if uprinc != nil {`, 1609 `principal = uprinc.(*auth.Principal)`, 1610 `res := o.Handler.Handle(Params, principal)`, 1611 }, 1612 "post": { 1613 `type PostHandlerFunc func(PostParams) middleware.Responder`, 1614 `return fn(params)`, 1615 `type PostHandler interface {`, 1616 `Handle(PostParams) middleware.Responder`, 1617 `res := o.Handler.Handle(Params)`, 1618 }, 1619 }, 1620 NotExpected: map[string][]string{ 1621 "post": { 1622 `uprinc, aCtx, err := o.Context.Authorize(r, route)`, 1623 `principal = uprinc.(`, 1624 }, 1625 }, 1626 }, 1627 { 1628 Title: "principal is interface", 1629 Opts: &GenOpts{ 1630 GenOptsCommon: GenOptsCommon{ 1631 Spec: filepath.FromSlash("../fixtures/enhancements/1659/fixture-1659.yaml"), 1632 IncludeHandler: true, 1633 IncludeParameters: true, 1634 IncludeResponses: true, 1635 IncludeMain: false, 1636 APIPackage: "restapi", 1637 ModelPackage: "models", 1638 ServerPackage: "server", 1639 ClientPackage: "client", 1640 Target: dr, 1641 Principal: "github.com/example/security.PrincipalIface", 1642 IsClient: false, 1643 PrincipalCustomIface: true, 1644 }, 1645 }, 1646 Expected: map[string][]string{ 1647 "configure": { 1648 `auth "github.com/example/security"`, 1649 `if api.ApikeyAuth == nil {`, 1650 `api.ApikeyAuth = func(token string) (auth.PrincipalIface, error) {`, 1651 `if api.BasicAuth == nil {`, 1652 `api.BasicAuth = func(user string, pass string) (auth.PrincipalIface, error) {`, 1653 `if api.PetstoreAuthAuth == nil {`, 1654 `api.PetstoreAuthAuth = func(token string, scopes []string) (auth.PrincipalIface, error) {`, 1655 `api.GetHandler =restapi.GetHandlerFunc(func(params restapi.GetParams, principal auth.PrincipalIface) middleware.Responder {`, 1656 `api.PostHandler =restapi.PostHandlerFunc(func(params restapi.PostParams) middleware.Responder {`, 1657 }, 1658 "get": { 1659 `type GetHandlerFunc func(GetParams, auth.PrincipalIface) middleware.Responder`, 1660 `func (fn GetHandlerFunc) Handle(params GetParams, principal auth.PrincipalIface) middleware.Responder {`, 1661 `return fn(params, principal)`, 1662 `type GetHandler interface {`, 1663 `Handle(GetParams, auth.PrincipalIface) middleware.Responder`, 1664 `uprinc, aCtx, err := o.Context.Authorize(r, route)`, 1665 `if uprinc != nil {`, 1666 `principal = uprinc.(auth.PrincipalIface)`, 1667 `res := o.Handler.Handle(Params, principal)`, 1668 }, 1669 "post": { 1670 `type PostHandlerFunc func(PostParams) middleware.Responder`, 1671 `return fn(params)`, 1672 `type PostHandler interface {`, 1673 `Handle(PostParams) middleware.Responder`, 1674 `res := o.Handler.Handle(Params)`, 1675 }, 1676 }, 1677 NotExpected: map[string][]string{ 1678 "post": { 1679 `uprinc, aCtx, err := o.Context.Authorize(r, route)`, 1680 `principal = uprinc.(`, 1681 }, 1682 }, 1683 }, 1684 { 1685 Title: "stratoscale: principal is struct", 1686 Opts: &GenOpts{ 1687 GenOptsCommon: GenOptsCommon{ 1688 Spec: filepath.FromSlash("../fixtures/enhancements/1659/fixture-1659.yaml"), 1689 IncludeHandler: true, 1690 IncludeParameters: true, 1691 IncludeResponses: true, 1692 IncludeMain: false, 1693 APIPackage: "restapi", 1694 ModelPackage: "models", 1695 ServerPackage: "server", 1696 ClientPackage: "client", 1697 Target: dr, 1698 Principal: "github.com/example/security.Principal", 1699 IsClient: false, 1700 Template: "stratoscale", 1701 }, 1702 }, 1703 Expected: map[string][]string{ 1704 "configure": { 1705 `auth "github.com/example/security"`, 1706 `AuthApikey func(token string) (*auth.Principal, error)`, 1707 `AuthBasic func(user string, pass string) (*auth.Principal, error)`, 1708 `AuthPetstoreAuth func(token string, scopes []string) (*auth.Principal, error)`, 1709 `api.ApikeyAuth = func(token string) (*auth.Principal, error) {`, 1710 `if c.AuthApikey == nil {`, 1711 `panic("you specified a custom principal type, but did not provide the authenticator to provide this")`, 1712 `return c.AuthApikey(token)`, 1713 `api.BasicAuth = func(user string, pass string) (*auth.Principal, error) {`, 1714 `if c.AuthBasic == nil {`, 1715 `panic("you specified a custom principal type, but did not provide the authenticator to provide this")`, 1716 `return c.AuthBasic(user, pass)`, 1717 `api.PetstoreAuthAuth = func(token string, scopes []string) (*auth.Principal, error) {`, 1718 `if c.AuthPetstoreAuth == nil {`, 1719 `panic("you specified a custom principal type, but did not provide the authenticator to provide this")`, 1720 `return c.AuthPetstoreAuth(token, scopes)`, 1721 `api.APIAuthorizer = authorizer(c.Authorizer)`, 1722 `api.GetHandler =restapi.GetHandlerFunc(func(params restapi.GetParams, principal *auth.Principal) middleware.Responder {`, 1723 `ctx = storeAuth(ctx, principal)`, 1724 `return c.RestapiAPI.Get(ctx, params)`, 1725 `api.PostHandler =restapi.PostHandlerFunc(func(params restapi.PostParams) middleware.Responder {`, 1726 `return c.RestapiAPI.Post(ctx, params)`, 1727 `func (a authorizer) Authorize(req *http.Request, principal interface{}) error {`, 1728 `ctx := storeAuth(req.Context(), principal)`, 1729 `func storeAuth(ctx context.Context, principal interface{})`, 1730 }, 1731 }, 1732 }, 1733 { 1734 Title: "stratoscale: principal is interface", 1735 Opts: &GenOpts{ 1736 GenOptsCommon: GenOptsCommon{ 1737 Spec: filepath.FromSlash("../fixtures/enhancements/1659/fixture-1659.yaml"), 1738 IncludeHandler: true, 1739 IncludeParameters: true, 1740 IncludeResponses: true, 1741 IncludeMain: false, 1742 APIPackage: "restapi", 1743 ModelPackage: "models", 1744 ServerPackage: "server", 1745 ClientPackage: "client", 1746 Target: dr, 1747 Principal: "github.com/example/security.PrincipalIface", 1748 IsClient: false, 1749 PrincipalCustomIface: true, 1750 Template: "stratoscale", 1751 }, 1752 }, 1753 Expected: map[string][]string{ 1754 "configure": { 1755 `auth "github.com/example/security"`, 1756 `AuthApikey func(token string) (auth.PrincipalIface, error)`, 1757 `AuthBasic func(user string, pass string) (auth.PrincipalIface, error)`, 1758 `AuthPetstoreAuth func(token string, scopes []string) (auth.PrincipalIface, error)`, 1759 `api.ApikeyAuth = func(token string) (auth.PrincipalIface, error) {`, 1760 `if c.AuthApikey == nil {`, 1761 `panic("you specified a custom principal type, but did not provide the authenticator to provide this")`, 1762 `return c.AuthApikey(token)`, 1763 `api.BasicAuth = func(user string, pass string) (auth.PrincipalIface, error) {`, 1764 `if c.AuthBasic == nil {`, 1765 `panic("you specified a custom principal type, but did not provide the authenticator to provide this")`, 1766 `return c.AuthBasic(user, pass)`, 1767 `api.PetstoreAuthAuth = func(token string, scopes []string) (auth.PrincipalIface, error) {`, 1768 `if c.AuthPetstoreAuth == nil {`, 1769 `panic("you specified a custom principal type, but did not provide the authenticator to provide this")`, 1770 `return c.AuthPetstoreAuth(token, scopes)`, 1771 `api.APIAuthorizer = authorizer(c.Authorizer)`, 1772 `api.GetHandler =restapi.GetHandlerFunc(func(params restapi.GetParams, principal auth.PrincipalIface) middleware.Responder {`, 1773 `ctx = storeAuth(ctx, principal)`, 1774 `return c.RestapiAPI.Get(ctx, params)`, 1775 `api.PostHandler =restapi.PostHandlerFunc(func(params restapi.PostParams) middleware.Responder {`, 1776 `return c.RestapiAPI.Post(ctx, params)`, 1777 `func (a authorizer) Authorize(req *http.Request, principal interface{}) error {`, 1778 `ctx := storeAuth(req.Context(), principal)`, 1779 `func storeAuth(ctx context.Context, principal interface{})`, 1780 }, 1781 }, 1782 }, 1783 } { 1784 fixture := toPin 1785 t.Run(fixture.Title, func(t *testing.T) { 1786 t.Parallel() 1787 1788 opts := fixture.Opts 1789 require.NoError(t, opts.EnsureDefaults()) 1790 require.NoError(t, opts.setTemplates()) 1791 1792 appGen, err := newAppGenerator(fixture.Title, nil, nil, opts) 1793 require.NoError(t, err) 1794 1795 op, err := appGen.makeCodegenApp() 1796 require.NoError(t, err) 1797 1798 bufC := bytes.NewBuffer(nil) 1799 require.NoError(t, opts.templates.MustGet("serverConfigureapi").Execute(bufC, op)) 1800 1801 _, err = appGen.GenOpts.LanguageOpts.FormatContent("configure_api.go", bufC.Bytes()) 1802 require.NoErrorf(t, err, bufC.String()) 1803 1804 for _, line := range fixture.Expected["configure"] { 1805 assertInCode(t, line, bufC.String()) 1806 } 1807 for _, line := range fixture.NotExpected["configure"] { 1808 assertNotInCode(t, line, bufC.String()) 1809 } 1810 1811 for i := range op.Operations { 1812 bufO := bytes.NewBuffer(nil) 1813 require.NoError(t, opts.templates.MustGet("serverOperation").Execute(bufO, op.Operations[i])) 1814 1815 _, erf := appGen.GenOpts.LanguageOpts.FormatContent(op.Operations[i].Name+".go", bufO.Bytes()) 1816 require.NoErrorf(t, erf, bufO.String()) 1817 1818 for _, line := range fixture.Expected[op.Operations[i].Name] { 1819 assertInCode(t, line, bufO.String()) 1820 } 1821 for _, line := range fixture.NotExpected[op.Operations[i].Name] { 1822 assertNotInCode(t, line, bufO.String()) 1823 } 1824 } 1825 }) 1826 } 1827 }