github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/service_plan_test.go (about) 1 package ccv3_test 2 3 import ( 4 "fmt" 5 "net/http" 6 7 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 8 . "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 9 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal" 11 "code.cloudfoundry.org/cli/resources" 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 . "github.com/onsi/gomega/ghttp" 15 ) 16 17 var _ = Describe("Service Plan", func() { 18 var client *Client 19 var query []Query 20 21 BeforeEach(func() { 22 client, _ = NewTestClient() 23 query = []Query{} 24 }) 25 26 Describe("GetServicePlanByGUID", func() { 27 const guid = "fake-guid" 28 29 var requester *ccv3fakes.FakeRequester 30 31 BeforeEach(func() { 32 requester = new(ccv3fakes.FakeRequester) 33 client, _ = NewFakeRequesterTestClient(requester) 34 }) 35 36 When("service plan exists", func() { 37 BeforeEach(func() { 38 requester.MakeRequestCalls(func(params RequestParams) (JobURL, Warnings, error) { 39 Expect(params.URIParams).To(BeEquivalentTo(map[string]string{"service_plan_guid": guid})) 40 Expect(params.RequestName).To(Equal(internal.GetServicePlanRequest)) 41 params.ResponseBody.(*resources.ServicePlan).GUID = guid 42 return "", Warnings{"one", "two"}, nil 43 }) 44 }) 45 46 It("returns the service offering with warnings", func() { 47 plan, warnings, err := client.GetServicePlanByGUID(guid) 48 Expect(err).ToNot(HaveOccurred()) 49 50 Expect(plan).To(Equal(resources.ServicePlan{ 51 GUID: guid, 52 })) 53 Expect(warnings).To(ConsistOf("one", "two")) 54 }) 55 }) 56 57 When("the cloud controller returns errors and warnings", func() { 58 BeforeEach(func() { 59 requester.MakeRequestReturns( 60 "", 61 Warnings{"one", "two"}, 62 ccerror.MultiError{ 63 ResponseCode: http.StatusTeapot, 64 Errors: []ccerror.V3Error{ 65 { 66 Code: 42424, 67 Detail: "Some detailed error message", 68 Title: "CF-SomeErrorTitle", 69 }, 70 { 71 Code: 11111, 72 Detail: "Some other detailed error message", 73 Title: "CF-SomeOtherErrorTitle", 74 }, 75 }, 76 }, 77 ) 78 }) 79 80 It("returns the error and all warnings", func() { 81 _, warnings, err := client.GetServicePlanByGUID(guid) 82 Expect(err).To(MatchError(ccerror.MultiError{ 83 ResponseCode: http.StatusTeapot, 84 Errors: []ccerror.V3Error{ 85 { 86 Code: 42424, 87 Detail: "Some detailed error message", 88 Title: "CF-SomeErrorTitle", 89 }, 90 { 91 Code: 11111, 92 Detail: "Some other detailed error message", 93 Title: "CF-SomeOtherErrorTitle", 94 }, 95 }, 96 })) 97 Expect(warnings).To(ConsistOf("one", "two")) 98 }) 99 }) 100 101 When("the guid is not specified", func() { 102 It("fails with an error", func() { 103 _, _, err := client.GetServicePlanByGUID("") 104 Expect(err).To(MatchError(ccerror.ServicePlanNotFound{})) 105 }) 106 }) 107 }) 108 109 Describe("GetServicePlans", func() { 110 var ( 111 plans []resources.ServicePlan 112 warnings Warnings 113 executeErr error 114 ) 115 116 JustBeforeEach(func() { 117 plans, warnings, executeErr = client.GetServicePlans(query...) 118 }) 119 120 When("service plans exist", func() { 121 BeforeEach(func() { 122 response1 := fmt.Sprintf(` 123 { 124 "pagination": { 125 "next": { 126 "href": "%s/v3/service_plans?names=myServicePlan&service_broker_names=myServiceBroker&service_offering_names=someOffering&page=2" 127 } 128 }, 129 "resources": [ 130 { 131 "guid": "service-plan-1-guid", 132 "name": "service-plan-1-name", 133 "description": "service-plan-1-description", 134 "available": true, 135 "free": true, 136 "visibility_type": "public", 137 "relationships": { 138 "service_offering": { 139 "data": { 140 "guid": "79d428b9-75b4-44db-addf-19c85c7f0f1e" 141 } 142 } 143 } 144 }, 145 { 146 "guid": "service-plan-2-guid", 147 "name": "service-plan-2-name", 148 "available": false, 149 "visibility_type": "admin", 150 "description": "service-plan-2-description", 151 "free": false, 152 "relationships": { 153 "service_offering": { 154 "data": { 155 "guid": "69d428b9-75b4-44db-addf-19c85c7f0f1e" 156 } 157 } 158 } 159 } 160 ] 161 }`, 162 server.URL()) 163 164 response2 := ` 165 { 166 "pagination": { 167 "next": { 168 "href": null 169 } 170 }, 171 "resources": [ 172 { 173 "guid": "service-plan-3-guid", 174 "name": "service-plan-3-name", 175 "visibility_type": "organization", 176 "description": "service-plan-3-description", 177 "available": true, 178 "free": true, 179 "relationships": { 180 "service_offering": { 181 "data": { 182 "guid": "59d428b9-75b4-44db-addf-19c85c7f0f1e" 183 } 184 } 185 } 186 } 187 ] 188 }` 189 190 server.AppendHandlers( 191 CombineHandlers( 192 VerifyRequest(http.MethodGet, "/v3/service_plans", "names=myServicePlan&service_broker_names=myServiceBroker&service_offering_names=someOffering"), 193 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 194 ), 195 CombineHandlers( 196 VerifyRequest(http.MethodGet, "/v3/service_plans", "names=myServicePlan&service_broker_names=myServiceBroker&service_offering_names=someOffering&page=2"), 197 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 198 ), 199 ) 200 201 query = []Query{ 202 { 203 Key: NameFilter, 204 Values: []string{"myServicePlan"}, 205 }, 206 { 207 Key: ServiceBrokerNamesFilter, 208 Values: []string{"myServiceBroker"}, 209 }, 210 { 211 Key: ServiceOfferingNamesFilter, 212 Values: []string{"someOffering"}, 213 }, 214 } 215 }) 216 217 It("returns a list of service plans with their associated warnings", func() { 218 Expect(executeErr).ToNot(HaveOccurred()) 219 220 Expect(plans).To(ConsistOf( 221 resources.ServicePlan{ 222 GUID: "service-plan-1-guid", 223 Name: "service-plan-1-name", 224 Description: "service-plan-1-description", 225 Available: true, 226 Free: true, 227 VisibilityType: "public", 228 ServiceOfferingGUID: "79d428b9-75b4-44db-addf-19c85c7f0f1e", 229 }, 230 resources.ServicePlan{ 231 GUID: "service-plan-2-guid", 232 Name: "service-plan-2-name", 233 Description: "service-plan-2-description", 234 Available: false, 235 Free: false, 236 VisibilityType: "admin", 237 ServiceOfferingGUID: "69d428b9-75b4-44db-addf-19c85c7f0f1e", 238 }, 239 resources.ServicePlan{ 240 GUID: "service-plan-3-guid", 241 Name: "service-plan-3-name", 242 Description: "service-plan-3-description", 243 Available: true, 244 Free: true, 245 VisibilityType: "organization", 246 ServiceOfferingGUID: "59d428b9-75b4-44db-addf-19c85c7f0f1e", 247 }, 248 )) 249 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 250 }) 251 }) 252 253 When("the cloud controller returns errors and warnings", func() { 254 BeforeEach(func() { 255 response := `{ 256 "errors": [ 257 { 258 "code": 42424, 259 "detail": "Some detailed error message", 260 "title": "CF-SomeErrorTitle" 261 }, 262 { 263 "code": 11111, 264 "detail": "Some other detailed error message", 265 "title": "CF-SomeOtherErrorTitle" 266 } 267 ] 268 }` 269 server.AppendHandlers( 270 CombineHandlers( 271 VerifyRequest(http.MethodGet, "/v3/service_plans"), 272 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 273 ), 274 ) 275 }) 276 277 It("returns the error and all warnings", func() { 278 Expect(executeErr).To(MatchError(ccerror.MultiError{ 279 ResponseCode: http.StatusTeapot, 280 Errors: []ccerror.V3Error{ 281 { 282 Code: 42424, 283 Detail: "Some detailed error message", 284 Title: "CF-SomeErrorTitle", 285 }, 286 { 287 Code: 11111, 288 Detail: "Some other detailed error message", 289 Title: "CF-SomeOtherErrorTitle", 290 }, 291 }, 292 })) 293 Expect(warnings).To(ConsistOf("this is a warning")) 294 }) 295 }) 296 }) 297 298 Describe("GetServicePlansWithSpaceAndOrganization", func() { 299 var ( 300 plans []ServicePlanWithSpaceAndOrganization 301 warnings Warnings 302 executeErr error 303 ) 304 305 JustBeforeEach(func() { 306 plans, warnings, executeErr = client.GetServicePlansWithSpaceAndOrganization(query...) 307 }) 308 309 When("when the query succeeds", func() { 310 BeforeEach(func() { 311 response1 := fmt.Sprintf(` 312 { 313 "pagination": { 314 "next": { 315 "href": "%s/v3/service_plans?include=space.organization&service_offering_names=someOffering&page=2" 316 } 317 }, 318 "resources": [ 319 { 320 "guid": "service-plan-1-guid", 321 "name": "service-plan-1-name", 322 "visibility_type": "public", 323 "relationships": { 324 "service_offering": { 325 "data": { 326 "guid": "79d428b9-75b4-44db-addf-19c85c7f0f1e" 327 } 328 } 329 } 330 }, 331 { 332 "guid": "service-plan-2-guid", 333 "name": "service-plan-2-name", 334 "visibility_type": "space", 335 "relationships": { 336 "service_offering": { 337 "data": { 338 "guid": "69d428b9-75b4-44db-addf-19c85c7f0f1e" 339 } 340 }, 341 "space": { 342 "data": { 343 "guid": "fake-space-guid" 344 } 345 } 346 } 347 } 348 ], 349 "included": { 350 "spaces": [ 351 { 352 "name": "matching-space", 353 "guid": "fake-space-guid", 354 "relationships": { 355 "organization": { 356 "data": { 357 "guid": "matching-org-guid" 358 } 359 } 360 } 361 }, 362 { 363 "name": "non-matching-space", 364 "guid": "fake-other-space-guid", 365 "relationships": { 366 "organization": { 367 "data": { 368 "guid": "other-org-guid" 369 } 370 } 371 } 372 } 373 ], 374 "organizations": [ 375 { 376 "name": "matching-org", 377 "guid": "matching-org-guid" 378 }, 379 { 380 "name": "non-matching-org", 381 "guid": "other-org-guid" 382 } 383 ] 384 } 385 }`, 386 server.URL()) 387 388 response2 := ` 389 { 390 "pagination": { 391 "next": { 392 "href": null 393 } 394 }, 395 "resources": [ 396 { 397 "guid": "service-plan-3-guid", 398 "name": "service-plan-3-name", 399 "visibility_type": "organization", 400 "relationships": { 401 "service_offering": { 402 "data": { 403 "guid": "59d428b9-75b4-44db-addf-19c85c7f0f1e" 404 } 405 } 406 } 407 } 408 ] 409 }` 410 411 server.AppendHandlers( 412 CombineHandlers( 413 VerifyRequest(http.MethodGet, "/v3/service_plans", "include=space.organization&service_offering_names=someOffering"), 414 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 415 ), 416 CombineHandlers( 417 VerifyRequest(http.MethodGet, "/v3/service_plans", "include=space.organization&service_offering_names=someOffering&page=2"), 418 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 419 ), 420 ) 421 422 query = []Query{ 423 { 424 Key: ServiceOfferingNamesFilter, 425 Values: []string{"someOffering"}, 426 }, 427 } 428 }) 429 430 It("returns space and org name for space-scoped plans", func() { 431 Expect(executeErr).ToNot(HaveOccurred()) 432 433 Expect(plans).To(ConsistOf( 434 ServicePlanWithSpaceAndOrganization{ 435 GUID: "service-plan-1-guid", 436 Name: "service-plan-1-name", 437 VisibilityType: "public", 438 ServiceOfferingGUID: "79d428b9-75b4-44db-addf-19c85c7f0f1e", 439 }, 440 ServicePlanWithSpaceAndOrganization{ 441 GUID: "service-plan-2-guid", 442 Name: "service-plan-2-name", 443 VisibilityType: "space", 444 ServiceOfferingGUID: "69d428b9-75b4-44db-addf-19c85c7f0f1e", 445 SpaceGUID: "fake-space-guid", 446 SpaceName: "matching-space", 447 OrganizationName: "matching-org", 448 }, 449 ServicePlanWithSpaceAndOrganization{ 450 GUID: "service-plan-3-guid", 451 Name: "service-plan-3-name", 452 VisibilityType: "organization", 453 ServiceOfferingGUID: "59d428b9-75b4-44db-addf-19c85c7f0f1e", 454 }, 455 )) 456 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 457 }) 458 }) 459 460 When("the query fails", func() { 461 BeforeEach(func() { 462 response := `{ 463 "errors": [ 464 { 465 "code": 42424, 466 "detail": "Some detailed error message", 467 "title": "CF-SomeErrorTitle" 468 }, 469 { 470 "code": 11111, 471 "detail": "Some other detailed error message", 472 "title": "CF-SomeOtherErrorTitle" 473 } 474 ] 475 }` 476 server.AppendHandlers( 477 CombineHandlers( 478 VerifyRequest(http.MethodGet, "/v3/service_plans", "include=space.organization"), 479 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 480 ), 481 ) 482 }) 483 484 It("returns the error and all warnings", func() { 485 Expect(executeErr).To(MatchError(ccerror.MultiError{ 486 ResponseCode: http.StatusTeapot, 487 Errors: []ccerror.V3Error{ 488 { 489 Code: 42424, 490 Detail: "Some detailed error message", 491 Title: "CF-SomeErrorTitle", 492 }, 493 { 494 Code: 11111, 495 Detail: "Some other detailed error message", 496 Title: "CF-SomeOtherErrorTitle", 497 }, 498 }, 499 })) 500 Expect(warnings).To(ConsistOf("this is a warning")) 501 }) 502 }) 503 }) 504 505 Describe("GetServicePlansWithOfferings", func() { 506 var ( 507 offerings []ServiceOfferingWithPlans 508 warnings Warnings 509 executeErr error 510 ) 511 512 JustBeforeEach(func() { 513 offerings, warnings, executeErr = client.GetServicePlansWithOfferings(query...) 514 }) 515 516 When("when the query succeeds", func() { 517 BeforeEach(func() { 518 519 response1Template := ` 520 { 521 "pagination": { 522 "next": { 523 "href": "%s/v3/service_plans?include=service_offering&space_guids=some-space-guid&organization_guids=some-org-guid&fields[service_offering.service_broker]=name,guid&page=2" 524 } 525 }, 526 "resources": [ 527 { 528 "guid": "service-plan-1-guid", 529 "name": "service-plan-1-name", 530 "description": "service-plan-1-description", 531 "available": true, 532 "free": true, 533 "relationships": { 534 "service_offering": { 535 "data": { 536 "guid": "79d428b9-75b4-44db-addf-19c85c7f0f1e" 537 } 538 } 539 } 540 }, 541 { 542 "guid": "service-plan-2-guid", 543 "name": "service-plan-2-name", 544 "description": "service-plan-2-description", 545 "available": false, 546 "free": false, 547 "costs": [ 548 { 549 "amount": 649.00, 550 "currency": "USD", 551 "unit": "MONTHLY" 552 }, 553 { 554 "amount": 649.123, 555 "currency": "GBP", 556 "unit": "MONTHLY" 557 } 558 ], 559 "broker_catalog": { 560 "metadata": { 561 "costs":[ 562 { 563 "amount": { 564 "usd": 649.0, 565 "gpb": 649.0 566 }, 567 "unit": "MONTHLY" 568 } 569 ] 570 } 571 }, 572 "relationships": { 573 "service_offering": { 574 "data": { 575 "guid": "69d428b9-75b4-44db-addf-19c85c7f0f1e" 576 } 577 } 578 } 579 } 580 ], 581 "included": { 582 "service_offerings": [ 583 { 584 "name": "service-offering-1", 585 "guid": "79d428b9-75b4-44db-addf-19c85c7f0f1e", 586 "description": "something about service offering 1", 587 "relationships": { 588 "service_broker": { 589 "data": { 590 "guid": "service-broker-1-guid" 591 } 592 } 593 } 594 }, 595 { 596 "name": "service-offering-2", 597 "guid": "69d428b9-75b4-44db-addf-19c85c7f0f1e", 598 "description": "something about service offering 2", 599 "relationships": { 600 "service_broker": { 601 "data": { 602 "guid": "service-broker-2-guid" 603 } 604 } 605 } 606 } 607 ], 608 "service_brokers": [ 609 { 610 "name": "service-broker-1", 611 "guid": "service-broker-1-guid" 612 }, 613 { 614 "name": "service-broker-2", 615 "guid": "service-broker-2-guid" 616 } 617 ] 618 } 619 }` 620 621 response1 := fmt.Sprintf(response1Template, server.URL()) 622 response2 := ` 623 { 624 "pagination": { 625 "next": { 626 "href": null 627 } 628 }, 629 "resources": [ 630 { 631 "guid": "service-plan-3-guid", 632 "name": "service-plan-3-name", 633 "description": "service-plan-3-description", 634 "available": true, 635 "free": true, 636 "relationships": { 637 "service_offering": { 638 "data": { 639 "guid": "79d428b9-75b4-44db-addf-19c85c7f0f1e" 640 } 641 } 642 } 643 } 644 ], 645 "included": { 646 "service_offerings": [ 647 { 648 "name": "service-offering-1", 649 "guid": "79d428b9-75b4-44db-addf-19c85c7f0f1e", 650 "description": "something about service offering 1", 651 "relationships": { 652 "service_broker": { 653 "data": { 654 "guid": "service-broker-1-guid" 655 } 656 } 657 } 658 } 659 ], 660 "service_brokers": [ 661 { 662 "name": "service-broker-1", 663 "guid": "service-broker-1-guid" 664 } 665 ] 666 } 667 }` 668 669 server.AppendHandlers( 670 CombineHandlers( 671 VerifyRequest(http.MethodGet, "/v3/service_plans", "include=service_offering&space_guids=some-space-guid&organization_guids=some-org-guid&fields[service_offering.service_broker]=name,guid"), 672 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 673 ), 674 CombineHandlers( 675 VerifyRequest(http.MethodGet, "/v3/service_plans", "include=service_offering&space_guids=some-space-guid&organization_guids=some-org-guid&fields[service_offering.service_broker]=name,guid&page=2"), 676 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 677 ), 678 ) 679 680 query = []Query{ 681 { 682 Key: OrganizationGUIDFilter, 683 Values: []string{"some-org-guid"}, 684 }, 685 { 686 Key: SpaceGUIDFilter, 687 Values: []string{"some-space-guid"}, 688 }, 689 } 690 }) 691 692 It("returns service offerings and service plans", func() { 693 Expect(executeErr).NotTo(HaveOccurred()) 694 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 695 Expect(offerings).To(Equal([]ServiceOfferingWithPlans{ 696 { 697 GUID: "79d428b9-75b4-44db-addf-19c85c7f0f1e", 698 Name: "service-offering-1", 699 Description: "something about service offering 1", 700 ServiceBrokerName: "service-broker-1", 701 Plans: []resources.ServicePlan{ 702 { 703 GUID: "service-plan-1-guid", 704 Name: "service-plan-1-name", 705 Description: "service-plan-1-description", 706 Available: true, 707 Free: true, 708 ServiceOfferingGUID: "79d428b9-75b4-44db-addf-19c85c7f0f1e", 709 }, 710 { 711 GUID: "service-plan-3-guid", 712 Name: "service-plan-3-name", 713 Description: "service-plan-3-description", 714 Available: true, 715 Free: true, 716 ServiceOfferingGUID: "79d428b9-75b4-44db-addf-19c85c7f0f1e", 717 }, 718 }, 719 }, 720 { 721 GUID: "69d428b9-75b4-44db-addf-19c85c7f0f1e", 722 Name: "service-offering-2", 723 Description: "something about service offering 2", 724 ServiceBrokerName: "service-broker-2", 725 Plans: []resources.ServicePlan{ 726 { 727 GUID: "service-plan-2-guid", 728 Name: "service-plan-2-name", 729 Available: false, 730 Description: "service-plan-2-description", 731 Free: false, 732 Costs: []resources.ServicePlanCost{ 733 { 734 Amount: 649.00, 735 Currency: "USD", 736 Unit: "MONTHLY", 737 }, 738 { 739 Amount: 649.123, 740 Currency: "GBP", 741 Unit: "MONTHLY", 742 }, 743 }, 744 ServiceOfferingGUID: "69d428b9-75b4-44db-addf-19c85c7f0f1e", 745 }, 746 }, 747 }, 748 })) 749 }) 750 }) 751 752 When("the query fails", func() { 753 BeforeEach(func() { 754 response := `{ 755 "errors": [ 756 { 757 "code": 42424, 758 "detail": "Some detailed error message", 759 "title": "CF-SomeErrorTitle" 760 }, 761 { 762 "code": 11111, 763 "detail": "Some other detailed error message", 764 "title": "CF-SomeOtherErrorTitle" 765 } 766 ] 767 }` 768 server.AppendHandlers( 769 CombineHandlers( 770 VerifyRequest(http.MethodGet, "/v3/service_plans", "include=service_offering&fields[service_offering.service_broker]=name,guid"), 771 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 772 ), 773 ) 774 }) 775 776 It("returns the error and all warnings", func() { 777 Expect(executeErr).To(MatchError(ccerror.MultiError{ 778 ResponseCode: http.StatusTeapot, 779 Errors: []ccerror.V3Error{ 780 { 781 Code: 42424, 782 Detail: "Some detailed error message", 783 Title: "CF-SomeErrorTitle", 784 }, 785 { 786 Code: 11111, 787 Detail: "Some other detailed error message", 788 Title: "CF-SomeOtherErrorTitle", 789 }, 790 }, 791 })) 792 Expect(warnings).To(ConsistOf("this is a warning")) 793 }) 794 }) 795 796 }) 797 })