github.com/goldeneggg/goa@v1.3.1/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 }}, DO NOT EDIT.
   289  //
   290  // API "test api": Application Contexts
   291  //
   292  // Command:
   293  // $ goagen
   294  // --out=$(GOPATH){{sep}}src{{sep}}{{.tmpDir}}
   295  // --design={{.design}}
   296  // --version={{.version}}
   297  
   298  package app
   299  
   300  import (
   301  	"context"
   302  	"github.com/goadesign/goa"
   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  	if ctx.ResponseData.Header().Get("Content-Type") == "" {
   334  		ctx.ResponseData.Header().Set("Content-Type", "application/vnd.rightscale.codegen.test.widgets")
   335  	}
   336  	return ctx.ResponseData.Service.Send(ctx.Context, 200, r)
   337  }
   338  `
   339  
   340  const controllersCodeTmpl = `// Code generated by goagen {{ .version }}, DO NOT EDIT.
   341  //
   342  // API "test api": Application Controllers
   343  //
   344  // Command:
   345  // $ goagen
   346  // --out=$(GOPATH){{sep}}src{{sep}}{{.tmpDir}}
   347  // --design={{.design}}
   348  // --version={{.version}}
   349  
   350  package app
   351  
   352  import (
   353  	"context"
   354  	"github.com/goadesign/goa"
   355  	"net/http"
   356  )
   357  
   358  // initService sets up the service encoders, decoders and mux.
   359  func initService(service *goa.Service) {
   360  	// Setup encoders and decoders
   361  
   362  	// Setup default encoder and decoder
   363  }
   364  
   365  // WidgetController is the controller interface for the Widget actions.
   366  type WidgetController interface {
   367  	goa.Muxer
   368  	Get(*GetWidgetContext) error
   369  }
   370  
   371  // MountWidgetController "mounts" a Widget resource controller on the given service.
   372  func MountWidgetController(service *goa.Service, ctrl WidgetController) {
   373  	initService(service)
   374  	var h goa.Handler
   375  
   376  	h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   377  		// Check if there was an error loading the request
   378  		if err := goa.ContextError(ctx); err != nil {
   379  			return err
   380  		}
   381  		// Build the context
   382  		rctx, err := NewGetWidgetContext(ctx, req, service)
   383  		if err != nil {
   384  			return err
   385  		}
   386  		return ctrl.Get(rctx)
   387  	}
   388  	service.Mux.Handle("GET", "/:id", ctrl.MuxHandler("get", h, nil))
   389  	service.LogInfo("mount", "ctrl", "Widget", "action", "Get", "route", "GET /:id")
   390  }
   391  `
   392  
   393  const hrefsCodeTmpl = `// Code generated by goagen {{.version}}, DO NOT EDIT.
   394  //
   395  // API "test api": Application Resource Href Factories
   396  //
   397  // Command:
   398  // $ goagen
   399  // --out=$(GOPATH){{sep}}src{{sep}}{{.tmpDir}}
   400  // --design={{.design}}
   401  // --version={{.version}}
   402  
   403  package app
   404  
   405  import (
   406  	"fmt"
   407  	"strings"
   408  )
   409  
   410  // WidgetHref returns the resource href.
   411  func WidgetHref(id interface{}) string {
   412  	paramid := strings.TrimLeftFunc(fmt.Sprintf("%v", id), func(r rune) bool { return r == '/' })
   413  	return fmt.Sprintf("/%v", paramid)
   414  }
   415  `
   416  
   417  const mediaTypesCodeTmpl = `// Code generated by goagen {{ .version }}, DO NOT EDIT.
   418  //
   419  // API "test api": Application Media Types
   420  //
   421  // Command:
   422  // $ goagen
   423  // --out=$(GOPATH){{sep}}src{{sep}}{{.tmpDir}}
   424  // --design={{.design}}
   425  // --version={{.version}}
   426  
   427  package app
   428  `
   429  
   430  const controllersSlicePayloadCode = `
   431  // MountWidgetController "mounts" a Widget resource controller on the given service.
   432  func MountWidgetController(service *goa.Service, ctrl WidgetController) {
   433  	initService(service)
   434  	var h goa.Handler
   435  
   436  	h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   437  		// Check if there was an error loading the request
   438  		if err := goa.ContextError(ctx); err != nil {
   439  			return err
   440  		}
   441  		// Build the context
   442  		rctx, err := NewGetWidgetContext(ctx, req, service)
   443  		if err != nil {
   444  			return err
   445  		}
   446  		// Build the payload
   447  		if rawPayload := goa.ContextRequest(ctx).Payload; rawPayload != nil {
   448  			rctx.Payload = rawPayload.(Collection)
   449  		} else {
   450  			return goa.MissingPayloadError()
   451  		}
   452  		return ctrl.Get(rctx)
   453  	}
   454  	service.Mux.Handle("GET", "/:id", ctrl.MuxHandler("get", h, unmarshalGetWidgetPayload))
   455  	service.LogInfo("mount", "ctrl", "Widget", "action", "Get", "route", "GET /:id")
   456  }
   457  
   458  // unmarshalGetWidgetPayload unmarshals the request body into the context request data Payload field.
   459  func unmarshalGetWidgetPayload(ctx context.Context, service *goa.Service, req *http.Request) error {
   460  	var payload Collection
   461  	if err := service.DecodeRequest(req, &payload); err != nil {
   462  		return err
   463  	}
   464  	goa.ContextRequest(ctx).Payload = payload
   465  	return nil
   466  }
   467  `
   468  
   469  const controllersOptionalPayloadCode = `
   470  // MountWidgetController "mounts" a Widget resource controller on the given service.
   471  func MountWidgetController(service *goa.Service, ctrl WidgetController) {
   472  	initService(service)
   473  	var h goa.Handler
   474  
   475  	h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   476  		// Check if there was an error loading the request
   477  		if err := goa.ContextError(ctx); err != nil {
   478  			return err
   479  		}
   480  		// Build the context
   481  		rctx, err := NewGetWidgetContext(ctx, req, service)
   482  		if err != nil {
   483  			return err
   484  		}
   485  		// Build the payload
   486  		if rawPayload := goa.ContextRequest(ctx).Payload; rawPayload != nil {
   487  			rctx.Payload = rawPayload.(Collection)
   488  		}
   489  		return ctrl.Get(rctx)
   490  	}
   491  	service.Mux.Handle("GET", "/:id", ctrl.MuxHandler("get", h, unmarshalGetWidgetPayload))
   492  	service.LogInfo("mount", "ctrl", "Widget", "action", "Get", "route", "GET /:id")
   493  }
   494  
   495  // unmarshalGetWidgetPayload unmarshals the request body into the context request data Payload field.
   496  func unmarshalGetWidgetPayload(ctx context.Context, service *goa.Service, req *http.Request) error {
   497  	var payload Collection
   498  	if err := service.DecodeRequest(req, &payload); err != nil {
   499  		return err
   500  	}
   501  	goa.ContextRequest(ctx).Payload = payload
   502  	return nil
   503  }
   504  `