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