github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/organization_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/resources" 10 . "code.cloudfoundry.org/cli/resources" 11 "code.cloudfoundry.org/cli/types" 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 . "github.com/onsi/gomega/ghttp" 15 ) 16 17 var _ = Describe("Organizations", func() { 18 var client *Client 19 20 BeforeEach(func() { 21 client, _ = NewTestClient() 22 }) 23 24 Describe("GetDefaultDomain", func() { 25 var ( 26 defaultDomain resources.Domain 27 warnings Warnings 28 executeErr error 29 orgGUID = "some-org-guid" 30 ) 31 32 JustBeforeEach(func() { 33 defaultDomain, warnings, executeErr = client.GetDefaultDomain(orgGUID) 34 }) 35 36 When("organizations exist", func() { 37 BeforeEach(func() { 38 response1 := fmt.Sprintf(` 39 { 40 "name": "domain-name-1", 41 "guid": "domain-guid-1", 42 "relationships": { 43 "organization": { 44 "data": { 45 "guid": "some-org-guid" 46 } 47 } 48 }, 49 "internal": false 50 }`) 51 52 server.AppendHandlers( 53 CombineHandlers( 54 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/organizations/%s/domains/default", orgGUID)), 55 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 56 ), 57 ) 58 }) 59 60 It("returns the queried organizations and all warnings", func() { 61 Expect(executeErr).NotTo(HaveOccurred()) 62 63 Expect(defaultDomain).To(Equal( 64 resources.Domain{Name: "domain-name-1", GUID: "domain-guid-1", Internal: types.NullBool{IsSet: true, Value: false}, 65 OrganizationGUID: "some-org-guid"}, 66 )) 67 Expect(warnings).To(ConsistOf("this is a warning")) 68 }) 69 }) 70 71 When("the cloud controller returns errors and warnings", func() { 72 BeforeEach(func() { 73 response := `{ 74 "errors": [ 75 { 76 "code": 10008, 77 "detail": "The request is semantically invalid: command presence", 78 "title": "CF-UnprocessableEntity" 79 }, 80 { 81 "code": 10010, 82 "detail": "Org not found", 83 "title": "CF-ResourceNotFound" 84 } 85 ] 86 }` 87 server.AppendHandlers( 88 CombineHandlers( 89 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/organizations/%s/domains/default", orgGUID)), 90 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 91 ), 92 ) 93 }) 94 95 It("returns the error and all warnings", func() { 96 Expect(executeErr).To(MatchError(ccerror.MultiError{ 97 ResponseCode: http.StatusTeapot, 98 Errors: []ccerror.V3Error{ 99 { 100 Code: 10008, 101 Detail: "The request is semantically invalid: command presence", 102 Title: "CF-UnprocessableEntity", 103 }, 104 { 105 Code: 10010, 106 Detail: "Org not found", 107 Title: "CF-ResourceNotFound", 108 }, 109 }, 110 })) 111 Expect(warnings).To(ConsistOf("this is a warning")) 112 }) 113 }) 114 }) 115 116 Describe("GetIsolationSegmentOrganizations", func() { 117 var ( 118 organizations []Organization 119 warnings Warnings 120 executeErr error 121 ) 122 123 JustBeforeEach(func() { 124 organizations, warnings, executeErr = client.GetIsolationSegmentOrganizations("some-iso-guid") 125 }) 126 127 When("organizations exist", func() { 128 BeforeEach(func() { 129 response1 := fmt.Sprintf(`{ 130 "pagination": { 131 "next": { 132 "href": "%s/v3/isolation_segments/some-iso-guid/organizations?page=2&per_page=2" 133 } 134 }, 135 "resources": [ 136 { 137 "name": "org-name-1", 138 "guid": "org-guid-1" 139 }, 140 { 141 "name": "org-name-2", 142 "guid": "org-guid-2" 143 } 144 ] 145 }`, server.URL()) 146 response2 := `{ 147 "pagination": { 148 "next": null 149 }, 150 "resources": [ 151 { 152 "name": "org-name-3", 153 "guid": "org-guid-3" 154 } 155 ] 156 }` 157 server.AppendHandlers( 158 CombineHandlers( 159 VerifyRequest(http.MethodGet, "/v3/isolation_segments/some-iso-guid/organizations"), 160 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 161 ), 162 ) 163 server.AppendHandlers( 164 CombineHandlers( 165 VerifyRequest(http.MethodGet, "/v3/isolation_segments/some-iso-guid/organizations", "page=2&per_page=2"), 166 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 167 ), 168 ) 169 }) 170 171 It("returns the queried organizations and all warnings", func() { 172 Expect(executeErr).NotTo(HaveOccurred()) 173 174 Expect(organizations).To(ConsistOf( 175 Organization{Name: "org-name-1", GUID: "org-guid-1"}, 176 Organization{Name: "org-name-2", GUID: "org-guid-2"}, 177 Organization{Name: "org-name-3", GUID: "org-guid-3"}, 178 )) 179 Expect(warnings).To(ConsistOf("this is a warning", "this is another warning")) 180 }) 181 }) 182 183 When("the cloud controller returns errors and warnings", func() { 184 BeforeEach(func() { 185 response := `{ 186 "errors": [ 187 { 188 "code": 10008, 189 "detail": "The request is semantically invalid: command presence", 190 "title": "CF-UnprocessableEntity" 191 }, 192 { 193 "code": 10010, 194 "detail": "Isolation segment not found", 195 "title": "CF-ResourceNotFound" 196 } 197 ] 198 }` 199 server.AppendHandlers( 200 CombineHandlers( 201 VerifyRequest(http.MethodGet, "/v3/isolation_segments/some-iso-guid/organizations"), 202 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 203 ), 204 ) 205 }) 206 207 It("returns the error and all warnings", func() { 208 Expect(executeErr).To(MatchError(ccerror.MultiError{ 209 ResponseCode: http.StatusTeapot, 210 Errors: []ccerror.V3Error{ 211 { 212 Code: 10008, 213 Detail: "The request is semantically invalid: command presence", 214 Title: "CF-UnprocessableEntity", 215 }, 216 { 217 Code: 10010, 218 Detail: "Isolation segment not found", 219 Title: "CF-ResourceNotFound", 220 }, 221 }, 222 })) 223 Expect(warnings).To(ConsistOf("this is a warning")) 224 }) 225 }) 226 }) 227 228 Describe("GetOrganization", func() { 229 var ( 230 organization Organization 231 warnings Warnings 232 executeErr error 233 ) 234 235 JustBeforeEach(func() { 236 organization, warnings, executeErr = client.GetOrganization("some-org-guid") 237 }) 238 239 When("organization exists", func() { 240 BeforeEach(func() { 241 response := `{ 242 "name": "some-org-name", 243 "guid": "some-org-guid", 244 "relationships": { 245 "quota": { 246 "data": { 247 "guid": "some-org-quota-guid" 248 } 249 } 250 } 251 }` 252 253 server.AppendHandlers( 254 CombineHandlers( 255 VerifyRequest(http.MethodGet, "/v3/organizations/some-org-guid"), 256 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 257 ), 258 ) 259 }) 260 261 It("returns the queried organization and all warnings", func() { 262 Expect(executeErr).NotTo(HaveOccurred()) 263 Expect(organization).To(Equal(Organization{ 264 Name: "some-org-name", 265 GUID: "some-org-guid", 266 QuotaGUID: "some-org-quota-guid", 267 })) 268 Expect(warnings).To(ConsistOf("this is a warning")) 269 }) 270 }) 271 272 When("the cloud controller returns errors and warnings", func() { 273 BeforeEach(func() { 274 response := `{ 275 "errors": [ 276 { 277 "code": 10008, 278 "detail": "The request is semantically invalid: command presence", 279 "title": "CF-UnprocessableEntity" 280 }, 281 { 282 "code": 10010, 283 "detail": "Org not found", 284 "title": "CF-ResourceNotFound" 285 } 286 ] 287 }` 288 289 server.AppendHandlers( 290 CombineHandlers( 291 VerifyRequest(http.MethodGet, "/v3/organizations/some-org-guid"), 292 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 293 ), 294 ) 295 }) 296 297 It("returns the error and all warnings", func() { 298 Expect(executeErr).To(MatchError(ccerror.MultiError{ 299 ResponseCode: http.StatusTeapot, 300 Errors: []ccerror.V3Error{ 301 { 302 Code: 10008, 303 Detail: "The request is semantically invalid: command presence", 304 Title: "CF-UnprocessableEntity", 305 }, 306 { 307 Code: 10010, 308 Detail: "Org not found", 309 Title: "CF-ResourceNotFound", 310 }, 311 }, 312 })) 313 Expect(warnings).To(ConsistOf("this is a warning")) 314 }) 315 }) 316 }) 317 318 Describe("GetOrganizations", func() { 319 var ( 320 organizations []Organization 321 warnings Warnings 322 executeErr error 323 ) 324 325 JustBeforeEach(func() { 326 organizations, warnings, executeErr = client.GetOrganizations(Query{ 327 Key: NameFilter, 328 Values: []string{"some-org-name"}, 329 }) 330 }) 331 332 When("organizations exist", func() { 333 BeforeEach(func() { 334 response1 := fmt.Sprintf(`{ 335 "pagination": { 336 "next": { 337 "href": "%s/v3/organizations?names=some-org-name&page=2&per_page=2" 338 } 339 }, 340 "resources": [ 341 { 342 "name": "org-name-1", 343 "guid": "org-guid-1" 344 }, 345 { 346 "name": "org-name-2", 347 "guid": "org-guid-2" 348 } 349 ] 350 }`, server.URL()) 351 response2 := `{ 352 "pagination": { 353 "next": null 354 }, 355 "resources": [ 356 { 357 "name": "org-name-3", 358 "guid": "org-guid-3" 359 } 360 ] 361 }` 362 server.AppendHandlers( 363 CombineHandlers( 364 VerifyRequest(http.MethodGet, "/v3/organizations", "names=some-org-name"), 365 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 366 ), 367 ) 368 server.AppendHandlers( 369 CombineHandlers( 370 VerifyRequest(http.MethodGet, "/v3/organizations", "names=some-org-name&page=2&per_page=2"), 371 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 372 ), 373 ) 374 }) 375 376 It("returns the queried organizations and all warnings", func() { 377 Expect(executeErr).NotTo(HaveOccurred()) 378 379 Expect(organizations).To(ConsistOf( 380 Organization{Name: "org-name-1", GUID: "org-guid-1"}, 381 Organization{Name: "org-name-2", GUID: "org-guid-2"}, 382 Organization{Name: "org-name-3", GUID: "org-guid-3"}, 383 )) 384 Expect(warnings).To(ConsistOf("this is a warning", "this is another warning")) 385 }) 386 }) 387 388 When("the cloud controller returns errors and warnings", func() { 389 BeforeEach(func() { 390 response := `{ 391 "errors": [ 392 { 393 "code": 10008, 394 "detail": "The request is semantically invalid: command presence", 395 "title": "CF-UnprocessableEntity" 396 }, 397 { 398 "code": 10010, 399 "detail": "Org not found", 400 "title": "CF-ResourceNotFound" 401 } 402 ] 403 }` 404 server.AppendHandlers( 405 CombineHandlers( 406 VerifyRequest(http.MethodGet, "/v3/organizations"), 407 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 408 ), 409 ) 410 }) 411 412 It("returns the error and all warnings", func() { 413 Expect(executeErr).To(MatchError(ccerror.MultiError{ 414 ResponseCode: http.StatusTeapot, 415 Errors: []ccerror.V3Error{ 416 { 417 Code: 10008, 418 Detail: "The request is semantically invalid: command presence", 419 Title: "CF-UnprocessableEntity", 420 }, 421 { 422 Code: 10010, 423 Detail: "Org not found", 424 Title: "CF-ResourceNotFound", 425 }, 426 }, 427 })) 428 Expect(warnings).To(ConsistOf("this is a warning")) 429 }) 430 }) 431 }) 432 433 Describe("CreateOrganization", func() { 434 var ( 435 createdOrg Organization 436 warnings Warnings 437 executeErr error 438 ) 439 440 JustBeforeEach(func() { 441 createdOrg, warnings, executeErr = client.CreateOrganization("some-org-name") 442 }) 443 444 When("the organization is created successfully", func() { 445 BeforeEach(func() { 446 response := `{ 447 "guid": "some-org-guid", 448 "name": "some-org-name" 449 }` 450 451 expectedBody := map[string]interface{}{ 452 "name": "some-org-name", 453 "suspended": false, 454 } 455 456 server.AppendHandlers( 457 CombineHandlers( 458 VerifyRequest(http.MethodPost, "/v3/organizations"), 459 VerifyJSONRepresenting(expectedBody), 460 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 461 ), 462 ) 463 }) 464 465 It("returns the created org", func() { 466 Expect(executeErr).ToNot(HaveOccurred()) 467 Expect(warnings).To(ConsistOf("this is a warning")) 468 Expect(createdOrg).To(Equal(Organization{ 469 GUID: "some-org-guid", 470 Name: "some-org-name", 471 })) 472 }) 473 }) 474 475 When("an organization with the same name already exists", func() { 476 BeforeEach(func() { 477 response := `{ 478 "errors": [ 479 { 480 "detail": "Organization 'some-org-name' already exists.", 481 "title": "CF-UnprocessableEntity", 482 "code": 10008 483 } 484 ] 485 }` 486 487 expectedBody := map[string]interface{}{ 488 "name": "some-org-name", 489 "suspended": false, 490 } 491 492 server.AppendHandlers( 493 CombineHandlers( 494 VerifyRequest(http.MethodPost, "/v3/organizations"), 495 VerifyJSONRepresenting(expectedBody), 496 RespondWith(http.StatusUnprocessableEntity, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 497 ), 498 ) 499 }) 500 501 It("returns a meaningful organization-name-taken error", func() { 502 Expect(executeErr).To(MatchError(ccerror.OrganizationNameTakenError{ 503 UnprocessableEntityError: ccerror.UnprocessableEntityError{ 504 Message: "Organization 'some-org-name' already exists.", 505 }, 506 })) 507 Expect(warnings).To(ConsistOf("this is a warning")) 508 }) 509 }) 510 511 When("creating the org fails", func() { 512 BeforeEach(func() { 513 response := `{ 514 "errors": [ 515 { 516 "detail": "Fail", 517 "title": "CF-SomeError", 518 "code": 10002 519 }, 520 { 521 "detail": "Something went terribly wrong", 522 "title": "CF-UnknownError", 523 "code": 10001 524 } 525 ] 526 }` 527 528 expectedBody := map[string]interface{}{ 529 "name": "some-org-name", 530 "suspended": false, 531 } 532 533 server.AppendHandlers( 534 CombineHandlers( 535 VerifyRequest(http.MethodPost, "/v3/organizations"), 536 VerifyJSONRepresenting(expectedBody), 537 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 538 ), 539 ) 540 }) 541 542 It("returns an error", func() { 543 Expect(executeErr).To(MatchError(ccerror.MultiError{ 544 ResponseCode: http.StatusTeapot, 545 Errors: []ccerror.V3Error{ 546 { 547 Code: 10002, 548 Detail: "Fail", 549 Title: "CF-SomeError", 550 }, 551 { 552 Code: 10001, 553 Detail: "Something went terribly wrong", 554 Title: "CF-UnknownError", 555 }, 556 }, 557 })) 558 Expect(warnings).To(ConsistOf("this is a warning")) 559 }) 560 }) 561 }) 562 563 Describe("UpdateOrganization", func() { 564 var ( 565 orgToUpdate Organization 566 updatedOrg Organization 567 warnings Warnings 568 executeErr error 569 ) 570 571 JustBeforeEach(func() { 572 updatedOrg, warnings, executeErr = client.UpdateOrganization(orgToUpdate) 573 }) 574 575 When("the organization is updated successfully", func() { 576 BeforeEach(func() { 577 response := `{ 578 "guid": "some-org-guid", 579 "name": "some-org-name", 580 "metadata": { 581 "labels": { 582 "k1":"v1", 583 "k2":"v2" 584 } 585 } 586 }` 587 588 expectedBody := map[string]interface{}{ 589 "name": "some-org-name", 590 "suspended": false, 591 "metadata": map[string]interface{}{ 592 "labels": map[string]string{ 593 "k1": "v1", 594 "k2": "v2", 595 }, 596 }, 597 } 598 599 server.AppendHandlers( 600 CombineHandlers( 601 VerifyRequest(http.MethodPatch, "/v3/organizations/some-guid"), 602 VerifyJSONRepresenting(expectedBody), 603 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 604 ), 605 ) 606 607 orgToUpdate = Organization{ 608 Name: "some-org-name", 609 GUID: "some-guid", 610 Metadata: &Metadata{ 611 Labels: map[string]types.NullString{ 612 "k1": types.NewNullString("v1"), 613 "k2": types.NewNullString("v2"), 614 }, 615 }, 616 } 617 }) 618 619 It("should include the labels in the JSON", func() { 620 Expect(executeErr).ToNot(HaveOccurred()) 621 Expect(len(warnings)).To(Equal(1)) 622 Expect(updatedOrg.Metadata.Labels).To(BeEquivalentTo( 623 map[string]types.NullString{ 624 "k1": types.NewNullString("v1"), 625 "k2": types.NewNullString("v2"), 626 })) 627 }) 628 }) 629 }) 630 631 Describe("DeleteOrganization", func() { 632 var ( 633 jobURL JobURL 634 warnings Warnings 635 executeErr error 636 ) 637 638 JustBeforeEach(func() { 639 jobURL, warnings, executeErr = client.DeleteOrganization("org-guid") 640 }) 641 642 When("no errors are encountered", func() { 643 BeforeEach(func() { 644 server.AppendHandlers( 645 CombineHandlers( 646 VerifyRequest(http.MethodDelete, "/v3/organizations/org-guid"), 647 RespondWith(http.StatusAccepted, nil, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}, "Location": []string{"job-url"}}), 648 )) 649 }) 650 651 It("deletes the Org and returns all warnings", func() { 652 Expect(executeErr).NotTo(HaveOccurred()) 653 Expect(warnings).To(ConsistOf(Warnings{"warning-1", "warning-2"})) 654 Expect(jobURL).To(Equal(JobURL("job-url"))) 655 }) 656 }) 657 658 When("an error is encountered", func() { 659 BeforeEach(func() { 660 response := `{ 661 "errors": [ 662 { 663 "detail": "Organization not found", 664 "title": "CF-ResourceNotFound", 665 "code": 10010 666 } 667 ] 668 }` 669 server.AppendHandlers( 670 CombineHandlers( 671 VerifyRequest(http.MethodDelete, "/v3/organizations/org-guid"), 672 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}}), 673 )) 674 }) 675 676 It("returns an error and all warnings", func() { 677 Expect(executeErr).To(MatchError(ccerror.ResourceNotFoundError{ 678 Message: "Organization not found", 679 })) 680 Expect(warnings).To(ConsistOf(Warnings{"warning-1", "warning-2"})) 681 }) 682 }) 683 }) 684 })