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

     1  package design_test
     2  
     3  import (
     4  	"errors"
     5  	"mime"
     6  	"sync"
     7  
     8  	. "github.com/onsi/ginkgo"
     9  	. "github.com/onsi/gomega"
    10  	"github.com/shogo82148/goa-v1/design"
    11  	"github.com/shogo82148/goa-v1/design/apidsl"
    12  	"github.com/shogo82148/goa-v1/dslengine"
    13  )
    14  
    15  var _ = Describe("IsObject", func() {
    16  	var dt design.DataType
    17  	var isObject bool
    18  
    19  	JustBeforeEach(func() {
    20  		isObject = dt.IsObject()
    21  	})
    22  
    23  	Context("with a primitive", func() {
    24  		BeforeEach(func() {
    25  			dt = design.String
    26  		})
    27  
    28  		It("returns false", func() {
    29  			Ω(isObject).Should(BeFalse())
    30  		})
    31  	})
    32  
    33  	Context("with an array", func() {
    34  		BeforeEach(func() {
    35  			dt = &design.Array{ElemType: &design.AttributeDefinition{Type: design.String}}
    36  		})
    37  
    38  		It("returns false", func() {
    39  			Ω(isObject).Should(BeFalse())
    40  		})
    41  	})
    42  
    43  	Context("with a hash", func() {
    44  		BeforeEach(func() {
    45  			dt = &design.Hash{
    46  				KeyType:  &design.AttributeDefinition{Type: design.String},
    47  				ElemType: &design.AttributeDefinition{Type: design.String},
    48  			}
    49  		})
    50  
    51  		It("returns false", func() {
    52  			Ω(isObject).Should(BeFalse())
    53  		})
    54  	})
    55  
    56  	Context("with a nil user type type", func() {
    57  		BeforeEach(func() {
    58  			dt = &design.UserTypeDefinition{AttributeDefinition: &design.AttributeDefinition{Type: nil}}
    59  		})
    60  
    61  		It("returns false", func() {
    62  			Ω(isObject).Should(BeFalse())
    63  		})
    64  	})
    65  
    66  	Context("with an object", func() {
    67  		BeforeEach(func() {
    68  			dt = design.Object{}
    69  		})
    70  
    71  		It("returns true", func() {
    72  			Ω(isObject).Should(BeTrue())
    73  		})
    74  	})
    75  })
    76  
    77  var _ = Describe("Project", func() {
    78  	var mt *design.MediaTypeDefinition
    79  	var view string
    80  
    81  	var projected *design.MediaTypeDefinition
    82  	var links *design.UserTypeDefinition
    83  	var prErr error
    84  
    85  	JustBeforeEach(func() {
    86  		design.ProjectedMediaTypes = make(map[string]*design.MediaTypeDefinition)
    87  		projected, links, prErr = mt.Project(view)
    88  	})
    89  
    90  	Context("with a media type with a default and a tiny view", func() {
    91  		BeforeEach(func() {
    92  			mt = &design.MediaTypeDefinition{
    93  				UserTypeDefinition: &design.UserTypeDefinition{
    94  					AttributeDefinition: &design.AttributeDefinition{
    95  						Type: design.Object{
    96  							"att1": &design.AttributeDefinition{Type: design.Integer},
    97  							"att2": &design.AttributeDefinition{Type: design.String},
    98  						},
    99  					},
   100  					TypeName: "Foo",
   101  				},
   102  				Identifier: "vnd.application/foo",
   103  				Views: map[string]*design.ViewDefinition{
   104  					"default": {
   105  						Name: "default",
   106  						AttributeDefinition: &design.AttributeDefinition{
   107  							Type: design.Object{
   108  								"att1": &design.AttributeDefinition{Type: design.String},
   109  								"att2": &design.AttributeDefinition{Type: design.String},
   110  							},
   111  						},
   112  					},
   113  					"tiny": {
   114  						Name: "tiny",
   115  						AttributeDefinition: &design.AttributeDefinition{
   116  							Type: design.Object{
   117  								"att2": &design.AttributeDefinition{Type: design.String},
   118  							},
   119  						},
   120  					},
   121  				},
   122  			}
   123  		})
   124  
   125  		Context("using the empty view", func() {
   126  			BeforeEach(func() {
   127  				view = ""
   128  			})
   129  
   130  			It("returns an error", func() {
   131  				Ω(prErr).Should(HaveOccurred())
   132  			})
   133  		})
   134  
   135  		Context("using the default view", func() {
   136  			BeforeEach(func() {
   137  				view = "default"
   138  			})
   139  
   140  			It("returns a media type with an identifier view param", func() {
   141  				Ω(prErr).ShouldNot(HaveOccurred())
   142  				_, params, err := mime.ParseMediaType(projected.Identifier)
   143  				Ω(err).ShouldNot(HaveOccurred())
   144  				Ω(params).Should(HaveKeyWithValue("view", "default"))
   145  			})
   146  
   147  			It("returns a media type with only a default view", func() {
   148  				Ω(prErr).ShouldNot(HaveOccurred())
   149  				Ω(projected.Views).Should(HaveLen(1))
   150  				Ω(projected.Views).Should(HaveKey("default"))
   151  			})
   152  
   153  			It("returns a media type with the default view attributes", func() {
   154  				Ω(prErr).ShouldNot(HaveOccurred())
   155  				Ω(projected).ShouldNot(BeNil())
   156  				Ω(projected.Type).Should(BeAssignableToTypeOf(design.Object{}))
   157  				Ω(projected.Type.ToObject()).Should(HaveKey("att1"))
   158  				att := projected.Type.ToObject()["att1"]
   159  				Ω(att).ShouldNot(BeNil())
   160  				Ω(att.Type).ShouldNot(BeNil())
   161  				Ω(att.Type.Kind()).Should(Equal(design.IntegerKind))
   162  			})
   163  		})
   164  
   165  		Context("using the tiny view", func() {
   166  			BeforeEach(func() {
   167  				view = "tiny"
   168  			})
   169  
   170  			It("returns a media type with an identifier view param", func() {
   171  				Ω(prErr).ShouldNot(HaveOccurred())
   172  				_, params, err := mime.ParseMediaType(projected.Identifier)
   173  				Ω(err).ShouldNot(HaveOccurred())
   174  				Ω(params).Should(HaveKeyWithValue("view", "tiny"))
   175  			})
   176  
   177  			It("returns a media type with only a default view", func() {
   178  				Ω(prErr).ShouldNot(HaveOccurred())
   179  				Ω(projected.Views).Should(HaveLen(1))
   180  				Ω(projected.Views).Should(HaveKey("default"))
   181  			})
   182  
   183  			It("returns a media type with the default view attributes", func() {
   184  				Ω(prErr).ShouldNot(HaveOccurred())
   185  				Ω(projected).ShouldNot(BeNil())
   186  				Ω(projected.Type).Should(BeAssignableToTypeOf(design.Object{}))
   187  				Ω(projected.Type.ToObject()).Should(HaveKey("att2"))
   188  				att := projected.Type.ToObject()["att2"]
   189  				Ω(att).ShouldNot(BeNil())
   190  				Ω(att.Type).ShouldNot(BeNil())
   191  				Ω(att.Type.Kind()).Should(Equal(design.StringKind))
   192  			})
   193  
   194  			Context("on a collection", func() {
   195  				BeforeEach(func() {
   196  					mt = apidsl.CollectionOf(design.Dup(mt))
   197  					dslengine.Execute(mt.DSL(), mt)
   198  					mt.GenerateExample(design.NewRandomGenerator(""), nil)
   199  				})
   200  
   201  				It("resets the example", func() {
   202  					Ω(prErr).ShouldNot(HaveOccurred())
   203  					Ω(projected).ShouldNot(BeNil())
   204  					Ω(projected.Example).Should(BeNil())
   205  				})
   206  			})
   207  		})
   208  
   209  	})
   210  
   211  	Context("with a media type with a links attribute", func() {
   212  		BeforeEach(func() {
   213  			mt = &design.MediaTypeDefinition{
   214  				UserTypeDefinition: &design.UserTypeDefinition{
   215  					AttributeDefinition: &design.AttributeDefinition{
   216  						Type: design.Object{
   217  							"att1":  &design.AttributeDefinition{Type: design.Integer},
   218  							"links": &design.AttributeDefinition{Type: design.String},
   219  						},
   220  					},
   221  					TypeName: "Foo",
   222  				},
   223  				Identifier: "vnd.application/foo",
   224  				Views: map[string]*design.ViewDefinition{
   225  					"default": {
   226  						Name: "default",
   227  						AttributeDefinition: &design.AttributeDefinition{
   228  							Type: design.Object{
   229  								"att1":  &design.AttributeDefinition{Type: design.String},
   230  								"links": &design.AttributeDefinition{Type: design.String},
   231  							},
   232  						},
   233  					},
   234  				},
   235  			}
   236  		})
   237  
   238  		Context("using the default view", func() {
   239  			BeforeEach(func() {
   240  				view = "default"
   241  			})
   242  
   243  			It("uses the links attribute in the view", func() {
   244  				Ω(prErr).ShouldNot(HaveOccurred())
   245  				Ω(projected).ShouldNot(BeNil())
   246  				Ω(projected.Type).Should(BeAssignableToTypeOf(design.Object{}))
   247  				Ω(projected.Type.ToObject()).Should(HaveKey("links"))
   248  				att := projected.Type.ToObject()["links"]
   249  				Ω(att).ShouldNot(BeNil())
   250  				Ω(att.Type).ShouldNot(BeNil())
   251  				Ω(att.Type.Kind()).Should(Equal(design.StringKind))
   252  			})
   253  		})
   254  	})
   255  
   256  	Context("with media types with view attributes with a cyclical dependency", func() {
   257  		const id = "vnd.application/MT1"
   258  		const typeName = "Mt1"
   259  		metadata := dslengine.MetadataDefinition{"foo": []string{"bar"}}
   260  
   261  		BeforeEach(func() {
   262  			dslengine.Reset()
   263  			apidsl.API("test", func() {})
   264  			mt = apidsl.MediaType(id, func() {
   265  				apidsl.TypeName(typeName)
   266  				apidsl.Attributes(func() {
   267  					apidsl.Attribute("att", "vnd.application/MT2", func() {
   268  						apidsl.Metadata("foo", "bar")
   269  					})
   270  				})
   271  				apidsl.Links(func() {
   272  					apidsl.Link("att", "default")
   273  				})
   274  				apidsl.View("default", func() {
   275  					apidsl.Attribute("att")
   276  					apidsl.Attribute("links")
   277  				})
   278  				apidsl.View("tiny", func() {
   279  					apidsl.Attribute("att", func() {
   280  						apidsl.View("tiny")
   281  					})
   282  				})
   283  			})
   284  			apidsl.MediaType("vnd.application/MT2", func() {
   285  				apidsl.TypeName("Mt2")
   286  				apidsl.Attributes(func() {
   287  					apidsl.Attribute("att2", mt)
   288  				})
   289  				apidsl.Links(func() {
   290  					apidsl.Link("att2", "default")
   291  				})
   292  				apidsl.View("default", func() {
   293  					apidsl.Attribute("att2")
   294  					apidsl.Attribute("links")
   295  				})
   296  				apidsl.View("tiny", func() {
   297  					apidsl.Attribute("links")
   298  				})
   299  			})
   300  			err := dslengine.Run()
   301  			Ω(err).ShouldNot(HaveOccurred())
   302  			Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   303  		})
   304  
   305  		Context("using the default view", func() {
   306  			BeforeEach(func() {
   307  				view = "default"
   308  			})
   309  
   310  			It("returns the projected media type with links", func() {
   311  				Ω(prErr).ShouldNot(HaveOccurred())
   312  				Ω(projected).ShouldNot(BeNil())
   313  				Ω(projected.Type).Should(BeAssignableToTypeOf(design.Object{}))
   314  				Ω(projected.Type.ToObject()).Should(HaveKey("att"))
   315  				l := projected.Type.ToObject()["links"]
   316  				Ω(l.Type.(*design.UserTypeDefinition).AttributeDefinition).Should(Equal(links.AttributeDefinition))
   317  				Ω(links.Type.ToObject()).Should(HaveKey("att"))
   318  				Ω(links.Type.ToObject()["att"].Metadata).Should(Equal(metadata))
   319  			})
   320  		})
   321  
   322  		Context("using the tiny view", func() {
   323  			BeforeEach(func() {
   324  				view = "tiny"
   325  			})
   326  
   327  			It("returns the projected media type with links", func() {
   328  				Ω(prErr).ShouldNot(HaveOccurred())
   329  				Ω(projected).ShouldNot(BeNil())
   330  				Ω(projected.Type).Should(BeAssignableToTypeOf(design.Object{}))
   331  				Ω(projected.Type.ToObject()).Should(HaveKey("att"))
   332  				att := projected.Type.ToObject()["att"]
   333  				Ω(att.Type.ToObject()).Should(HaveKey("links"))
   334  				Ω(att.Type.ToObject()).ShouldNot(HaveKey("att2"))
   335  			})
   336  		})
   337  	})
   338  })
   339  
   340  var _ = Describe("UserTypes", func() {
   341  	var (
   342  		o         design.Object
   343  		userTypes map[string]*design.UserTypeDefinition
   344  	)
   345  
   346  	JustBeforeEach(func() {
   347  		userTypes = design.UserTypes(o)
   348  	})
   349  
   350  	Context("with an object not using user types", func() {
   351  		BeforeEach(func() {
   352  			o = design.Object{"foo": &design.AttributeDefinition{Type: design.String}}
   353  		})
   354  
   355  		It("returns nil", func() {
   356  			Ω(userTypes).Should(BeNil())
   357  		})
   358  	})
   359  
   360  	Context("with an object with an attribute using a user type", func() {
   361  		var ut *design.UserTypeDefinition
   362  		BeforeEach(func() {
   363  			ut = &design.UserTypeDefinition{
   364  				TypeName:            "foo",
   365  				AttributeDefinition: &design.AttributeDefinition{Type: design.String},
   366  			}
   367  
   368  			o = design.Object{"foo": &design.AttributeDefinition{Type: ut}}
   369  		})
   370  
   371  		It("returns the user type", func() {
   372  			Ω(userTypes).Should(HaveLen(1))
   373  			Ω(userTypes[ut.TypeName]).Should(Equal(ut))
   374  		})
   375  	})
   376  
   377  	Context("with an object with an attribute using recursive user types", func() {
   378  		var ut, childut *design.UserTypeDefinition
   379  
   380  		BeforeEach(func() {
   381  			childut = &design.UserTypeDefinition{
   382  				TypeName:            "child",
   383  				AttributeDefinition: &design.AttributeDefinition{Type: design.String},
   384  			}
   385  			child := design.Object{"child": &design.AttributeDefinition{Type: childut}}
   386  			ut = &design.UserTypeDefinition{
   387  				TypeName:            "parent",
   388  				AttributeDefinition: &design.AttributeDefinition{Type: child},
   389  			}
   390  
   391  			o = design.Object{"foo": &design.AttributeDefinition{Type: ut}}
   392  		})
   393  
   394  		It("returns the user types", func() {
   395  			Ω(userTypes).Should(HaveLen(2))
   396  			Ω(userTypes[ut.TypeName]).Should(Equal(ut))
   397  			Ω(userTypes[childut.TypeName]).Should(Equal(childut))
   398  		})
   399  	})
   400  })
   401  
   402  var _ = Describe("MediaTypeDefinition", func() {
   403  	Describe("IterateViews", func() {
   404  		var (
   405  			m  *design.MediaTypeDefinition
   406  			it design.ViewIterator
   407  
   408  			iteratedViews []string
   409  		)
   410  		BeforeEach(func() {
   411  			m = &design.MediaTypeDefinition{}
   412  
   413  			// setup iterator that just accumulates view names into iteratedViews
   414  			iteratedViews = []string{}
   415  			it = func(v *design.ViewDefinition) error {
   416  				iteratedViews = append(iteratedViews, v.Name)
   417  				return nil
   418  			}
   419  		})
   420  		It("works with empty", func() {
   421  			Expect(m.Views).To(BeEmpty())
   422  			Expect(m.IterateViews(it)).To(Succeed())
   423  			Expect(iteratedViews).To(BeEmpty())
   424  		})
   425  		Context("with non-empty views map", func() {
   426  			BeforeEach(func() {
   427  				m.Views = map[string]*design.ViewDefinition{
   428  					"d": {Name: "d"},
   429  					"c": {Name: "c"},
   430  					"a": {Name: "a"},
   431  					"b": {Name: "b"},
   432  				}
   433  			})
   434  			It("sorts views", func() {
   435  				Expect(m.IterateViews(it)).To(Succeed())
   436  				Expect(iteratedViews).To(Equal([]string{"a", "b", "c", "d"}))
   437  			})
   438  			It("propagates error", func() {
   439  				errIterator := func(v *design.ViewDefinition) error {
   440  					if len(iteratedViews) > 2 {
   441  						return errors.New("foo")
   442  					}
   443  					iteratedViews = append(iteratedViews, v.Name)
   444  					return nil
   445  				}
   446  				Expect(m.IterateViews(errIterator)).To(MatchError("foo"))
   447  				Expect(iteratedViews).To(Equal([]string{"a", "b", "c"}))
   448  			})
   449  		})
   450  	})
   451  })
   452  
   453  var _ = Describe("Walk", func() {
   454  	var target design.DataStructure
   455  	var matchedName string
   456  	var count int
   457  	var matched bool
   458  
   459  	counter := func(*design.AttributeDefinition) error {
   460  		count++
   461  		return nil
   462  	}
   463  
   464  	matcher := func(name string) func(*design.AttributeDefinition) error {
   465  		done := errors.New("done")
   466  		return func(att *design.AttributeDefinition) error {
   467  			if u, ok := att.Type.(*design.UserTypeDefinition); ok {
   468  				if u.TypeName == name {
   469  					matched = true
   470  					return done
   471  				}
   472  			} else if m, ok := att.Type.(*design.MediaTypeDefinition); ok {
   473  				if m.TypeName == name {
   474  					matched = true
   475  					return done
   476  				}
   477  			}
   478  			return nil
   479  		}
   480  	}
   481  
   482  	BeforeEach(func() {
   483  		matchedName = ""
   484  		count = 0
   485  		matched = false
   486  	})
   487  
   488  	JustBeforeEach(func() {
   489  		target.Walk(counter)
   490  		if matchedName != "" {
   491  			target.Walk(matcher(matchedName))
   492  		}
   493  	})
   494  
   495  	Context("with simple attribute", func() {
   496  		BeforeEach(func() {
   497  			target = &design.AttributeDefinition{Type: design.String}
   498  		})
   499  
   500  		It("walks it", func() {
   501  			Ω(count).Should(Equal(1))
   502  		})
   503  	})
   504  
   505  	Context("with an object attribute", func() {
   506  		BeforeEach(func() {
   507  			o := design.Object{"foo": &design.AttributeDefinition{Type: design.String}}
   508  			target = &design.AttributeDefinition{Type: o}
   509  		})
   510  
   511  		It("walks it", func() {
   512  			Ω(count).Should(Equal(2))
   513  		})
   514  	})
   515  
   516  	Context("with an object attribute containing user types", func() {
   517  		const typeName = "foo"
   518  		BeforeEach(func() {
   519  			matchedName = typeName
   520  			at := &design.AttributeDefinition{Type: design.String}
   521  			ut := &design.UserTypeDefinition{AttributeDefinition: at, TypeName: typeName}
   522  			o := design.Object{"foo": &design.AttributeDefinition{Type: ut}}
   523  			target = &design.AttributeDefinition{Type: o}
   524  		})
   525  
   526  		It("walks it", func() {
   527  			Ω(count).Should(Equal(3))
   528  			Ω(matched).Should(BeTrue())
   529  		})
   530  	})
   531  
   532  	Context("with an object attribute containing recursive user types", func() {
   533  		const typeName = "foo"
   534  		BeforeEach(func() {
   535  			matchedName = typeName
   536  			co := design.Object{}
   537  			at := &design.AttributeDefinition{Type: co}
   538  			ut := &design.UserTypeDefinition{AttributeDefinition: at, TypeName: typeName}
   539  			co["recurse"] = &design.AttributeDefinition{Type: ut}
   540  			o := design.Object{"foo": &design.AttributeDefinition{Type: ut}}
   541  			target = &design.AttributeDefinition{Type: o}
   542  		})
   543  
   544  		It("walks it", func() {
   545  			Ω(count).Should(Equal(4))
   546  			Ω(matched).Should(BeTrue())
   547  		})
   548  	})
   549  })
   550  
   551  var _ = Describe("Finalize", func() {
   552  	BeforeEach(func() {
   553  		dslengine.Reset()
   554  		apidsl.MediaType("application/vnd.menu+json", func() {
   555  			apidsl.Attributes(func() {
   556  				apidsl.Attribute("name", design.String, "The name of an application")
   557  				apidsl.Attribute("child", apidsl.CollectionOf("application/vnd.menu"))
   558  			})
   559  
   560  			apidsl.View("default", func() {
   561  				apidsl.Attribute("name")
   562  			})
   563  		})
   564  	})
   565  
   566  	It("running the DSL should not loop indefinitely", func() {
   567  		var mu sync.Mutex
   568  		err := errors.New("infinite loop")
   569  		go func() {
   570  			err2 := dslengine.Run()
   571  			mu.Lock()
   572  			defer mu.Unlock()
   573  			err = err2
   574  		}()
   575  		Eventually(func() error {
   576  			mu.Lock()
   577  			defer mu.Unlock()
   578  			return err
   579  		}).ShouldNot(HaveOccurred())
   580  	})
   581  })
   582  
   583  var _ = Describe("GenerateExample", func() {
   584  
   585  	Context("Given a UUID", func() {
   586  		It("generates a string example", func() {
   587  			rand := design.NewRandomGenerator("foo")
   588  			Ω(design.UUID.GenerateExample(rand, nil)).Should(BeAssignableToTypeOf("foo"))
   589  		})
   590  	})
   591  
   592  	Context("Given a Hash keyed by UUIDs", func() {
   593  		var h *design.Hash
   594  		BeforeEach(func() {
   595  			h = &design.Hash{
   596  				KeyType:  &design.AttributeDefinition{Type: design.UUID},
   597  				ElemType: &design.AttributeDefinition{Type: design.String},
   598  			}
   599  		})
   600  		It("generates a serializable example", func() {
   601  			rand := design.NewRandomGenerator("foo")
   602  			Ω(h.GenerateExample(rand, nil)).Should(BeAssignableToTypeOf(map[string]string{"foo": "bar"}))
   603  		})
   604  	})
   605  })