github.com/josephbuchma/goa@v1.2.0/design/types_test.go (about)

     1  package design_test
     2  
     3  import (
     4  	"errors"
     5  	"mime"
     6  	"sync"
     7  
     8  	. "github.com/goadesign/goa/design"
     9  	. "github.com/goadesign/goa/design/apidsl"
    10  	"github.com/goadesign/goa/dslengine"
    11  	. "github.com/onsi/ginkgo"
    12  	. "github.com/onsi/gomega"
    13  )
    14  
    15  var _ = Describe("IsObject", func() {
    16  	var dt 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 = 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 = &Array{ElemType: &AttributeDefinition{Type: 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 = &Hash{
    46  				KeyType:  &AttributeDefinition{Type: String},
    47  				ElemType: &AttributeDefinition{Type: 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 = &UserTypeDefinition{AttributeDefinition: &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 = Object{}
    69  		})
    70  
    71  		It("returns true", func() {
    72  			Ω(isObject).Should(BeTrue())
    73  		})
    74  	})
    75  })
    76  
    77  var _ = Describe("Project", func() {
    78  	var mt *MediaTypeDefinition
    79  	var view string
    80  
    81  	var projected *MediaTypeDefinition
    82  	var links *UserTypeDefinition
    83  	var prErr error
    84  
    85  	JustBeforeEach(func() {
    86  		ProjectedMediaTypes = make(map[string]*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 = &MediaTypeDefinition{
    93  				UserTypeDefinition: &UserTypeDefinition{
    94  					AttributeDefinition: &AttributeDefinition{
    95  						Type: Object{
    96  							"att1": &AttributeDefinition{Type: Integer},
    97  							"att2": &AttributeDefinition{Type: String},
    98  						},
    99  					},
   100  					TypeName: "Foo",
   101  				},
   102  				Identifier: "vnd.application/foo",
   103  				Views: map[string]*ViewDefinition{
   104  					"default": {
   105  						Name: "default",
   106  						AttributeDefinition: &AttributeDefinition{
   107  							Type: Object{
   108  								"att1": &AttributeDefinition{Type: String},
   109  								"att2": &AttributeDefinition{Type: String},
   110  							},
   111  						},
   112  					},
   113  					"tiny": {
   114  						Name: "tiny",
   115  						AttributeDefinition: &AttributeDefinition{
   116  							Type: Object{
   117  								"att2": &AttributeDefinition{Type: 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(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(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(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(StringKind))
   192  			})
   193  		})
   194  
   195  	})
   196  
   197  	Context("with a media type with a links attribute", func() {
   198  		BeforeEach(func() {
   199  			mt = &MediaTypeDefinition{
   200  				UserTypeDefinition: &UserTypeDefinition{
   201  					AttributeDefinition: &AttributeDefinition{
   202  						Type: Object{
   203  							"att1":  &AttributeDefinition{Type: Integer},
   204  							"links": &AttributeDefinition{Type: String},
   205  						},
   206  					},
   207  					TypeName: "Foo",
   208  				},
   209  				Identifier: "vnd.application/foo",
   210  				Views: map[string]*ViewDefinition{
   211  					"default": {
   212  						Name: "default",
   213  						AttributeDefinition: &AttributeDefinition{
   214  							Type: Object{
   215  								"att1":  &AttributeDefinition{Type: String},
   216  								"links": &AttributeDefinition{Type: String},
   217  							},
   218  						},
   219  					},
   220  				},
   221  			}
   222  		})
   223  
   224  		Context("using the default view", func() {
   225  			BeforeEach(func() {
   226  				view = "default"
   227  			})
   228  
   229  			It("uses the links attribute in the view", func() {
   230  				Ω(prErr).ShouldNot(HaveOccurred())
   231  				Ω(projected).ShouldNot(BeNil())
   232  				Ω(projected.Type).Should(BeAssignableToTypeOf(Object{}))
   233  				Ω(projected.Type.ToObject()).Should(HaveKey("links"))
   234  				att := projected.Type.ToObject()["links"]
   235  				Ω(att).ShouldNot(BeNil())
   236  				Ω(att.Type).ShouldNot(BeNil())
   237  				Ω(att.Type.Kind()).Should(Equal(StringKind))
   238  			})
   239  		})
   240  	})
   241  
   242  	Context("with media types with view attributes with a cyclical dependency", func() {
   243  		const id = "vnd.application/MT1"
   244  		const typeName = "Mt1"
   245  		metadata := dslengine.MetadataDefinition{"foo": []string{"bar"}}
   246  
   247  		BeforeEach(func() {
   248  			dslengine.Reset()
   249  			API("test", func() {})
   250  			mt = MediaType(id, func() {
   251  				TypeName(typeName)
   252  				Attributes(func() {
   253  					Attribute("att", "vnd.application/MT2", func() {
   254  						Metadata("foo", "bar")
   255  					})
   256  				})
   257  				Links(func() {
   258  					Link("att", "default")
   259  				})
   260  				View("default", func() {
   261  					Attribute("att")
   262  					Attribute("links")
   263  				})
   264  				View("tiny", func() {
   265  					Attribute("att", func() {
   266  						View("tiny")
   267  					})
   268  				})
   269  			})
   270  			MediaType("vnd.application/MT2", func() {
   271  				TypeName("Mt2")
   272  				Attributes(func() {
   273  					Attribute("att2", mt)
   274  				})
   275  				Links(func() {
   276  					Link("att2", "default")
   277  				})
   278  				View("default", func() {
   279  					Attribute("att2")
   280  					Attribute("links")
   281  				})
   282  				View("tiny", func() {
   283  					Attribute("links")
   284  				})
   285  			})
   286  			err := dslengine.Run()
   287  			Ω(err).ShouldNot(HaveOccurred())
   288  			Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   289  		})
   290  
   291  		Context("using the default view", func() {
   292  			BeforeEach(func() {
   293  				view = "default"
   294  			})
   295  
   296  			It("returns the projected media type with links", func() {
   297  				Ω(prErr).ShouldNot(HaveOccurred())
   298  				Ω(projected).ShouldNot(BeNil())
   299  				Ω(projected.Type).Should(BeAssignableToTypeOf(Object{}))
   300  				Ω(projected.Type.ToObject()).Should(HaveKey("att"))
   301  				l := projected.Type.ToObject()["links"]
   302  				Ω(l.Type.(*UserTypeDefinition).AttributeDefinition).Should(Equal(links.AttributeDefinition))
   303  				Ω(links.Type.ToObject()).Should(HaveKey("att"))
   304  				Ω(links.Type.ToObject()["att"].Metadata).Should(Equal(metadata))
   305  			})
   306  		})
   307  
   308  		Context("using the tiny view", func() {
   309  			BeforeEach(func() {
   310  				view = "tiny"
   311  			})
   312  
   313  			It("returns the projected media type with links", func() {
   314  				Ω(prErr).ShouldNot(HaveOccurred())
   315  				Ω(projected).ShouldNot(BeNil())
   316  				Ω(projected.Type).Should(BeAssignableToTypeOf(Object{}))
   317  				Ω(projected.Type.ToObject()).Should(HaveKey("att"))
   318  				att := projected.Type.ToObject()["att"]
   319  				Ω(att.Type.ToObject()).Should(HaveKey("links"))
   320  				Ω(att.Type.ToObject()).ShouldNot(HaveKey("att2"))
   321  			})
   322  		})
   323  	})
   324  })
   325  
   326  var _ = Describe("UserTypes", func() {
   327  	var (
   328  		o         Object
   329  		userTypes map[string]*UserTypeDefinition
   330  	)
   331  
   332  	JustBeforeEach(func() {
   333  		userTypes = UserTypes(o)
   334  	})
   335  
   336  	Context("with an object not using user types", func() {
   337  		BeforeEach(func() {
   338  			o = Object{"foo": &AttributeDefinition{Type: String}}
   339  		})
   340  
   341  		It("returns nil", func() {
   342  			Ω(userTypes).Should(BeNil())
   343  		})
   344  	})
   345  
   346  	Context("with an object with an attribute using a user type", func() {
   347  		var ut *UserTypeDefinition
   348  		BeforeEach(func() {
   349  			ut = &UserTypeDefinition{
   350  				TypeName:            "foo",
   351  				AttributeDefinition: &AttributeDefinition{Type: String},
   352  			}
   353  
   354  			o = Object{"foo": &AttributeDefinition{Type: ut}}
   355  		})
   356  
   357  		It("returns the user type", func() {
   358  			Ω(userTypes).Should(HaveLen(1))
   359  			Ω(userTypes[ut.TypeName]).Should(Equal(ut))
   360  		})
   361  	})
   362  
   363  	Context("with an object with an attribute using recursive user types", func() {
   364  		var ut, childut *UserTypeDefinition
   365  
   366  		BeforeEach(func() {
   367  			childut = &UserTypeDefinition{
   368  				TypeName:            "child",
   369  				AttributeDefinition: &AttributeDefinition{Type: String},
   370  			}
   371  			child := Object{"child": &AttributeDefinition{Type: childut}}
   372  			ut = &UserTypeDefinition{
   373  				TypeName:            "parent",
   374  				AttributeDefinition: &AttributeDefinition{Type: child},
   375  			}
   376  
   377  			o = Object{"foo": &AttributeDefinition{Type: ut}}
   378  		})
   379  
   380  		It("returns the user types", func() {
   381  			Ω(userTypes).Should(HaveLen(2))
   382  			Ω(userTypes[ut.TypeName]).Should(Equal(ut))
   383  			Ω(userTypes[childut.TypeName]).Should(Equal(childut))
   384  		})
   385  	})
   386  })
   387  
   388  var _ = Describe("MediaTypeDefinition", func() {
   389  	Describe("IterateViews", func() {
   390  		var (
   391  			m  *MediaTypeDefinition
   392  			it ViewIterator
   393  
   394  			iteratedViews []string
   395  		)
   396  		BeforeEach(func() {
   397  			m = &MediaTypeDefinition{}
   398  
   399  			// setup iterator that just accumulates view names into iteratedViews
   400  			iteratedViews = []string{}
   401  			it = func(v *ViewDefinition) error {
   402  				iteratedViews = append(iteratedViews, v.Name)
   403  				return nil
   404  			}
   405  		})
   406  		It("works with empty", func() {
   407  			Expect(m.Views).To(BeEmpty())
   408  			Expect(m.IterateViews(it)).To(Succeed())
   409  			Expect(iteratedViews).To(BeEmpty())
   410  		})
   411  		Context("with non-empty views map", func() {
   412  			BeforeEach(func() {
   413  				m.Views = map[string]*ViewDefinition{
   414  					"d": {Name: "d"},
   415  					"c": {Name: "c"},
   416  					"a": {Name: "a"},
   417  					"b": {Name: "b"},
   418  				}
   419  			})
   420  			It("sorts views", func() {
   421  				Expect(m.IterateViews(it)).To(Succeed())
   422  				Expect(iteratedViews).To(Equal([]string{"a", "b", "c", "d"}))
   423  			})
   424  			It("propagates error", func() {
   425  				errIterator := func(v *ViewDefinition) error {
   426  					if len(iteratedViews) > 2 {
   427  						return errors.New("foo")
   428  					}
   429  					iteratedViews = append(iteratedViews, v.Name)
   430  					return nil
   431  				}
   432  				Expect(m.IterateViews(errIterator)).To(MatchError("foo"))
   433  				Expect(iteratedViews).To(Equal([]string{"a", "b", "c"}))
   434  			})
   435  		})
   436  	})
   437  })
   438  
   439  var _ = Describe("Walk", func() {
   440  	var target DataStructure
   441  	var matchedName string
   442  	var count int
   443  	var matched bool
   444  
   445  	counter := func(*AttributeDefinition) error {
   446  		count++
   447  		return nil
   448  	}
   449  
   450  	matcher := func(name string) func(*AttributeDefinition) error {
   451  		done := errors.New("done")
   452  		return func(att *AttributeDefinition) error {
   453  			if u, ok := att.Type.(*UserTypeDefinition); ok {
   454  				if u.TypeName == name {
   455  					matched = true
   456  					return done
   457  				}
   458  			} else if m, ok := att.Type.(*MediaTypeDefinition); ok {
   459  				if m.TypeName == name {
   460  					matched = true
   461  					return done
   462  				}
   463  			}
   464  			return nil
   465  		}
   466  	}
   467  
   468  	BeforeEach(func() {
   469  		matchedName = ""
   470  		count = 0
   471  		matched = false
   472  	})
   473  
   474  	JustBeforeEach(func() {
   475  		target.Walk(counter)
   476  		if matchedName != "" {
   477  			target.Walk(matcher(matchedName))
   478  		}
   479  	})
   480  
   481  	Context("with simple attribute", func() {
   482  		BeforeEach(func() {
   483  			target = &AttributeDefinition{Type: String}
   484  		})
   485  
   486  		It("walks it", func() {
   487  			Ω(count).Should(Equal(1))
   488  		})
   489  	})
   490  
   491  	Context("with an object attribute", func() {
   492  		BeforeEach(func() {
   493  			o := Object{"foo": &AttributeDefinition{Type: String}}
   494  			target = &AttributeDefinition{Type: o}
   495  		})
   496  
   497  		It("walks it", func() {
   498  			Ω(count).Should(Equal(2))
   499  		})
   500  	})
   501  
   502  	Context("with an object attribute containing user types", func() {
   503  		const typeName = "foo"
   504  		BeforeEach(func() {
   505  			matchedName = typeName
   506  			at := &AttributeDefinition{Type: String}
   507  			ut := &UserTypeDefinition{AttributeDefinition: at, TypeName: typeName}
   508  			o := Object{"foo": &AttributeDefinition{Type: ut}}
   509  			target = &AttributeDefinition{Type: o}
   510  		})
   511  
   512  		It("walks it", func() {
   513  			Ω(count).Should(Equal(3))
   514  			Ω(matched).Should(BeTrue())
   515  		})
   516  	})
   517  
   518  	Context("with an object attribute containing recursive user types", func() {
   519  		const typeName = "foo"
   520  		BeforeEach(func() {
   521  			matchedName = typeName
   522  			co := Object{}
   523  			at := &AttributeDefinition{Type: co}
   524  			ut := &UserTypeDefinition{AttributeDefinition: at, TypeName: typeName}
   525  			co["recurse"] = &AttributeDefinition{Type: ut}
   526  			o := Object{"foo": &AttributeDefinition{Type: ut}}
   527  			target = &AttributeDefinition{Type: o}
   528  		})
   529  
   530  		It("walks it", func() {
   531  			Ω(count).Should(Equal(4))
   532  			Ω(matched).Should(BeTrue())
   533  		})
   534  	})
   535  })
   536  
   537  var _ = Describe("Finalize", func() {
   538  	BeforeEach(func() {
   539  		dslengine.Reset()
   540  		MediaType("application/vnd.menu+json", func() {
   541  			Attributes(func() {
   542  				Attribute("name", String, "The name of an application")
   543  				Attribute("child", CollectionOf("application/vnd.menu"))
   544  			})
   545  
   546  			View("default", func() {
   547  				Attribute("name")
   548  			})
   549  		})
   550  	})
   551  
   552  	It("running the DSL should not loop indefinitely", func() {
   553  		var mu sync.Mutex
   554  		err := errors.New("infinite loop")
   555  		go func() {
   556  			err2 := dslengine.Run()
   557  			mu.Lock()
   558  			defer mu.Unlock()
   559  			err = err2
   560  		}()
   561  		Eventually(func() error {
   562  			mu.Lock()
   563  			defer mu.Unlock()
   564  			return err
   565  		}).ShouldNot(HaveOccurred())
   566  	})
   567  })