github.com/shogo82148/goa-v1@v1.6.2/design/definitions_test.go (about)

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