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