github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+incompatible/actor/v2action/route_test.go (about) 1 package v2action_test 2 3 import ( 4 "errors" 5 6 . "code.cloudfoundry.org/cli/actor/v2action" 7 "code.cloudfoundry.org/cli/actor/v2action/v2actionfakes" 8 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 9 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv2" 10 "code.cloudfoundry.org/cli/types" 11 . "github.com/onsi/ginkgo" 12 . "github.com/onsi/ginkgo/extensions/table" 13 . "github.com/onsi/gomega" 14 ) 15 16 var _ = Describe("Route Actions", func() { 17 var ( 18 actor *Actor 19 fakeCloudControllerClient *v2actionfakes.FakeCloudControllerClient 20 ) 21 22 BeforeEach(func() { 23 fakeCloudControllerClient = new(v2actionfakes.FakeCloudControllerClient) 24 actor = NewActor(fakeCloudControllerClient, nil, nil) 25 }) 26 27 Describe("Route", func() { 28 DescribeTable("String", func(host string, domain string, path string, port types.NullInt, expectedValue string) { 29 route := Route{ 30 Host: host, 31 Domain: Domain{ 32 Name: domain, 33 }, 34 Path: path, 35 Port: port, 36 } 37 Expect(route.String()).To(Equal(expectedValue)) 38 }, 39 40 Entry("has domain", "", "domain.com", "", types.NullInt{IsSet: false}, "domain.com"), 41 Entry("has host, domain", "host", "domain.com", "", types.NullInt{IsSet: false}, "host.domain.com"), 42 Entry("has domain, path", "", "domain.com", "/path", types.NullInt{IsSet: false}, "domain.com/path"), 43 Entry("has domain, path", "", "domain.com", "path", types.NullInt{IsSet: false}, "domain.com/path"), 44 Entry("has host, domain, path", "host", "domain.com", "/path", types.NullInt{IsSet: false}, "host.domain.com/path"), 45 Entry("has domain, port", "", "domain.com", "", types.NullInt{IsSet: true, Value: 3333}, "domain.com:3333"), 46 Entry("has host, domain, path, port", "host", "domain.com", "/path", types.NullInt{IsSet: true, Value: 3333}, "host.domain.com:3333/path"), 47 ) 48 }) 49 50 Describe("BindRouteToApplication", func() { 51 Context("when no errors are encountered", func() { 52 BeforeEach(func() { 53 fakeCloudControllerClient.BindRouteToApplicationReturns( 54 ccv2.Route{}, 55 ccv2.Warnings{"bind warning"}, 56 nil) 57 }) 58 59 It("binds the route to the application and returns all warnings", func() { 60 warnings, err := actor.BindRouteToApplication("some-route-guid", "some-app-guid") 61 Expect(err).ToNot(HaveOccurred()) 62 Expect(warnings).To(ConsistOf("bind warning")) 63 64 Expect(fakeCloudControllerClient.BindRouteToApplicationCallCount()).To(Equal(1)) 65 routeGUID, appGUID := fakeCloudControllerClient.BindRouteToApplicationArgsForCall(0) 66 Expect(routeGUID).To(Equal("some-route-guid")) 67 Expect(appGUID).To(Equal("some-app-guid")) 68 }) 69 }) 70 71 Context("when an error is encountered", func() { 72 Context("InvalidRelationError", func() { 73 BeforeEach(func() { 74 fakeCloudControllerClient.BindRouteToApplicationReturns( 75 ccv2.Route{}, 76 ccv2.Warnings{"bind warning"}, 77 ccerror.InvalidRelationError{}) 78 }) 79 80 It("returns the error", func() { 81 warnings, err := actor.BindRouteToApplication("some-route-guid", "some-app-guid") 82 Expect(err).To(MatchError(RouteInDifferentSpaceError{})) 83 Expect(warnings).To(ConsistOf("bind warning")) 84 }) 85 }) 86 87 Context("generic error", func() { 88 var expectedErr error 89 90 BeforeEach(func() { 91 expectedErr = errors.New("bind route failed") 92 fakeCloudControllerClient.BindRouteToApplicationReturns( 93 ccv2.Route{}, 94 ccv2.Warnings{"bind warning"}, 95 expectedErr) 96 }) 97 98 It("returns the error", func() { 99 warnings, err := actor.BindRouteToApplication("some-route-guid", "some-app-guid") 100 Expect(err).To(MatchError(expectedErr)) 101 Expect(warnings).To(ConsistOf("bind warning")) 102 }) 103 }) 104 }) 105 }) 106 107 Describe("CreateRoute", func() { 108 Context("when no errors are encountered", func() { 109 BeforeEach(func() { 110 fakeCloudControllerClient.CreateRouteReturns( 111 ccv2.Route{ 112 GUID: "some-route-guid", 113 Host: "some-host", 114 Path: "some-path", 115 Port: types.NullInt{IsSet: true, Value: 3333}, 116 DomainGUID: "some-domain-guid", 117 SpaceGUID: "some-space-guid", 118 }, 119 ccv2.Warnings{"create route warning"}, 120 nil) 121 }) 122 123 It("creates the route and returns all warnings", func() { 124 route, warnings, err := actor.CreateRoute(Route{ 125 Domain: Domain{ 126 Name: "some-domain", 127 GUID: "some-domain-guid", 128 }, 129 Host: "some-host", 130 Path: "/some-path", 131 Port: types.NullInt{IsSet: true, Value: 3333}, 132 SpaceGUID: "some-space-guid", 133 }, 134 true) 135 Expect(err).ToNot(HaveOccurred()) 136 Expect(warnings).To(ConsistOf("create route warning")) 137 Expect(route).To(Equal(Route{ 138 Domain: Domain{ 139 Name: "some-domain", 140 GUID: "some-domain-guid", 141 }, 142 GUID: "some-route-guid", 143 Host: "some-host", 144 Path: "some-path", 145 Port: types.NullInt{IsSet: true, Value: 3333}, 146 SpaceGUID: "some-space-guid", 147 })) 148 149 Expect(fakeCloudControllerClient.CreateRouteCallCount()).To(Equal(1)) 150 passedRoute, generatePort := fakeCloudControllerClient.CreateRouteArgsForCall(0) 151 Expect(passedRoute).To(Equal(ccv2.Route{ 152 DomainGUID: "some-domain-guid", 153 Host: "some-host", 154 Path: "/some-path", 155 Port: types.NullInt{IsSet: true, Value: 3333}, 156 SpaceGUID: "some-space-guid", 157 })) 158 Expect(generatePort).To(BeTrue()) 159 }) 160 }) 161 162 Context("when path does not start with /", func() { 163 It("prepends / to path", func() { 164 _, _, err := actor.CreateRoute( 165 Route{ 166 Domain: Domain{ 167 Name: "some-domain", 168 GUID: "some-domain-guid", 169 }, 170 Host: "some-host", 171 Path: "some-path", 172 Port: types.NullInt{IsSet: true, Value: 3333}, 173 SpaceGUID: "some-space-guid", 174 }, 175 true, 176 ) 177 Expect(err).ToNot(HaveOccurred()) 178 Expect(fakeCloudControllerClient.CreateRouteCallCount()).To(Equal(1)) 179 passedRoute, _ := fakeCloudControllerClient.CreateRouteArgsForCall(0) 180 Expect(passedRoute).To(Equal(ccv2.Route{ 181 DomainGUID: "some-domain-guid", 182 Host: "some-host", 183 Path: "/some-path", 184 Port: types.NullInt{IsSet: true, Value: 3333}, 185 SpaceGUID: "some-space-guid", 186 })) 187 }) 188 }) 189 190 Context("when is not provided", func() { 191 It("passes empty path", func() { 192 _, _, err := actor.CreateRoute( 193 Route{ 194 Domain: Domain{ 195 Name: "some-domain", 196 GUID: "some-domain-guid", 197 }, 198 Host: "some-host", 199 Port: types.NullInt{IsSet: true, Value: 3333}, 200 SpaceGUID: "some-space-guid", 201 }, 202 true, 203 ) 204 Expect(err).ToNot(HaveOccurred()) 205 Expect(fakeCloudControllerClient.CreateRouteCallCount()).To(Equal(1)) 206 passedRoute, _ := fakeCloudControllerClient.CreateRouteArgsForCall(0) 207 Expect(passedRoute).To(Equal(ccv2.Route{ 208 DomainGUID: "some-domain-guid", 209 Host: "some-host", 210 Port: types.NullInt{IsSet: true, Value: 3333}, 211 SpaceGUID: "some-space-guid", 212 })) 213 }) 214 }) 215 216 Context("when an error is encountered", func() { 217 var expectedErr error 218 219 BeforeEach(func() { 220 expectedErr = errors.New("bind route failed") 221 fakeCloudControllerClient.CreateRouteReturns( 222 ccv2.Route{}, 223 ccv2.Warnings{"create route warning"}, 224 expectedErr) 225 }) 226 227 It("returns the error", func() { 228 _, warnings, err := actor.CreateRoute(Route{}, true) 229 Expect(err).To(MatchError(expectedErr)) 230 Expect(warnings).To(ConsistOf("create route warning")) 231 }) 232 }) 233 }) 234 235 Describe("CreateRouteWithExistenceCheck", func() { 236 var ( 237 route Route 238 createdRoute Route 239 createRouteWarnings Warnings 240 createRouteErr error 241 ) 242 243 BeforeEach(func() { 244 fakeCloudControllerClient.GetSpacesReturns( 245 []ccv2.Space{ 246 { 247 GUID: "some-space-guid", 248 Name: "some-space", 249 AllowSSH: true, 250 SpaceQuotaDefinitionGUID: "some-space-quota-guid", 251 }, 252 }, 253 ccv2.Warnings{"get-space-warning"}, 254 nil) 255 256 fakeCloudControllerClient.CreateRouteReturns( 257 ccv2.Route{ 258 GUID: "some-route-guid", 259 Host: "some-host", 260 Path: "some-path", 261 Port: types.NullInt{IsSet: true, Value: 3333}, 262 DomainGUID: "some-domain-guid", 263 SpaceGUID: "some-space-guid", 264 }, 265 ccv2.Warnings{"create-route-warning"}, 266 nil) 267 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{}, ccv2.Warnings{"get-routes-warning"}, nil) 268 route = Route{ 269 Domain: Domain{ 270 Name: "some-domain", 271 GUID: "some-domain-guid", 272 }, 273 Host: "some-host", 274 Path: "some-path", 275 Port: types.NullInt{IsSet: true, Value: 3333}, 276 } 277 }) 278 279 JustBeforeEach(func() { 280 createdRoute, createRouteWarnings, createRouteErr = actor.CreateRouteWithExistenceCheck( 281 "some-org-guid", 282 "some-space", 283 route, 284 true) 285 }) 286 287 Context("when route does not exist", func() { 288 BeforeEach(func() { 289 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{}, ccv2.Warnings{"get-routes-warning"}, nil) 290 }) 291 292 It("creates the route and returns all warnings", func() { 293 Expect(createRouteErr).ToNot(HaveOccurred()) 294 Expect(createRouteWarnings).To(ConsistOf( 295 "get-space-warning", 296 "get-routes-warning", 297 "create-route-warning", 298 )) 299 300 Expect(createdRoute).To(Equal(Route{ 301 Domain: Domain{ 302 Name: "some-domain", 303 GUID: "some-domain-guid", 304 }, 305 GUID: "some-route-guid", 306 Host: "some-host", 307 Path: "some-path", 308 Port: types.NullInt{IsSet: true, Value: 3333}, 309 SpaceGUID: "some-space-guid", 310 })) 311 312 Expect(fakeCloudControllerClient.GetSpacesCallCount()).To(Equal(1)) 313 Expect(fakeCloudControllerClient.GetSpacesArgsForCall(0)).To(ConsistOf( 314 ccv2.Query{ 315 Filter: ccv2.OrganizationGUIDFilter, 316 Operator: ccv2.EqualOperator, 317 Values: []string{"some-org-guid"}, 318 }, 319 ccv2.Query{ 320 Filter: ccv2.NameFilter, 321 Operator: ccv2.EqualOperator, 322 Values: []string{"some-space"}, 323 })) 324 325 Expect(fakeCloudControllerClient.CreateRouteCallCount()).To(Equal(1)) 326 passedRoute, generatePort := fakeCloudControllerClient.CreateRouteArgsForCall(0) 327 Expect(passedRoute).To(Equal(ccv2.Route{ 328 DomainGUID: "some-domain-guid", 329 Host: "some-host", 330 Path: "/some-path", 331 Port: types.NullInt{IsSet: true, Value: 3333}, 332 SpaceGUID: "some-space-guid", 333 })) 334 Expect(generatePort).To(BeTrue()) 335 }) 336 337 Context("when creating route errors", func() { 338 var expectedErr error 339 340 BeforeEach(func() { 341 expectedErr = errors.New("bind route failed") 342 fakeCloudControllerClient.CreateRouteReturns( 343 ccv2.Route{}, 344 ccv2.Warnings{"create-route-warning"}, 345 expectedErr) 346 }) 347 348 It("returns the error and warnings", func() { 349 Expect(createRouteErr).To(MatchError(expectedErr)) 350 Expect(createRouteWarnings).To(ConsistOf("get-space-warning", "get-routes-warning", "create-route-warning")) 351 }) 352 }) 353 }) 354 355 Context("when route already exists", func() { 356 var foundRoute ccv2.Route 357 358 BeforeEach(func() { 359 foundRoute = ccv2.Route{ 360 DomainGUID: "some-domain-guid", 361 Host: "some-host", 362 Path: "some-path", 363 Port: types.NullInt{IsSet: true, Value: 3333}, 364 SpaceGUID: "some-space-guid", 365 } 366 fakeCloudControllerClient.GetRoutesReturns( 367 []ccv2.Route{foundRoute}, 368 ccv2.Warnings{"get-routes-warning"}, 369 nil) 370 fakeCloudControllerClient.GetSharedDomainReturns( 371 ccv2.Domain{Name: "some-domain", GUID: "some-domain-guid"}, 372 ccv2.Warnings{"get-domain-warning"}, 373 nil) 374 }) 375 376 It("returns the error and warnings", func() { 377 Expect(createRouteErr).To(MatchError(RouteAlreadyExistsError{ 378 Route: CCToActorRoute(foundRoute, Domain{Name: "some-domain", GUID: "some-domain-guid"}), 379 })) 380 Expect(createRouteWarnings).To(ConsistOf("get-space-warning", "get-routes-warning", "get-domain-warning")) 381 }) 382 }) 383 384 Context("when route domain does not have GUID", func() { 385 BeforeEach(func() { 386 route = Route{ 387 Domain: Domain{ 388 Name: "some-domain", 389 }, 390 Host: "some-host", 391 Path: "some-path", 392 Port: types.NullInt{IsSet: true, Value: 3333}, 393 } 394 }) 395 396 Context("when the domain exists", func() { 397 BeforeEach(func() { 398 fakeCloudControllerClient.GetSharedDomainsReturns( 399 []ccv2.Domain{}, 400 ccv2.Warnings{"get-shared-domains-warning"}, 401 nil, 402 ) 403 fakeCloudControllerClient.GetOrganizationPrivateDomainsReturns( 404 []ccv2.Domain{{Name: "some-domain", GUID: "some-requested-domain-guid"}}, 405 ccv2.Warnings{"get-private-domains-warning"}, 406 nil, 407 ) 408 }) 409 410 It("gets domain and finds route with fully instantiated domain", func() { 411 Expect(createRouteErr).ToNot(HaveOccurred()) 412 Expect(createRouteWarnings).To(ConsistOf( 413 "get-space-warning", 414 "get-shared-domains-warning", 415 "get-private-domains-warning", 416 "get-routes-warning", 417 "create-route-warning", 418 )) 419 Expect(createdRoute).To(Equal(Route{ 420 Domain: Domain{ 421 Name: "some-domain", 422 GUID: "some-requested-domain-guid", 423 }, 424 GUID: "some-route-guid", 425 Host: "some-host", 426 Path: "some-path", 427 Port: types.NullInt{IsSet: true, Value: 3333}, 428 SpaceGUID: "some-space-guid", 429 })) 430 Expect(fakeCloudControllerClient.GetSharedDomainsCallCount()).To(Equal(1)) 431 Expect(fakeCloudControllerClient.GetOrganizationPrivateDomainsCallCount()).To(Equal(1)) 432 orgGUID, queries := fakeCloudControllerClient.GetOrganizationPrivateDomainsArgsForCall(0) 433 Expect(orgGUID).To(Equal("some-org-guid")) 434 Expect(queries).To(HaveLen(1)) 435 Expect(queries[0]).To(Equal(ccv2.Query{ 436 Filter: ccv2.NameFilter, 437 Operator: ccv2.InOperator, 438 Values: []string{"some-domain"}, 439 })) 440 }) 441 }) 442 443 Context("when the domain doesn't exist", func() { 444 BeforeEach(func() { 445 fakeCloudControllerClient.GetSharedDomainsReturns( 446 []ccv2.Domain{}, 447 ccv2.Warnings{"get-shared-domains-warning"}, 448 nil, 449 ) 450 fakeCloudControllerClient.GetOrganizationPrivateDomainsReturns( 451 []ccv2.Domain{}, 452 ccv2.Warnings{"get-private-domains-warning"}, 453 nil, 454 ) 455 }) 456 457 It("returns all warnings and domain not found err", func() { 458 Expect(createRouteErr).To(Equal(DomainNotFoundError{Name: "some-domain"})) 459 Expect(createRouteWarnings).To(ConsistOf( 460 "get-space-warning", 461 "get-shared-domains-warning", 462 "get-private-domains-warning", 463 )) 464 }) 465 }) 466 }) 467 468 Context("when getting space errors", func() { 469 var expectedErr error 470 471 BeforeEach(func() { 472 expectedErr = errors.New("bind route failed") 473 fakeCloudControllerClient.GetSpacesReturns( 474 []ccv2.Space{}, 475 ccv2.Warnings{"get-space-warning"}, 476 expectedErr) 477 }) 478 479 It("returns the error and warnings", func() { 480 Expect(createRouteErr).To(MatchError(expectedErr)) 481 Expect(createRouteWarnings).To(ConsistOf("get-space-warning")) 482 }) 483 }) 484 485 Context("when getting routes errors", func() { 486 var expectedErr error 487 488 BeforeEach(func() { 489 expectedErr = errors.New("bind route failed") 490 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{}, ccv2.Warnings{"get-routes-warning"}, expectedErr) 491 }) 492 493 It("returns the error and warnings", func() { 494 Expect(createRouteErr).To(MatchError(expectedErr)) 495 Expect(createRouteWarnings).To(ConsistOf("get-space-warning", "get-routes-warning")) 496 }) 497 }) 498 }) 499 500 Describe("GetOrphanedRoutesBySpace", func() { 501 BeforeEach(func() { 502 fakeCloudControllerClient.GetRouteApplicationsStub = func(routeGUID string, queries ...ccv2.Query) ([]ccv2.Application, ccv2.Warnings, error) { 503 switch routeGUID { 504 case "orphaned-route-guid-1": 505 return []ccv2.Application{}, nil, nil 506 case "orphaned-route-guid-2": 507 return []ccv2.Application{}, nil, nil 508 case "not-orphaned-route-guid-3": 509 return []ccv2.Application{ 510 {GUID: "app-guid"}, 511 }, nil, nil 512 } 513 Fail("Unexpected route-guid") 514 return []ccv2.Application{}, nil, nil 515 } 516 }) 517 518 Context("when there are orphaned routes", func() { 519 BeforeEach(func() { 520 fakeCloudControllerClient.GetSpaceRoutesReturns([]ccv2.Route{ 521 { 522 GUID: "orphaned-route-guid-1", 523 DomainGUID: "some-domain-guid", 524 }, 525 { 526 GUID: "orphaned-route-guid-2", 527 DomainGUID: "some-other-domain-guid", 528 }, 529 { 530 GUID: "not-orphaned-route-guid-3", 531 DomainGUID: "not-orphaned-route-domain-guid", 532 }, 533 }, nil, nil) 534 fakeCloudControllerClient.GetSharedDomainStub = func(domainGUID string) (ccv2.Domain, ccv2.Warnings, error) { 535 switch domainGUID { 536 case "some-domain-guid": 537 return ccv2.Domain{ 538 GUID: "some-domain-guid", 539 Name: "some-domain.com", 540 }, nil, nil 541 case "some-other-domain-guid": 542 return ccv2.Domain{ 543 GUID: "some-other-domain-guid", 544 Name: "some-other-domain.com", 545 }, nil, nil 546 case "not-orphaned-route-domain-guid": 547 return ccv2.Domain{ 548 GUID: "not-orphaned-route-domain-guid", 549 Name: "not-orphaned-route-domain.com", 550 }, nil, nil 551 } 552 return ccv2.Domain{}, nil, errors.New("Unexpected domain GUID") 553 } 554 }) 555 556 It("returns the orphaned routes with the domain names", func() { 557 orphanedRoutes, _, err := actor.GetOrphanedRoutesBySpace("space-guid") 558 Expect(err).NotTo(HaveOccurred()) 559 Expect(orphanedRoutes).To(ConsistOf([]Route{ 560 { 561 GUID: "orphaned-route-guid-1", 562 Domain: Domain{ 563 Name: "some-domain.com", 564 GUID: "some-domain-guid", 565 }, 566 }, 567 { 568 GUID: "orphaned-route-guid-2", 569 Domain: Domain{ 570 Name: "some-other-domain.com", 571 GUID: "some-other-domain-guid", 572 }, 573 }, 574 })) 575 576 Expect(fakeCloudControllerClient.GetSpaceRoutesCallCount()).To(Equal(1)) 577 578 spaceGUID, queries := fakeCloudControllerClient.GetSpaceRoutesArgsForCall(0) 579 Expect(spaceGUID).To(Equal("space-guid")) 580 Expect(queries).To(BeNil()) 581 582 Expect(fakeCloudControllerClient.GetRouteApplicationsCallCount()).To(Equal(3)) 583 584 routeGUID, queries := fakeCloudControllerClient.GetRouteApplicationsArgsForCall(0) 585 Expect(routeGUID).To(Equal("orphaned-route-guid-1")) 586 Expect(queries).To(BeNil()) 587 588 routeGUID, queries = fakeCloudControllerClient.GetRouteApplicationsArgsForCall(1) 589 Expect(routeGUID).To(Equal("orphaned-route-guid-2")) 590 Expect(queries).To(BeNil()) 591 592 routeGUID, queries = fakeCloudControllerClient.GetRouteApplicationsArgsForCall(2) 593 Expect(routeGUID).To(Equal("not-orphaned-route-guid-3")) 594 Expect(queries).To(BeNil()) 595 }) 596 }) 597 598 Context("when there are no orphaned routes", func() { 599 var expectedErr OrphanedRoutesNotFoundError 600 601 BeforeEach(func() { 602 fakeCloudControllerClient.GetSpaceRoutesReturns([]ccv2.Route{ 603 ccv2.Route{GUID: "not-orphaned-route-guid-3"}, 604 }, nil, nil) 605 }) 606 607 It("returns an OrphanedRoutesNotFoundError", func() { 608 orphanedRoutes, _, err := actor.GetOrphanedRoutesBySpace("space-guid") 609 Expect(err).To(MatchError(expectedErr)) 610 Expect(orphanedRoutes).To(BeNil()) 611 612 Expect(fakeCloudControllerClient.GetSpaceRoutesCallCount()).To(Equal(1)) 613 614 spaceGUID, queries := fakeCloudControllerClient.GetSpaceRoutesArgsForCall(0) 615 Expect(spaceGUID).To(Equal("space-guid")) 616 Expect(queries).To(BeNil()) 617 618 Expect(fakeCloudControllerClient.GetRouteApplicationsCallCount()).To(Equal(1)) 619 620 routeGUID, queries := fakeCloudControllerClient.GetRouteApplicationsArgsForCall(0) 621 Expect(routeGUID).To(Equal("not-orphaned-route-guid-3")) 622 Expect(queries).To(BeNil()) 623 }) 624 }) 625 626 Context("when there are warnings", func() { 627 BeforeEach(func() { 628 fakeCloudControllerClient.GetSpaceRoutesReturns([]ccv2.Route{ 629 ccv2.Route{GUID: "route-guid-1"}, 630 ccv2.Route{GUID: "route-guid-2"}, 631 }, ccv2.Warnings{"get-routes-warning"}, nil) 632 fakeCloudControllerClient.GetRouteApplicationsReturns(nil, ccv2.Warnings{"get-applications-warning"}, nil) 633 fakeCloudControllerClient.GetSharedDomainReturns(ccv2.Domain{GUID: "some-guid"}, ccv2.Warnings{"get-shared-domain-warning"}, nil) 634 }) 635 636 It("returns all the warnings", func() { 637 _, warnings, err := actor.GetOrphanedRoutesBySpace("space-guid") 638 Expect(err).NotTo(HaveOccurred()) 639 Expect(warnings).To(ConsistOf("get-routes-warning", "get-applications-warning", "get-shared-domain-warning", "get-applications-warning", "get-shared-domain-warning")) 640 }) 641 }) 642 643 Context("when the spaces routes API request returns an error", func() { 644 var expectedErr error 645 646 BeforeEach(func() { 647 expectedErr = errors.New("spaces routes error") 648 fakeCloudControllerClient.GetSpaceRoutesReturns(nil, nil, expectedErr) 649 }) 650 651 It("returns the error", func() { 652 routes, _, err := actor.GetOrphanedRoutesBySpace("space-guid") 653 Expect(err).To(Equal(expectedErr)) 654 Expect(routes).To(BeNil()) 655 }) 656 }) 657 658 Context("when a route's applications API request returns an error", func() { 659 var expectedErr error 660 661 BeforeEach(func() { 662 expectedErr = errors.New("application error") 663 fakeCloudControllerClient.GetSpaceRoutesReturns([]ccv2.Route{ 664 ccv2.Route{GUID: "route-guid"}, 665 }, nil, nil) 666 fakeCloudControllerClient.GetRouteApplicationsReturns(nil, nil, expectedErr) 667 }) 668 669 It("returns the error", func() { 670 routes, _, err := actor.GetOrphanedRoutesBySpace("space-guid") 671 Expect(err).To(Equal(expectedErr)) 672 Expect(routes).To(BeNil()) 673 }) 674 }) 675 }) 676 677 Describe("DeleteRoute", func() { 678 Context("when the route exists", func() { 679 BeforeEach(func() { 680 fakeCloudControllerClient.DeleteRouteReturns(nil, nil) 681 }) 682 683 It("deletes the route", func() { 684 _, err := actor.DeleteRoute("some-route-guid") 685 Expect(err).NotTo(HaveOccurred()) 686 687 Expect(fakeCloudControllerClient.DeleteRouteCallCount()).To(Equal(1)) 688 Expect(fakeCloudControllerClient.DeleteRouteArgsForCall(0)).To(Equal("some-route-guid")) 689 }) 690 }) 691 692 Context("when the API returns both warnings and an error", func() { 693 var expectedErr error 694 695 BeforeEach(func() { 696 expectedErr = errors.New("bananahammock") 697 fakeCloudControllerClient.DeleteRouteReturns(ccv2.Warnings{"foo", "bar"}, expectedErr) 698 }) 699 700 It("returns both the warnings and the error", func() { 701 warnings, err := actor.DeleteRoute("some-route-guid") 702 Expect(err).To(MatchError(expectedErr)) 703 Expect(warnings).To(ConsistOf("foo", "bar")) 704 }) 705 }) 706 }) 707 708 Describe("GetApplicationRoutes", func() { 709 Context("when the CC API client does not return any errors", func() { 710 BeforeEach(func() { 711 fakeCloudControllerClient.GetApplicationRoutesReturns([]ccv2.Route{ 712 ccv2.Route{ 713 GUID: "route-guid-1", 714 SpaceGUID: "some-space-guid", 715 Host: "host", 716 Path: "/path", 717 Port: types.NullInt{IsSet: true, Value: 1234}, 718 DomainGUID: "domain-1-guid", 719 }, 720 ccv2.Route{ 721 GUID: "route-guid-2", 722 SpaceGUID: "some-space-guid", 723 Host: "host", 724 Path: "/path", 725 Port: types.NullInt{IsSet: true, Value: 1234}, 726 DomainGUID: "domain-2-guid", 727 }, 728 }, ccv2.Warnings{"get-application-routes-warning"}, nil) 729 730 fakeCloudControllerClient.GetSharedDomainReturnsOnCall(0, ccv2.Domain{Name: "domain.com"}, nil, nil) 731 fakeCloudControllerClient.GetSharedDomainReturnsOnCall(1, ccv2.Domain{Name: "other-domain.com"}, nil, nil) 732 }) 733 734 It("returns the application routes and any warnings", func() { 735 routes, warnings, err := actor.GetApplicationRoutes("application-guid") 736 Expect(fakeCloudControllerClient.GetApplicationRoutesCallCount()).To(Equal(1)) 737 Expect(fakeCloudControllerClient.GetApplicationRoutesArgsForCall(0)).To(Equal("application-guid")) 738 Expect(fakeCloudControllerClient.GetSharedDomainCallCount()).To(Equal(2)) 739 Expect(fakeCloudControllerClient.GetSharedDomainArgsForCall(0)).To(Equal("domain-1-guid")) 740 Expect(fakeCloudControllerClient.GetSharedDomainArgsForCall(1)).To(Equal("domain-2-guid")) 741 742 Expect(warnings).To(ConsistOf("get-application-routes-warning")) 743 Expect(err).NotTo(HaveOccurred()) 744 Expect(routes).To(ConsistOf([]Route{ 745 { 746 Domain: Domain{ 747 Name: "domain.com", 748 }, 749 GUID: "route-guid-1", 750 Host: "host", 751 Path: "/path", 752 Port: types.NullInt{IsSet: true, Value: 1234}, 753 SpaceGUID: "some-space-guid", 754 }, 755 { 756 Domain: Domain{ 757 Name: "other-domain.com", 758 }, 759 GUID: "route-guid-2", 760 Host: "host", 761 Path: "/path", 762 Port: types.NullInt{IsSet: true, Value: 1234}, 763 SpaceGUID: "some-space-guid", 764 }, 765 })) 766 }) 767 }) 768 769 Context("when the CC API client returns an error", func() { 770 Context("when getting application routes returns an error and warnings", func() { 771 BeforeEach(func() { 772 fakeCloudControllerClient.GetApplicationRoutesReturns( 773 []ccv2.Route{}, ccv2.Warnings{"application-routes-warning"}, errors.New("get-application-routes-error")) 774 }) 775 776 It("returns the error and warnings", func() { 777 routes, warnings, err := actor.GetApplicationRoutes("application-guid") 778 Expect(warnings).To(ConsistOf("application-routes-warning")) 779 Expect(err).To(MatchError("get-application-routes-error")) 780 Expect(routes).To(BeNil()) 781 }) 782 }) 783 784 Context("when getting the domain returns an error and warnings", func() { 785 BeforeEach(func() { 786 fakeCloudControllerClient.GetApplicationRoutesReturns([]ccv2.Route{ 787 ccv2.Route{ 788 GUID: "route-guid-1", 789 SpaceGUID: "some-space-guid", 790 Host: "host", 791 Path: "/path", 792 Port: types.NullInt{IsSet: true, Value: 1234}, 793 DomainGUID: "domain-1-guid", 794 }, 795 }, nil, nil) 796 fakeCloudControllerClient.GetSharedDomainReturns(ccv2.Domain{}, ccv2.Warnings{"domain-warning"}, errors.New("get-domain-error")) 797 }) 798 799 It("returns the error and warnings", func() { 800 routes, warnings, err := actor.GetApplicationRoutes("application-guid") 801 Expect(warnings).To(ConsistOf("domain-warning")) 802 Expect(err).To(MatchError("get-domain-error")) 803 Expect(routes).To(BeNil()) 804 805 Expect(fakeCloudControllerClient.GetSharedDomainCallCount()).To(Equal(1)) 806 Expect(fakeCloudControllerClient.GetSharedDomainArgsForCall(0)).To(Equal("domain-1-guid")) 807 }) 808 }) 809 }) 810 811 Context("when the CC API client returns warnings and no errors", func() { 812 BeforeEach(func() { 813 fakeCloudControllerClient.GetApplicationRoutesReturns([]ccv2.Route{ 814 ccv2.Route{ 815 GUID: "route-guid-1", 816 SpaceGUID: "some-space-guid", 817 Host: "host", 818 Path: "/path", 819 Port: types.NullInt{IsSet: true, Value: 1234}, 820 DomainGUID: "domain-1-guid", 821 }, 822 }, ccv2.Warnings{"application-routes-warning"}, nil) 823 fakeCloudControllerClient.GetSharedDomainReturns(ccv2.Domain{}, ccv2.Warnings{"domain-warning"}, nil) 824 }) 825 826 It("returns the warnings", func() { 827 _, warnings, _ := actor.GetApplicationRoutes("application-guid") 828 Expect(warnings).To(ConsistOf("application-routes-warning", "domain-warning")) 829 }) 830 }) 831 }) 832 833 Describe("GetSpaceRoutes", func() { 834 Context("when the CC API client does not return any errors", func() { 835 BeforeEach(func() { 836 fakeCloudControllerClient.GetSpaceRoutesReturns([]ccv2.Route{ 837 ccv2.Route{ 838 GUID: "route-guid-1", 839 SpaceGUID: "some-space-guid", 840 Host: "host", 841 Path: "/path", 842 Port: types.NullInt{IsSet: true, Value: 1234}, 843 DomainGUID: "domain-1-guid", 844 }, 845 ccv2.Route{ 846 GUID: "route-guid-2", 847 SpaceGUID: "some-space-guid", 848 Host: "host", 849 Path: "/path", 850 Port: types.NullInt{IsSet: true, Value: 1234}, 851 DomainGUID: "domain-2-guid", 852 }, 853 }, ccv2.Warnings{"get-space-routes-warning"}, nil) 854 fakeCloudControllerClient.GetSharedDomainReturnsOnCall(0, ccv2.Domain{Name: "domain.com"}, nil, nil) 855 fakeCloudControllerClient.GetSharedDomainReturnsOnCall(1, ccv2.Domain{Name: "other-domain.com"}, nil, nil) 856 }) 857 858 It("returns the space routes and any warnings", func() { 859 routes, warnings, err := actor.GetSpaceRoutes("space-guid") 860 Expect(fakeCloudControllerClient.GetSpaceRoutesCallCount()).To(Equal(1)) 861 Expect(fakeCloudControllerClient.GetSpaceRoutesArgsForCall(0)).To(Equal("space-guid")) 862 Expect(fakeCloudControllerClient.GetSharedDomainCallCount()).To(Equal(2)) 863 Expect(fakeCloudControllerClient.GetSharedDomainArgsForCall(0)).To(Equal("domain-1-guid")) 864 Expect(fakeCloudControllerClient.GetSharedDomainArgsForCall(1)).To(Equal("domain-2-guid")) 865 866 Expect(warnings).To(ConsistOf("get-space-routes-warning")) 867 Expect(err).NotTo(HaveOccurred()) 868 Expect(routes).To(ConsistOf([]Route{ 869 { 870 Domain: Domain{ 871 Name: "domain.com", 872 }, 873 GUID: "route-guid-1", 874 Host: "host", 875 Path: "/path", 876 Port: types.NullInt{IsSet: true, Value: 1234}, 877 SpaceGUID: "some-space-guid", 878 }, 879 { 880 Domain: Domain{ 881 Name: "other-domain.com", 882 }, 883 GUID: "route-guid-2", 884 Host: "host", 885 Path: "/path", 886 Port: types.NullInt{IsSet: true, Value: 1234}, 887 SpaceGUID: "some-space-guid", 888 }, 889 })) 890 }) 891 }) 892 893 Context("when the CC API client returns an error", func() { 894 Context("when getting space routes returns an error and warnings", func() { 895 BeforeEach(func() { 896 fakeCloudControllerClient.GetSpaceRoutesReturns( 897 []ccv2.Route{}, ccv2.Warnings{"space-routes-warning"}, errors.New("get-space-routes-error")) 898 }) 899 900 It("returns the error and warnings", func() { 901 routes, warnings, err := actor.GetSpaceRoutes("space-guid") 902 Expect(warnings).To(ConsistOf("space-routes-warning")) 903 Expect(err).To(MatchError("get-space-routes-error")) 904 Expect(routes).To(BeNil()) 905 }) 906 }) 907 908 Context("when getting the domain returns an error and warnings", func() { 909 BeforeEach(func() { 910 fakeCloudControllerClient.GetSpaceRoutesReturns([]ccv2.Route{ 911 ccv2.Route{ 912 GUID: "route-guid-1", 913 SpaceGUID: "some-space-guid", 914 Host: "host", 915 Path: "/path", 916 Port: types.NullInt{IsSet: true, Value: 1234}, 917 DomainGUID: "domain-1-guid", 918 }, 919 }, nil, nil) 920 fakeCloudControllerClient.GetSharedDomainReturns(ccv2.Domain{}, ccv2.Warnings{"domain-warning"}, errors.New("get-domain-error")) 921 }) 922 923 It("returns the error and warnings", func() { 924 routes, warnings, err := actor.GetSpaceRoutes("space-guid") 925 Expect(fakeCloudControllerClient.GetSharedDomainCallCount()).To(Equal(1)) 926 Expect(fakeCloudControllerClient.GetSharedDomainArgsForCall(0)).To(Equal("domain-1-guid")) 927 928 Expect(warnings).To(ConsistOf("domain-warning")) 929 Expect(err).To(MatchError("get-domain-error")) 930 Expect(routes).To(BeNil()) 931 }) 932 }) 933 }) 934 935 Context("when the CC API client returns warnings and no errors", func() { 936 BeforeEach(func() { 937 fakeCloudControllerClient.GetSpaceRoutesReturns([]ccv2.Route{ 938 ccv2.Route{ 939 GUID: "route-guid-1", 940 SpaceGUID: "some-space-guid", 941 Host: "host", 942 Path: "/path", 943 Port: types.NullInt{IsSet: true, Value: 1234}, 944 DomainGUID: "domain-1-guid", 945 }, 946 }, ccv2.Warnings{"space-routes-warning"}, nil) 947 fakeCloudControllerClient.GetSharedDomainReturns(ccv2.Domain{}, ccv2.Warnings{"domain-warning"}, nil) 948 }) 949 950 It("returns the warnings", func() { 951 _, warnings, _ := actor.GetSpaceRoutes("space-guid") 952 Expect(warnings).To(ConsistOf("space-routes-warning", "domain-warning")) 953 }) 954 }) 955 }) 956 957 Describe("GetRouteByHostAndDomain", func() { 958 var ( 959 host string 960 domainGUID string 961 962 route Route 963 warnings Warnings 964 executeErr error 965 ) 966 967 BeforeEach(func() { 968 host = "some-host" 969 domainGUID = "some-domain-guid" 970 }) 971 972 JustBeforeEach(func() { 973 route, warnings, executeErr = actor.GetRouteByHostAndDomain(host, domainGUID) 974 }) 975 976 Context("when finding the route is successful", func() { 977 BeforeEach(func() { 978 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{ 979 { 980 GUID: "route-guid-1", 981 SpaceGUID: "some-space-guid", 982 Host: "host", 983 Path: "/path", 984 Port: types.NullInt{IsSet: true, Value: 1234}, 985 DomainGUID: "domain-1-guid", 986 }, 987 }, ccv2.Warnings{"get-routes-warning"}, nil) 988 }) 989 990 Context("when finding the domain is successful", func() { 991 BeforeEach(func() { 992 fakeCloudControllerClient.GetSharedDomainReturns( 993 ccv2.Domain{ 994 Name: "domain.com", 995 }, ccv2.Warnings{"get-domain-warning"}, nil) 996 }) 997 998 It("returns the routes and any warnings", func() { 999 Expect(warnings).To(ConsistOf("get-routes-warning", "get-domain-warning")) 1000 Expect(executeErr).NotTo(HaveOccurred()) 1001 Expect(route).To(Equal(Route{ 1002 Domain: Domain{ 1003 Name: "domain.com", 1004 }, 1005 GUID: "route-guid-1", 1006 Host: "host", 1007 Path: "/path", 1008 Port: types.NullInt{IsSet: true, Value: 1234}, 1009 SpaceGUID: "some-space-guid", 1010 })) 1011 1012 Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(1)) 1013 Expect(fakeCloudControllerClient.GetRoutesArgsForCall(0)).To(Equal([]ccv2.Query{ 1014 {Filter: ccv2.HostFilter, Operator: ccv2.EqualOperator, Values: []string{host}}, 1015 {Filter: ccv2.DomainGUIDFilter, Operator: ccv2.EqualOperator, Values: []string{domainGUID}}, 1016 })) 1017 1018 Expect(fakeCloudControllerClient.GetSharedDomainCallCount()).To(Equal(1)) 1019 Expect(fakeCloudControllerClient.GetSharedDomainArgsForCall(0)).To(Equal("domain-1-guid")) 1020 }) 1021 }) 1022 1023 Context("when getting the domain returns an error and warnings", func() { 1024 var expectedErr error 1025 1026 BeforeEach(func() { 1027 expectedErr = errors.New("get-domain-error") 1028 fakeCloudControllerClient.GetSharedDomainReturns(ccv2.Domain{}, ccv2.Warnings{"get-domain-warning"}, expectedErr) 1029 }) 1030 1031 It("returns the error and warnings", func() { 1032 Expect(executeErr).To(MatchError(expectedErr)) 1033 Expect(warnings).To(ConsistOf("get-routes-warning", "get-domain-warning")) 1034 }) 1035 }) 1036 }) 1037 1038 Context("when getting routes returns an error and warnings", func() { 1039 var expectedErr error 1040 1041 BeforeEach(func() { 1042 expectedErr = errors.New("get-routes-err") 1043 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{}, ccv2.Warnings{"get-routes-warning"}, expectedErr) 1044 }) 1045 1046 It("returns the error and warnings", func() { 1047 Expect(executeErr).To(MatchError(expectedErr)) 1048 Expect(warnings).To(ConsistOf("get-routes-warning")) 1049 }) 1050 }) 1051 1052 Context("when no route is found", func() { 1053 BeforeEach(func() { 1054 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{}, ccv2.Warnings{"get-routes-warning"}, nil) 1055 }) 1056 1057 It("returns a RouteNotFoundError and warnings", func() { 1058 Expect(executeErr).To(MatchError(RouteNotFoundError{Host: host, DomainGUID: domainGUID})) 1059 Expect(warnings).To(ConsistOf("get-routes-warning")) 1060 }) 1061 }) 1062 }) 1063 1064 Describe("CheckRoute", func() { 1065 Context("when the API calls succeed", func() { 1066 BeforeEach(func() { 1067 fakeCloudControllerClient.CheckRouteReturns(true, ccv2.Warnings{"some-check-route-warnings"}, nil) 1068 }) 1069 1070 It("returns the bool and warnings", func() { 1071 exists, warnings, err := actor.CheckRoute(Route{ 1072 Host: "some-host", 1073 Domain: Domain{ 1074 GUID: "some-domain-guid", 1075 }, 1076 Path: "some-path", 1077 Port: types.NullInt{IsSet: true, Value: 42}, 1078 }) 1079 1080 Expect(err).ToNot(HaveOccurred()) 1081 Expect(warnings).To(ConsistOf("some-check-route-warnings")) 1082 Expect(exists).To(BeTrue()) 1083 1084 Expect(fakeCloudControllerClient.CheckRouteCallCount()).To(Equal(1)) 1085 Expect(fakeCloudControllerClient.CheckRouteArgsForCall(0)).To(Equal(ccv2.Route{ 1086 Host: "some-host", 1087 DomainGUID: "some-domain-guid", 1088 Path: "some-path", 1089 Port: types.NullInt{IsSet: true, Value: 42}, 1090 })) 1091 }) 1092 }) 1093 1094 Context("when the cc returns an error", func() { 1095 var expectedErr error 1096 1097 BeforeEach(func() { 1098 expectedErr = errors.New("booo") 1099 fakeCloudControllerClient.CheckRouteReturns(false, ccv2.Warnings{"some-check-route-warnings"}, expectedErr) 1100 }) 1101 1102 It("returns the bool and warnings", func() { 1103 exists, warnings, err := actor.CheckRoute(Route{ 1104 Host: "some-host", 1105 Domain: Domain{ 1106 GUID: "some-domain-guid", 1107 }, 1108 }) 1109 1110 Expect(err).To(MatchError(expectedErr)) 1111 Expect(warnings).To(ConsistOf("some-check-route-warnings")) 1112 Expect(exists).To(BeFalse()) 1113 }) 1114 }) 1115 }) 1116 1117 Describe("FindRouteBoundToSpaceWithSettings", func() { 1118 var ( 1119 route Route 1120 1121 returnedRoute Route 1122 warnings Warnings 1123 executeErr error 1124 ) 1125 1126 BeforeEach(func() { 1127 route = Route{ 1128 Domain: Domain{ 1129 Name: "some-domain.com", 1130 GUID: "some-domain-guid", 1131 }, 1132 Host: "some-host", 1133 SpaceGUID: "some-space-guid", 1134 } 1135 1136 fakeCloudControllerClient.GetSharedDomainReturns( 1137 ccv2.Domain{ 1138 GUID: "some-domain-guid", 1139 Name: "some-domain.com", 1140 }, 1141 ccv2.Warnings{"get domain warning"}, 1142 nil) 1143 }) 1144 1145 JustBeforeEach(func() { 1146 returnedRoute, warnings, executeErr = actor.FindRouteBoundToSpaceWithSettings(route) 1147 }) 1148 1149 Context("when the route exists in the current space", func() { 1150 var existingRoute Route 1151 1152 BeforeEach(func() { 1153 existingRoute = route 1154 existingRoute.GUID = "some-route-guid" 1155 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{ActorToCCRoute(existingRoute)}, ccv2.Warnings{"get route warning"}, nil) 1156 }) 1157 1158 It("returns the route", func() { 1159 Expect(executeErr).ToNot(HaveOccurred()) 1160 Expect(returnedRoute).To(Equal(existingRoute)) 1161 Expect(warnings).To(ConsistOf("get route warning", "get domain warning")) 1162 }) 1163 }) 1164 1165 Context("when the route exists in a different space", func() { 1166 Context("when the user has access to the route", func() { 1167 BeforeEach(func() { 1168 existingRoute := route 1169 existingRoute.GUID = "some-route-guid" 1170 existingRoute.SpaceGUID = "some-other-space-guid" 1171 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{ActorToCCRoute(existingRoute)}, ccv2.Warnings{"get route warning"}, nil) 1172 }) 1173 1174 It("returns a RouteInDifferentSpaceError", func() { 1175 Expect(executeErr).To(MatchError(RouteInDifferentSpaceError{Route: route.String()})) 1176 Expect(warnings).To(ConsistOf("get route warning", "get domain warning")) 1177 }) 1178 }) 1179 1180 Context("when the user does not have access to the route", func() { 1181 BeforeEach(func() { 1182 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{}, ccv2.Warnings{"get route warning"}, nil) 1183 fakeCloudControllerClient.CheckRouteReturns(true, ccv2.Warnings{"check route warning"}, nil) 1184 }) 1185 1186 It("returns a RouteInDifferentSpaceError", func() { 1187 Expect(executeErr).To(MatchError(RouteInDifferentSpaceError{Route: route.String()})) 1188 Expect(warnings).To(ConsistOf("get route warning", "check route warning")) 1189 }) 1190 }) 1191 }) 1192 1193 Context("when the route does not exist", func() { 1194 var expectedErr error 1195 1196 BeforeEach(func() { 1197 expectedErr = RouteNotFoundError{Host: route.Host, DomainGUID: route.Domain.GUID} 1198 fakeCloudControllerClient.GetRoutesReturns([]ccv2.Route{}, ccv2.Warnings{"get route warning"}, nil) 1199 fakeCloudControllerClient.CheckRouteReturns(false, ccv2.Warnings{"check route warning"}, nil) 1200 }) 1201 1202 It("returns the route", func() { 1203 Expect(executeErr).To(MatchError(expectedErr)) 1204 Expect(warnings).To(ConsistOf("get route warning", "check route warning")) 1205 }) 1206 }) 1207 1208 Context("when finding the route errors", func() { 1209 var expectedErr error 1210 1211 BeforeEach(func() { 1212 expectedErr = errors.New("booo") 1213 fakeCloudControllerClient.GetRoutesReturns(nil, ccv2.Warnings{"get route warning"}, expectedErr) 1214 }) 1215 1216 It("the error and warnings", func() { 1217 Expect(executeErr).To(MatchError(expectedErr)) 1218 Expect(warnings).To(ConsistOf("get route warning")) 1219 }) 1220 }) 1221 }) 1222 })