github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/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 })