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