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:      &params,
   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  `