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