github.com/goldeneggg/goa@v1.3.1/design/definitions_test.go (about)

     1  package design_test
     2  
     3  import (
     4  	"path"
     5  
     6  	"github.com/goadesign/goa/design"
     7  	"github.com/goadesign/goa/dslengine"
     8  	. "github.com/onsi/ginkgo"
     9  	. "github.com/onsi/gomega"
    10  )
    11  
    12  var _ = Describe("Inherit", func() {
    13  	var child, parent *design.AttributeDefinition
    14  
    15  	BeforeEach(func() {
    16  		parent = &design.AttributeDefinition{Type: design.Object{}}
    17  		child = &design.AttributeDefinition{Type: design.Object{}}
    18  	})
    19  
    20  	JustBeforeEach(func() {
    21  		child.Inherit(parent)
    22  	})
    23  
    24  	Context("with a empty parent", func() {
    25  		const attName = "c"
    26  		BeforeEach(func() {
    27  			child.Type.(design.Object)[attName] = &design.AttributeDefinition{Type: design.String}
    28  		})
    29  
    30  		It("does not change", func() {
    31  			obj := child.Type.(design.Object)
    32  			Ω(obj).Should(HaveLen(1))
    33  			Ω(obj).Should(HaveKey(attName))
    34  		})
    35  	})
    36  
    37  	Context("with a parent that defines no inherited attribute", func() {
    38  		const (
    39  			attName = "c"
    40  			def     = "default"
    41  		)
    42  
    43  		BeforeEach(func() {
    44  			child.Type.(design.Object)[attName] = &design.AttributeDefinition{Type: design.String}
    45  			parent.Type.(design.Object)["other"] = &design.AttributeDefinition{Type: design.String, DefaultValue: def}
    46  		})
    47  
    48  		It("does not change", func() {
    49  			obj := child.Type.(design.Object)
    50  			Ω(obj).Should(HaveLen(1))
    51  			Ω(obj).Should(HaveKey(attName))
    52  			Ω(obj[attName].DefaultValue).Should(BeNil())
    53  		})
    54  	})
    55  
    56  	Context("with a parent that defines an inherited attribute", func() {
    57  		const (
    58  			attName = "c"
    59  			def     = "default"
    60  		)
    61  
    62  		BeforeEach(func() {
    63  			child.Type.(design.Object)[attName] = &design.AttributeDefinition{Type: design.String}
    64  			parent.Type.(design.Object)[attName] = &design.AttributeDefinition{Type: design.String, DefaultValue: def}
    65  		})
    66  
    67  		It("inherits the default value", func() {
    68  			obj := child.Type.(design.Object)
    69  			Ω(obj).Should(HaveLen(1))
    70  			Ω(obj).Should(HaveKey(attName))
    71  			Ω(obj[attName].DefaultValue).Should(Equal(def))
    72  		})
    73  	})
    74  
    75  	Context("with recursive type definitions", func() {
    76  		BeforeEach(func() {
    77  			po := design.Object{}
    78  			parent = &design.AttributeDefinition{Type: po}
    79  			child = &design.AttributeDefinition{Type: &design.UserTypeDefinition{AttributeDefinition: parent}}
    80  			po["recurse"] = child
    81  		})
    82  
    83  		It("does not recurse infinitely", func() {})
    84  	})
    85  
    86  })
    87  
    88  var _ = Describe("IsRequired", func() {
    89  	var required string
    90  	var attName string
    91  
    92  	var attribute *design.AttributeDefinition
    93  	var res bool
    94  
    95  	JustBeforeEach(func() {
    96  		integer := &design.AttributeDefinition{Type: design.Integer}
    97  		attribute = &design.AttributeDefinition{
    98  			Type:       design.Object{required: integer},
    99  			Validation: &dslengine.ValidationDefinition{Required: []string{required}},
   100  		}
   101  		res = attribute.IsRequired(attName)
   102  	})
   103  
   104  	Context("called on a required field", func() {
   105  		BeforeEach(func() {
   106  			attName = "required"
   107  			required = "required"
   108  		})
   109  
   110  		It("returns true", func() {
   111  			Ω(res).Should(BeTrue())
   112  		})
   113  	})
   114  
   115  	Context("called on a non-required field", func() {
   116  		BeforeEach(func() {
   117  			attName = "non-required"
   118  			required = "required"
   119  		})
   120  
   121  		It("returns false", func() {
   122  			Ω(res).Should(BeFalse())
   123  		})
   124  	})
   125  })
   126  
   127  var _ = Describe("IterateHeaders", func() {
   128  	It("works when Parent.Headers is nil", func() {
   129  		// create a Resource with no headers, Action with one header
   130  		resource := &design.ResourceDefinition{}
   131  		action := &design.ActionDefinition{
   132  			Parent: resource,
   133  			Headers: &design.AttributeDefinition{
   134  				Type: design.Object{
   135  					"a": &design.AttributeDefinition{Type: design.String},
   136  				},
   137  			},
   138  		}
   139  		names := []string{}
   140  		// iterator that collects header names
   141  		it := func(name string, _ bool, _ *design.AttributeDefinition) error {
   142  			names = append(names, name)
   143  			return nil
   144  		}
   145  		Ω(action.IterateHeaders(it)).Should(Succeed(), "despite action.Parent.Headers being nil")
   146  		Ω(names).Should(ConsistOf("a"))
   147  	})
   148  
   149  })
   150  var _ = Describe("Finalize ActionDefinition", func() {
   151  	Context("with an action with no response", func() {
   152  		var action *design.ActionDefinition
   153  
   154  		BeforeEach(func() {
   155  			// create a Resource with responses, Action with no response
   156  			resource := &design.ResourceDefinition{
   157  				Responses: map[string]*design.ResponseDefinition{
   158  					"NotFound": &design.ResponseDefinition{Name: "NotFound", Status: 404},
   159  				},
   160  			}
   161  			action = &design.ActionDefinition{Parent: resource}
   162  		})
   163  
   164  		It("does not panic and merges the resource responses", func() {
   165  			Ω(action.Finalize).ShouldNot(Panic())
   166  			Ω(action.Responses).Should(HaveKey("NotFound"))
   167  		})
   168  	})
   169  })
   170  
   171  var _ = Describe("FullPath", func() {
   172  
   173  	Context("Given a base resource and a resource with an action with a route", func() {
   174  		var resource, parentResource *design.ResourceDefinition
   175  		var action *design.ActionDefinition
   176  		var route *design.RouteDefinition
   177  
   178  		var actionPath string
   179  		var resourcePath string
   180  		var parentResourcePath string
   181  
   182  		JustBeforeEach(func() {
   183  			showAct := &design.ActionDefinition{}
   184  			showRoute := &design.RouteDefinition{
   185  				Path:   parentResourcePath,
   186  				Parent: showAct,
   187  			}
   188  			showAct.Routes = []*design.RouteDefinition{showRoute}
   189  			parentResource = &design.ResourceDefinition{}
   190  			parentResource.Actions = map[string]*design.ActionDefinition{"show": showAct}
   191  			parentResource.Name = "foo"
   192  			design.Design.Resources = map[string]*design.ResourceDefinition{"foo": parentResource}
   193  			showAct.Parent = parentResource
   194  
   195  			action = &design.ActionDefinition{}
   196  			route = &design.RouteDefinition{
   197  				Path:   actionPath,
   198  				Parent: action,
   199  			}
   200  			action.Routes = []*design.RouteDefinition{route}
   201  			resource = &design.ResourceDefinition{}
   202  			resource.Actions = map[string]*design.ActionDefinition{"action": action}
   203  			resource.BasePath = resourcePath
   204  			resource.ParentName = parentResource.Name
   205  			action.Parent = resource
   206  		})
   207  
   208  		AfterEach(func() {
   209  			design.Design.Resources = nil
   210  		})
   211  
   212  		Context("with relative routes", func() {
   213  			BeforeEach(func() {
   214  				actionPath = "/action"
   215  				resourcePath = "/resource"
   216  				parentResourcePath = "/parent"
   217  			})
   218  
   219  			It("FullPath concatenates them", func() {
   220  				Ω(route.FullPath()).Should(Equal(path.Join(parentResourcePath, resourcePath, actionPath)))
   221  			})
   222  
   223  			Context("with an action with absolute route", func() {
   224  				BeforeEach(func() {
   225  					actionPath = "//action"
   226  				})
   227  
   228  				It("FullPath uses it", func() {
   229  					Ω(route.FullPath()).Should(Equal(actionPath[1:]))
   230  				})
   231  			})
   232  
   233  			Context("with n resource with absolute route", func() {
   234  				BeforeEach(func() {
   235  					resourcePath = "//resource"
   236  				})
   237  
   238  				It("FullPath uses it", func() {
   239  					Ω(route.FullPath()).Should(Equal(resourcePath[1:] + "/" + actionPath[1:]))
   240  				})
   241  			})
   242  		})
   243  
   244  		Context("with trailing slashes", func() {
   245  			BeforeEach(func() {
   246  				actionPath = "/action/"
   247  				resourcePath = "/resource"
   248  				parentResourcePath = "/parent"
   249  			})
   250  
   251  			It("Keeps trailing slashes", func() {
   252  				Ω(route.FullPath()).Should(Equal("/parent/resource/action/"))
   253  			})
   254  		})
   255  	})
   256  })
   257  
   258  var _ = Describe("AllParams", func() {
   259  	Context("Given a resource with a parent and an action with a route", func() {
   260  		var (
   261  			resource, parent *design.ResourceDefinition
   262  			action           *design.ActionDefinition
   263  			allParams        design.Object
   264  			pathParams       design.Object
   265  		)
   266  
   267  		BeforeEach(func() {
   268  			// Parent resource
   269  			{
   270  				baseParams := &design.AttributeDefinition{Type: design.Object{
   271  					"pbasepath":  &design.AttributeDefinition{Type: design.String},
   272  					"pbasequery": &design.AttributeDefinition{Type: design.String},
   273  				}}
   274  				parent = &design.ResourceDefinition{
   275  					Name:                "parent",
   276  					CanonicalActionName: "canonical",
   277  					BasePath:            "/:pbasepath",
   278  					Params:              baseParams,
   279  				}
   280  				canParams := &design.AttributeDefinition{Type: design.Object{
   281  					"canpath":  &design.AttributeDefinition{Type: design.String},
   282  					"canquery": &design.AttributeDefinition{Type: design.String},
   283  				}}
   284  				canonical := &design.ActionDefinition{
   285  					Name:   "canonical",
   286  					Parent: parent,
   287  					Params: canParams,
   288  				}
   289  				croute := &design.RouteDefinition{
   290  					Path:   "/:canpath",
   291  					Parent: canonical,
   292  				}
   293  				canonical.Routes = []*design.RouteDefinition{croute}
   294  				parent.Actions = map[string]*design.ActionDefinition{"canonical": canonical}
   295  			}
   296  
   297  			// Resource
   298  			{
   299  				baseParams := &design.AttributeDefinition{Type: design.Object{
   300  					"basepath":  &design.AttributeDefinition{Type: design.String},
   301  					"basequery": &design.AttributeDefinition{Type: design.String},
   302  				}}
   303  				resource = &design.ResourceDefinition{
   304  					Name:       "child",
   305  					ParentName: "parent",
   306  					BasePath:   "/:basepath",
   307  					Params:     baseParams,
   308  				}
   309  			}
   310  
   311  			// Action
   312  			{
   313  				params := &design.AttributeDefinition{Type: design.Object{
   314  					"path":     &design.AttributeDefinition{Type: design.String},
   315  					"query":    &design.AttributeDefinition{Type: design.String},
   316  					"basepath": &design.AttributeDefinition{Type: design.String},
   317  				}}
   318  				action = &design.ActionDefinition{
   319  					Name:   "action",
   320  					Parent: resource,
   321  					Params: params,
   322  				}
   323  				route := &design.RouteDefinition{
   324  					Path:   "/:path",
   325  					Parent: action,
   326  				}
   327  				action.Routes = []*design.RouteDefinition{route}
   328  				resource.Actions = map[string]*design.ActionDefinition{"action": action}
   329  			}
   330  			design.Design.Resources = map[string]*design.ResourceDefinition{"resource": resource, "parent": parent}
   331  			design.Design.BasePath = "/:apipath"
   332  			params := design.Object{
   333  				"apipath":  &design.AttributeDefinition{Type: design.String},
   334  				"apiquery": &design.AttributeDefinition{Type: design.String},
   335  			}
   336  			design.Design.Params = &design.AttributeDefinition{Type: params}
   337  		})
   338  
   339  		JustBeforeEach(func() {
   340  			allParams = action.AllParams().Type.ToObject()
   341  			pathParams = action.PathParams().Type.ToObject()
   342  			Ω(allParams).ShouldNot(BeNil())
   343  			Ω(pathParams).ShouldNot(BeNil())
   344  		})
   345  
   346  		AfterEach(func() {
   347  			design.Design.Params = nil
   348  			design.Design.Resources = nil
   349  			design.Design.BasePath = ""
   350  		})
   351  
   352  		It("AllParams returns both path and query parameters of the action and the resource", func() {
   353  			for p := range action.Params.Type.ToObject() {
   354  				Ω(allParams).Should(HaveKey(p))
   355  			}
   356  			for p := range resource.Params.Type.ToObject() {
   357  				Ω(allParams).Should(HaveKey(p))
   358  			}
   359  		})
   360  
   361  		It("AllParams returns the path parameters of the action, the resource, the parent resource and the API", func() {
   362  			for _, p := range []string{"path", "basepath", "canpath", "pbasepath", "apipath"} {
   363  				Ω(allParams).Should(HaveKey(p))
   364  			}
   365  		})
   366  
   367  		It("AllParams does NOT return the query parameters of the parent resource canonical action or the API", func() {
   368  			for _, p := range []string{"canquery", "pbasequery", "apiquery"} {
   369  				Ω(allParams).ShouldNot(HaveKey(p))
   370  			}
   371  		})
   372  
   373  		It("PathParams returns the path parameters recursively", func() {
   374  			Ω(pathParams).Should(HaveLen(5))
   375  			for _, p := range []string{"path", "basepath", "canpath", "pbasepath", "apipath"} {
   376  				Ω(pathParams).Should(HaveKey(p))
   377  			}
   378  		})
   379  	})
   380  })
   381  
   382  var _ = Describe("PathParams", func() {
   383  	Context("Given a resource with a nil base params", func() {
   384  		var (
   385  			resource   *design.ResourceDefinition
   386  			pathParams design.Object
   387  		)
   388  
   389  		BeforeEach(func() {
   390  			resource = &design.ResourceDefinition{
   391  				Name:     "resource",
   392  				BasePath: "/:basepath",
   393  			}
   394  			design.Design.Resources = map[string]*design.ResourceDefinition{"resource": resource}
   395  		})
   396  
   397  		AfterEach(func() {
   398  			design.Design.Resources = nil
   399  		})
   400  
   401  		JustBeforeEach(func() {
   402  			pathParams = resource.PathParams().Type.ToObject()
   403  			Ω(pathParams).ShouldNot(BeNil())
   404  		})
   405  
   406  		It("returns an empty attribute", func() {
   407  			Ω(pathParams).Should(BeEmpty())
   408  		})
   409  	})
   410  
   411  	Context("Given a resource defining a subset of all base path params", func() {
   412  		var (
   413  			resource   *design.ResourceDefinition
   414  			pathParams design.Object
   415  		)
   416  
   417  		BeforeEach(func() {
   418  			params := design.Object{"basepath": &design.AttributeDefinition{Type: design.String}}
   419  			resource = &design.ResourceDefinition{
   420  				Name:     "resource",
   421  				BasePath: "/:basepath/:sub",
   422  				Params:   &design.AttributeDefinition{Type: params},
   423  			}
   424  			design.Design.Resources = map[string]*design.ResourceDefinition{"resource": resource}
   425  		})
   426  
   427  		JustBeforeEach(func() {
   428  			pathParams = resource.PathParams().Type.ToObject()
   429  			Ω(pathParams).ShouldNot(BeNil())
   430  		})
   431  
   432  		AfterEach(func() {
   433  			design.Design.Resources = nil
   434  		})
   435  
   436  		It("returns an empty attribute", func() {
   437  			Ω(pathParams).Should(HaveLen(1))
   438  			Ω(pathParams).Should(HaveKey("basepath"))
   439  		})
   440  	})
   441  })
   442  
   443  var _ = Describe("IterateSets", func() {
   444  
   445  	var api *design.APIDefinition
   446  
   447  	BeforeEach(func() {
   448  		api = &design.APIDefinition{}
   449  		api.Name = "Test"
   450  	})
   451  
   452  	Context("ResourceDefinition", func() {
   453  		// a function that collects resource definitions for validation
   454  		var valFunc = func(validate func([]*design.ResourceDefinition)) func(s dslengine.DefinitionSet) error {
   455  			return func(s dslengine.DefinitionSet) error {
   456  				if len(s) == 0 {
   457  					return nil
   458  				}
   459  
   460  				if _, ok := s[0].(*design.ResourceDefinition); !ok {
   461  					return nil
   462  				}
   463  
   464  				resources := make([]*design.ResourceDefinition, len(s))
   465  				for i, res := range s {
   466  					resources[i] = res.(*design.ResourceDefinition)
   467  				}
   468  
   469  				validate(resources)
   470  
   471  				return nil
   472  			}
   473  		}
   474  
   475  		It("should order nested resources", func() {
   476  			inspected := false
   477  			api.Resources = make(map[string]*design.ResourceDefinition)
   478  
   479  			api.Resources["V"] = &design.ResourceDefinition{Name: "V", ParentName: "W"}
   480  			api.Resources["W"] = &design.ResourceDefinition{Name: "W", ParentName: "X"}
   481  			api.Resources["X"] = &design.ResourceDefinition{Name: "X", ParentName: "Y"}
   482  			api.Resources["Y"] = &design.ResourceDefinition{Name: "Y", ParentName: "Z"}
   483  			api.Resources["Z"] = &design.ResourceDefinition{Name: "Z"}
   484  
   485  			validate := func(s []*design.ResourceDefinition) {
   486  				Ω(s[0].Name).Should(Equal("Z"))
   487  				Ω(s[1].Name).Should(Equal("Y"))
   488  				Ω(s[2].Name).Should(Equal("X"))
   489  				Ω(s[3].Name).Should(Equal("W"))
   490  				Ω(s[4].Name).Should(Equal("V"))
   491  				inspected = true
   492  			}
   493  
   494  			api.IterateSets(valFunc(validate))
   495  
   496  			Ω(inspected).Should(BeTrue())
   497  		})
   498  
   499  		It("should order multiple nested resources", func() {
   500  			inspected := false
   501  			api.Resources = make(map[string]*design.ResourceDefinition)
   502  
   503  			api.Resources["A"] = &design.ResourceDefinition{Name: "A"}
   504  			api.Resources["B"] = &design.ResourceDefinition{Name: "B", ParentName: "A"}
   505  			api.Resources["C"] = &design.ResourceDefinition{Name: "C", ParentName: "A"}
   506  			api.Resources["I"] = &design.ResourceDefinition{Name: "I"}
   507  			api.Resources["J"] = &design.ResourceDefinition{Name: "J", ParentName: "K"}
   508  			api.Resources["K"] = &design.ResourceDefinition{Name: "K", ParentName: "I"}
   509  			api.Resources["X"] = &design.ResourceDefinition{Name: "X"}
   510  			api.Resources["Y"] = &design.ResourceDefinition{Name: "Y"}
   511  			api.Resources["Z"] = &design.ResourceDefinition{Name: "Z"}
   512  
   513  			validate := func(s []*design.ResourceDefinition) {
   514  				Ω(s[0].Name).Should(Equal("A"))
   515  				Ω(s[1].Name).Should(Equal("B"))
   516  				Ω(s[2].Name).Should(Equal("C"))
   517  				Ω(s[3].Name).Should(Equal("I"))
   518  				Ω(s[4].Name).Should(Equal("K"))
   519  				Ω(s[5].Name).Should(Equal("J"))
   520  				Ω(s[6].Name).Should(Equal("X"))
   521  				Ω(s[7].Name).Should(Equal("Y"))
   522  				Ω(s[8].Name).Should(Equal("Z"))
   523  				inspected = true
   524  			}
   525  
   526  			api.IterateSets(valFunc(validate))
   527  
   528  			Ω(inspected).Should(BeTrue())
   529  		})
   530  	})
   531  
   532  })