github.com/furusax0621/goa-v1@v1.4.3/goagen/gen_app/generator_test.go (about) 1 package genapp_test 2 3 import ( 4 "bytes" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "strings" 9 "text/template" 10 11 "github.com/goadesign/goa/design" 12 "github.com/goadesign/goa/dslengine" 13 "github.com/goadesign/goa/goagen/codegen" 14 genapp "github.com/goadesign/goa/goagen/gen_app" 15 "github.com/goadesign/goa/version" 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18 ) 19 20 var _ = Describe("Generate", func() { 21 var workspace *codegen.Workspace 22 var outDir string 23 var files []string 24 var genErr error 25 26 BeforeEach(func() { 27 var err error 28 workspace, err = codegen.NewWorkspace("test") 29 Ω(err).ShouldNot(HaveOccurred()) 30 outDir, err = ioutil.TempDir(filepath.Join(workspace.Path, "src"), "") 31 Ω(err).ShouldNot(HaveOccurred()) 32 os.Args = []string{"goagen", "--out=" + outDir, "--design=foo", "--version=" + version.String()} 33 codegen.TempCount = 0 34 }) 35 36 JustBeforeEach(func() { 37 design.GeneratedMediaTypes = make(design.MediaTypeRoot) 38 design.ProjectedMediaTypes = make(design.MediaTypeRoot) 39 files, genErr = genapp.Generate() 40 }) 41 42 AfterEach(func() { 43 workspace.Delete() 44 delete(codegen.Reserved, "app") 45 }) 46 47 Context("with a dummy API", func() { 48 BeforeEach(func() { 49 design.Design = &design.APIDefinition{ 50 Name: "test api", 51 Title: "dummy API with no resource", 52 Description: "I told you it's dummy", 53 } 54 }) 55 56 It("generates correct empty files", func() { 57 Ω(genErr).Should(BeNil()) 58 Ω(files).Should(HaveLen(6)) 59 isEmptySource := func(filename string) { 60 contextsContent, err := ioutil.ReadFile(filepath.Join(outDir, "app", filename)) 61 Ω(err).ShouldNot(HaveOccurred()) 62 lines := strings.Split(string(contextsContent), "\n") 63 Ω(lines).ShouldNot(BeEmpty()) 64 Ω(len(lines)).Should(BeNumerically(">", 1)) 65 } 66 isEmptySource("contexts.go") 67 isEmptySource("controllers.go") 68 isEmptySource("hrefs.go") 69 isEmptySource("media_types.go") 70 }) 71 }) 72 73 Context("with a simple API", func() { 74 var contextsCode, controllersCode, hrefsCode, mediaTypesCode string 75 var payload *design.UserTypeDefinition 76 77 isSource := func(filename, content string) { 78 contextsContent, err := ioutil.ReadFile(filepath.Join(outDir, "app", filename)) 79 Ω(err).ShouldNot(HaveOccurred()) 80 Ω(string(contextsContent)).Should(Equal(content)) 81 } 82 83 funcs := template.FuncMap{ 84 "sep": func() string { return string(os.PathSeparator) }, 85 } 86 87 runCodeTemplates := func(data map[string]string) { 88 contextsCodeT, err := template.New("context").Funcs(funcs).Parse(contextsCodeTmpl) 89 Ω(err).ShouldNot(HaveOccurred()) 90 var b bytes.Buffer 91 err = contextsCodeT.Execute(&b, data) 92 Ω(err).ShouldNot(HaveOccurred()) 93 contextsCode = b.String() 94 95 controllersCodeT, err := template.New("controllers").Funcs(funcs).Parse(controllersCodeTmpl) 96 Ω(err).ShouldNot(HaveOccurred()) 97 b.Reset() 98 err = controllersCodeT.Execute(&b, data) 99 Ω(err).ShouldNot(HaveOccurred()) 100 controllersCode = b.String() 101 102 hrefsCodeT, err := template.New("hrefs").Funcs(funcs).Parse(hrefsCodeTmpl) 103 Ω(err).ShouldNot(HaveOccurred()) 104 b.Reset() 105 err = hrefsCodeT.Execute(&b, data) 106 Ω(err).ShouldNot(HaveOccurred()) 107 hrefsCode = b.String() 108 109 mediaTypesCodeT, err := template.New("media types").Funcs(funcs).Parse(mediaTypesCodeTmpl) 110 Ω(err).ShouldNot(HaveOccurred()) 111 b.Reset() 112 err = mediaTypesCodeT.Execute(&b, data) 113 Ω(err).ShouldNot(HaveOccurred()) 114 mediaTypesCode = b.String() 115 } 116 117 BeforeEach(func() { 118 payload = nil 119 required := &dslengine.ValidationDefinition{ 120 Required: []string{"id"}, 121 } 122 idAt := design.AttributeDefinition{ 123 Type: design.String, 124 Description: "widget id", 125 } 126 params := design.AttributeDefinition{ 127 Type: design.Object{ 128 "id": &idAt, 129 }, 130 Validation: required, 131 } 132 resp := design.ResponseDefinition{ 133 Name: "ok", 134 Status: 200, 135 Description: "get of widgets", 136 MediaType: "application/vnd.rightscale.codegen.test.widgets", 137 ViewName: "default", 138 } 139 route := design.RouteDefinition{ 140 Verb: "GET", 141 Path: "/:id", 142 } 143 at := design.AttributeDefinition{ 144 Type: design.String, 145 } 146 ut := design.UserTypeDefinition{ 147 AttributeDefinition: &at, 148 TypeName: "id", 149 } 150 res := design.ResourceDefinition{ 151 Name: "Widget", 152 BasePath: "/widgets", 153 Description: "Widgetty", 154 MediaType: "application/vnd.rightscale.codegen.test.widgets", 155 CanonicalActionName: "get", 156 } 157 get := design.ActionDefinition{ 158 Name: "get", 159 Description: "get widgets", 160 Parent: &res, 161 Routes: []*design.RouteDefinition{&route}, 162 Responses: map[string]*design.ResponseDefinition{"ok": &resp}, 163 Params: ¶ms, 164 Payload: payload, 165 } 166 res.Actions = map[string]*design.ActionDefinition{"get": &get} 167 mt := design.MediaTypeDefinition{ 168 UserTypeDefinition: &ut, 169 Identifier: "application/vnd.rightscale.codegen.test.widgets", 170 ContentType: "application/vnd.rightscale.codegen.test.widgets", 171 Views: map[string]*design.ViewDefinition{ 172 "default": { 173 AttributeDefinition: ut.AttributeDefinition, 174 Name: "default", 175 }, 176 }, 177 } 178 design.Design = &design.APIDefinition{ 179 Name: "test api", 180 Title: "dummy API with no resource", 181 Description: "I told you it's dummy", 182 Resources: map[string]*design.ResourceDefinition{"Widget": &res}, 183 MediaTypes: map[string]*design.MediaTypeDefinition{"application/vnd.rightscale.codegen.test.widgets": &mt}, 184 } 185 }) 186 187 Context("", func() { 188 BeforeEach(func() { 189 runCodeTemplates(map[string]string{"outDir": outDir, "design": "foo", "tmpDir": filepath.Base(outDir), "version": version.String()}) 190 }) 191 192 It("generates the corresponding code", func() { 193 Ω(genErr).Should(BeNil()) 194 Ω(files).Should(HaveLen(8)) 195 196 isSource("contexts.go", contextsCode) 197 isSource("controllers.go", controllersCode) 198 isSource("hrefs.go", hrefsCode) 199 isSource("media_types.go", mediaTypesCode) 200 }) 201 }) 202 203 Context("with a slice payload", func() { 204 BeforeEach(func() { 205 elemType := &design.AttributeDefinition{Type: design.Integer} 206 payload = &design.UserTypeDefinition{ 207 AttributeDefinition: &design.AttributeDefinition{ 208 Type: &design.Array{ElemType: elemType}, 209 }, 210 TypeName: "Collection", 211 } 212 design.Design.Resources["Widget"].Actions["get"].Payload = payload 213 runCodeTemplates(map[string]string{"outDir": outDir, "design": "foo", "tmpDir": filepath.Base(outDir), "version": version.String()}) 214 }) 215 216 It("generates the correct payload assignment code", func() { 217 Ω(genErr).Should(BeNil()) 218 219 contextsContent, err := ioutil.ReadFile(filepath.Join(outDir, "app", "controllers.go")) 220 Ω(err).ShouldNot(HaveOccurred()) 221 Ω(string(contextsContent)).Should(ContainSubstring(controllersSlicePayloadCode)) 222 }) 223 }) 224 225 Context("with a optional payload", func() { 226 BeforeEach(func() { 227 elemType := &design.AttributeDefinition{Type: design.Integer} 228 payload = &design.UserTypeDefinition{ 229 AttributeDefinition: &design.AttributeDefinition{ 230 Type: &design.Array{ElemType: elemType}, 231 }, 232 TypeName: "Collection", 233 } 234 design.Design.Resources["Widget"].Actions["get"].Payload = payload 235 design.Design.Resources["Widget"].Actions["get"].PayloadOptional = true 236 runCodeTemplates(map[string]string{"outDir": outDir, "design": "foo", "tmpDir": filepath.Base(outDir), "version": version.String()}) 237 }) 238 239 It("generates the no payloads assignment code", func() { 240 Ω(genErr).Should(BeNil()) 241 242 contextsContent, err := ioutil.ReadFile(filepath.Join(outDir, "app", "controllers.go")) 243 Ω(err).ShouldNot(HaveOccurred()) 244 Ω(string(contextsContent)).Should(ContainSubstring(controllersOptionalPayloadCode)) 245 }) 246 }) 247 248 Context("with a multipart payload", func() { 249 BeforeEach(func() { 250 elemTypeInt := &design.AttributeDefinition{Type: design.Integer} 251 elemTypeFile := &design.AttributeDefinition{Type: design.File} 252 payload = &design.UserTypeDefinition{ 253 AttributeDefinition: &design.AttributeDefinition{ 254 Type: design.Object{ 255 "int": elemTypeInt, 256 "file": elemTypeFile, 257 }, 258 }, 259 TypeName: "Collection", 260 } 261 design.Design.Resources["Widget"].Actions["get"].Payload = payload 262 design.Design.Resources["Widget"].Actions["get"].PayloadMultipart = true 263 runCodeTemplates(map[string]string{"outDir": outDir, "design": "foo", "tmpDir": filepath.Base(outDir), "version": version.String()}) 264 }) 265 266 It("generates the corresponding code", func() { 267 Ω(genErr).Should(BeNil()) 268 269 contextsContent, err := ioutil.ReadFile(filepath.Join(outDir, "app", "controllers.go")) 270 Ω(err).ShouldNot(HaveOccurred()) 271 Ω(string(contextsContent)).Should(ContainSubstring(controllersMultipartPayloadCode)) 272 }) 273 }) 274 }) 275 }) 276 277 var _ = Describe("NewGenerator", func() { 278 var generator *genapp.Generator 279 280 var args = struct { 281 api *design.APIDefinition 282 outDir string 283 target string 284 noTest bool 285 }{ 286 api: &design.APIDefinition{ 287 Name: "test api", 288 }, 289 target: "app", 290 noTest: true, 291 } 292 293 Context("with options all options set", func() { 294 BeforeEach(func() { 295 296 generator = genapp.NewGenerator( 297 genapp.API(args.api), 298 genapp.OutDir(args.outDir), 299 genapp.Target(args.target), 300 genapp.NoTest(args.noTest), 301 ) 302 }) 303 304 It("has all public properties set with expected value", func() { 305 Ω(generator).ShouldNot(BeNil()) 306 Ω(generator.API.Name).Should(Equal(args.api.Name)) 307 Ω(generator.OutDir).Should(Equal(args.outDir)) 308 Ω(generator.Target).Should(Equal(args.target)) 309 Ω(generator.NoTest).Should(Equal(args.noTest)) 310 }) 311 312 }) 313 }) 314 315 const contextsCodeTmpl = `// Code generated by goagen {{ .version }}, DO NOT EDIT. 316 // 317 // API "test api": Application Contexts 318 // 319 // Command: 320 // $ goagen 321 // --out=$(GOPATH){{sep}}src{{sep}}{{.tmpDir}} 322 // --design={{.design}} 323 // --version={{.version}} 324 325 package app 326 327 import ( 328 "context" 329 "github.com/goadesign/goa" 330 "net/http" 331 ) 332 333 // GetWidgetContext provides the Widget get action context. 334 type GetWidgetContext struct { 335 context.Context 336 *goa.ResponseData 337 *goa.RequestData 338 ID string 339 } 340 341 // NewGetWidgetContext parses the incoming request URL and body, performs validations and creates the 342 // context used by the Widget controller get action. 343 func NewGetWidgetContext(ctx context.Context, r *http.Request, service *goa.Service) (*GetWidgetContext, error) { 344 var err error 345 resp := goa.ContextResponse(ctx) 346 resp.Service = service 347 req := goa.ContextRequest(ctx) 348 req.Request = r 349 rctx := GetWidgetContext{Context: ctx, ResponseData: resp, RequestData: req} 350 paramID := req.Params["id"] 351 if len(paramID) > 0 { 352 rawID := paramID[0] 353 rctx.ID = rawID 354 } 355 return &rctx, err 356 } 357 358 // OK sends a HTTP response with status code 200. 359 func (ctx *GetWidgetContext) OK(r ID) error { 360 if ctx.ResponseData.Header().Get("Content-Type") == "" { 361 ctx.ResponseData.Header().Set("Content-Type", "application/vnd.rightscale.codegen.test.widgets") 362 } 363 return ctx.ResponseData.Service.Send(ctx.Context, 200, r) 364 } 365 ` 366 367 const controllersCodeTmpl = `// Code generated by goagen {{ .version }}, DO NOT EDIT. 368 // 369 // API "test api": Application Controllers 370 // 371 // Command: 372 // $ goagen 373 // --out=$(GOPATH){{sep}}src{{sep}}{{.tmpDir}} 374 // --design={{.design}} 375 // --version={{.version}} 376 377 package app 378 379 import ( 380 "context" 381 "github.com/goadesign/goa" 382 "net/http" 383 ) 384 385 // initService sets up the service encoders, decoders and mux. 386 func initService(service *goa.Service) { 387 // Setup encoders and decoders 388 389 // Setup default encoder and decoder 390 } 391 392 // WidgetController is the controller interface for the Widget actions. 393 type WidgetController interface { 394 goa.Muxer 395 Get(*GetWidgetContext) error 396 } 397 398 // MountWidgetController "mounts" a Widget resource controller on the given service. 399 func MountWidgetController(service *goa.Service, ctrl WidgetController) { 400 initService(service) 401 var h goa.Handler 402 403 h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 404 // Check if there was an error loading the request 405 if err := goa.ContextError(ctx); err != nil { 406 return err 407 } 408 // Build the context 409 rctx, err := NewGetWidgetContext(ctx, req, service) 410 if err != nil { 411 return err 412 } 413 return ctrl.Get(rctx) 414 } 415 service.Mux.Handle("GET", "/:id", ctrl.MuxHandler("get", h, nil)) 416 service.LogInfo("mount", "ctrl", "Widget", "action", "Get", "route", "GET /:id") 417 } 418 ` 419 420 const hrefsCodeTmpl = `// Code generated by goagen {{.version}}, DO NOT EDIT. 421 // 422 // API "test api": Application Resource Href Factories 423 // 424 // Command: 425 // $ goagen 426 // --out=$(GOPATH){{sep}}src{{sep}}{{.tmpDir}} 427 // --design={{.design}} 428 // --version={{.version}} 429 430 package app 431 432 import ( 433 "fmt" 434 "strings" 435 ) 436 437 // WidgetHref returns the resource href. 438 func WidgetHref(id interface{}) string { 439 paramid := strings.TrimLeftFunc(fmt.Sprintf("%v", id), func(r rune) bool { return r == '/' }) 440 return fmt.Sprintf("/%v", paramid) 441 } 442 ` 443 444 const mediaTypesCodeTmpl = `// Code generated by goagen {{ .version }}, DO NOT EDIT. 445 // 446 // API "test api": Application Media Types 447 // 448 // Command: 449 // $ goagen 450 // --out=$(GOPATH){{sep}}src{{sep}}{{.tmpDir}} 451 // --design={{.design}} 452 // --version={{.version}} 453 454 package app 455 ` 456 457 const controllersSlicePayloadCode = ` 458 // MountWidgetController "mounts" a Widget resource controller on the given service. 459 func MountWidgetController(service *goa.Service, ctrl WidgetController) { 460 initService(service) 461 var h goa.Handler 462 463 h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 464 // Check if there was an error loading the request 465 if err := goa.ContextError(ctx); err != nil { 466 return err 467 } 468 // Build the context 469 rctx, err := NewGetWidgetContext(ctx, req, service) 470 if err != nil { 471 return err 472 } 473 // Build the payload 474 if rawPayload := goa.ContextRequest(ctx).Payload; rawPayload != nil { 475 rctx.Payload = rawPayload.(Collection) 476 } else { 477 return goa.MissingPayloadError() 478 } 479 return ctrl.Get(rctx) 480 } 481 service.Mux.Handle("GET", "/:id", ctrl.MuxHandler("get", h, unmarshalGetWidgetPayload)) 482 service.LogInfo("mount", "ctrl", "Widget", "action", "Get", "route", "GET /:id") 483 } 484 485 // unmarshalGetWidgetPayload unmarshals the request body into the context request data Payload field. 486 func unmarshalGetWidgetPayload(ctx context.Context, service *goa.Service, req *http.Request) error { 487 var payload Collection 488 if err := service.DecodeRequest(req, &payload); err != nil { 489 return err 490 } 491 goa.ContextRequest(ctx).Payload = payload 492 return nil 493 } 494 ` 495 496 const controllersOptionalPayloadCode = ` 497 // MountWidgetController "mounts" a Widget resource controller on the given service. 498 func MountWidgetController(service *goa.Service, ctrl WidgetController) { 499 initService(service) 500 var h goa.Handler 501 502 h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 503 // Check if there was an error loading the request 504 if err := goa.ContextError(ctx); err != nil { 505 return err 506 } 507 // Build the context 508 rctx, err := NewGetWidgetContext(ctx, req, service) 509 if err != nil { 510 return err 511 } 512 // Build the payload 513 if rawPayload := goa.ContextRequest(ctx).Payload; rawPayload != nil { 514 rctx.Payload = rawPayload.(Collection) 515 } 516 return ctrl.Get(rctx) 517 } 518 service.Mux.Handle("GET", "/:id", ctrl.MuxHandler("get", h, unmarshalGetWidgetPayload)) 519 service.LogInfo("mount", "ctrl", "Widget", "action", "Get", "route", "GET /:id") 520 } 521 522 // unmarshalGetWidgetPayload unmarshals the request body into the context request data Payload field. 523 func unmarshalGetWidgetPayload(ctx context.Context, service *goa.Service, req *http.Request) error { 524 var payload Collection 525 if err := service.DecodeRequest(req, &payload); err != nil { 526 return err 527 } 528 goa.ContextRequest(ctx).Payload = payload 529 return nil 530 } 531 ` 532 533 const controllersMultipartPayloadCode = ` 534 // MountWidgetController "mounts" a Widget resource controller on the given service. 535 func MountWidgetController(service *goa.Service, ctrl WidgetController) { 536 initService(service) 537 var h goa.Handler 538 539 h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 540 // Check if there was an error loading the request 541 if err := goa.ContextError(ctx); err != nil { 542 return err 543 } 544 // Build the context 545 rctx, err := NewGetWidgetContext(ctx, req, service) 546 if err != nil { 547 return err 548 } 549 // Build the payload 550 if rawPayload := goa.ContextRequest(ctx).Payload; rawPayload != nil { 551 rctx.Payload = rawPayload.(*Collection) 552 } else { 553 return goa.MissingPayloadError() 554 } 555 return ctrl.Get(rctx) 556 } 557 service.Mux.Handle("GET", "/:id", ctrl.MuxHandler("get", h, unmarshalGetWidgetPayload)) 558 service.LogInfo("mount", "ctrl", "Widget", "action", "Get", "route", "GET /:id") 559 } 560 561 // unmarshalGetWidgetPayload unmarshals the request body into the context request data Payload field. 562 func unmarshalGetWidgetPayload(ctx context.Context, service *goa.Service, req *http.Request) error { 563 var err error 564 var payload collection 565 _, rawFile, err2 := req.FormFile("file") 566 if err2 == nil { 567 payload.File = rawFile 568 } else { 569 err = goa.MergeErrors(err, goa.InvalidParamTypeError("file", "file", "file")) 570 } 571 rawInt := req.FormValue("int") 572 if int_, err2 := strconv.Atoi(rawInt); err2 == nil { 573 tmp2 := int_ 574 tmp1 := &tmp2 575 payload.Int = tmp1 576 } else { 577 err = goa.MergeErrors(err, goa.InvalidParamTypeError("int", rawInt, "integer")) 578 } 579 if err != nil { 580 return err 581 } 582 goa.ContextRequest(ctx).Payload = payload.Publicize() 583 return nil 584 } 585 `