github.com/furusax0621/goa-v1@v1.4.3/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 Context("on a collection", func() { 195 BeforeEach(func() { 196 mt = CollectionOf(Dup(mt)) 197 dslengine.Execute(mt.DSL(), mt) 198 mt.GenerateExample(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 = &MediaTypeDefinition{ 214 UserTypeDefinition: &UserTypeDefinition{ 215 AttributeDefinition: &AttributeDefinition{ 216 Type: Object{ 217 "att1": &AttributeDefinition{Type: Integer}, 218 "links": &AttributeDefinition{Type: String}, 219 }, 220 }, 221 TypeName: "Foo", 222 }, 223 Identifier: "vnd.application/foo", 224 Views: map[string]*ViewDefinition{ 225 "default": { 226 Name: "default", 227 AttributeDefinition: &AttributeDefinition{ 228 Type: Object{ 229 "att1": &AttributeDefinition{Type: String}, 230 "links": &AttributeDefinition{Type: 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(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(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 API("test", func() {}) 264 mt = MediaType(id, func() { 265 TypeName(typeName) 266 Attributes(func() { 267 Attribute("att", "vnd.application/MT2", func() { 268 Metadata("foo", "bar") 269 }) 270 }) 271 Links(func() { 272 Link("att", "default") 273 }) 274 View("default", func() { 275 Attribute("att") 276 Attribute("links") 277 }) 278 View("tiny", func() { 279 Attribute("att", func() { 280 View("tiny") 281 }) 282 }) 283 }) 284 MediaType("vnd.application/MT2", func() { 285 TypeName("Mt2") 286 Attributes(func() { 287 Attribute("att2", mt) 288 }) 289 Links(func() { 290 Link("att2", "default") 291 }) 292 View("default", func() { 293 Attribute("att2") 294 Attribute("links") 295 }) 296 View("tiny", func() { 297 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(Object{})) 314 Ω(projected.Type.ToObject()).Should(HaveKey("att")) 315 l := projected.Type.ToObject()["links"] 316 Ω(l.Type.(*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(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 Object 343 userTypes map[string]*UserTypeDefinition 344 ) 345 346 JustBeforeEach(func() { 347 userTypes = UserTypes(o) 348 }) 349 350 Context("with an object not using user types", func() { 351 BeforeEach(func() { 352 o = Object{"foo": &AttributeDefinition{Type: 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 *UserTypeDefinition 362 BeforeEach(func() { 363 ut = &UserTypeDefinition{ 364 TypeName: "foo", 365 AttributeDefinition: &AttributeDefinition{Type: String}, 366 } 367 368 o = Object{"foo": &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 *UserTypeDefinition 379 380 BeforeEach(func() { 381 childut = &UserTypeDefinition{ 382 TypeName: "child", 383 AttributeDefinition: &AttributeDefinition{Type: String}, 384 } 385 child := Object{"child": &AttributeDefinition{Type: childut}} 386 ut = &UserTypeDefinition{ 387 TypeName: "parent", 388 AttributeDefinition: &AttributeDefinition{Type: child}, 389 } 390 391 o = Object{"foo": &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 *MediaTypeDefinition 406 it ViewIterator 407 408 iteratedViews []string 409 ) 410 BeforeEach(func() { 411 m = &MediaTypeDefinition{} 412 413 // setup iterator that just accumulates view names into iteratedViews 414 iteratedViews = []string{} 415 it = func(v *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]*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 *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 DataStructure 455 var matchedName string 456 var count int 457 var matched bool 458 459 counter := func(*AttributeDefinition) error { 460 count++ 461 return nil 462 } 463 464 matcher := func(name string) func(*AttributeDefinition) error { 465 done := errors.New("done") 466 return func(att *AttributeDefinition) error { 467 if u, ok := att.Type.(*UserTypeDefinition); ok { 468 if u.TypeName == name { 469 matched = true 470 return done 471 } 472 } else if m, ok := att.Type.(*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 = &AttributeDefinition{Type: 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 := Object{"foo": &AttributeDefinition{Type: String}} 508 target = &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 := &AttributeDefinition{Type: String} 521 ut := &UserTypeDefinition{AttributeDefinition: at, TypeName: typeName} 522 o := Object{"foo": &AttributeDefinition{Type: ut}} 523 target = &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 := Object{} 537 at := &AttributeDefinition{Type: co} 538 ut := &UserTypeDefinition{AttributeDefinition: at, TypeName: typeName} 539 co["recurse"] = &AttributeDefinition{Type: ut} 540 o := Object{"foo": &AttributeDefinition{Type: ut}} 541 target = &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 MediaType("application/vnd.menu+json", func() { 555 Attributes(func() { 556 Attribute("name", String, "The name of an application") 557 Attribute("child", CollectionOf("application/vnd.menu")) 558 }) 559 560 View("default", func() { 561 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 := NewRandomGenerator("foo") 588 Ω(UUID.GenerateExample(rand, nil)).Should(BeAssignableToTypeOf("foo")) 589 }) 590 }) 591 592 Context("Given a Hash keyed by UUIDs", func() { 593 var h *Hash 594 BeforeEach(func() { 595 h = &Hash{ 596 KeyType: &AttributeDefinition{Type: UUID}, 597 ElemType: &AttributeDefinition{Type: String}, 598 } 599 }) 600 It("generates a serializable example", func() { 601 rand := NewRandomGenerator("foo") 602 Ω(h.GenerateExample(rand, nil)).Should(BeAssignableToTypeOf(map[string]string{"foo": "bar"})) 603 }) 604 }) 605 })