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