github.com/swisscom/cloudfoundry-cli@v7.1.0+incompatible/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 } 454 455 server.AppendHandlers( 456 CombineHandlers( 457 VerifyRequest(http.MethodPost, "/v3/organizations"), 458 VerifyJSONRepresenting(expectedBody), 459 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 460 ), 461 ) 462 }) 463 464 It("returns the created org", func() { 465 Expect(executeErr).ToNot(HaveOccurred()) 466 Expect(warnings).To(ConsistOf("this is a warning")) 467 Expect(createdOrg).To(Equal(Organization{ 468 GUID: "some-org-guid", 469 Name: "some-org-name", 470 })) 471 }) 472 }) 473 474 When("an organization with the same name already exists", func() { 475 BeforeEach(func() { 476 response := `{ 477 "errors": [ 478 { 479 "detail": "Organization 'some-org-name' already exists.", 480 "title": "CF-UnprocessableEntity", 481 "code": 10008 482 } 483 ] 484 }` 485 486 expectedBody := map[string]interface{}{ 487 "name": "some-org-name", 488 } 489 490 server.AppendHandlers( 491 CombineHandlers( 492 VerifyRequest(http.MethodPost, "/v3/organizations"), 493 VerifyJSONRepresenting(expectedBody), 494 RespondWith(http.StatusUnprocessableEntity, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 495 ), 496 ) 497 }) 498 499 It("returns a meaningful organization-name-taken error", func() { 500 Expect(executeErr).To(MatchError(ccerror.OrganizationNameTakenError{ 501 UnprocessableEntityError: ccerror.UnprocessableEntityError{ 502 Message: "Organization 'some-org-name' already exists.", 503 }, 504 })) 505 Expect(warnings).To(ConsistOf("this is a warning")) 506 }) 507 }) 508 509 When("creating the org fails", func() { 510 BeforeEach(func() { 511 response := `{ 512 "errors": [ 513 { 514 "detail": "Fail", 515 "title": "CF-SomeError", 516 "code": 10002 517 }, 518 { 519 "detail": "Something went terribly wrong", 520 "title": "CF-UnknownError", 521 "code": 10001 522 } 523 ] 524 }` 525 526 expectedBody := map[string]interface{}{ 527 "name": "some-org-name", 528 } 529 530 server.AppendHandlers( 531 CombineHandlers( 532 VerifyRequest(http.MethodPost, "/v3/organizations"), 533 VerifyJSONRepresenting(expectedBody), 534 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 535 ), 536 ) 537 }) 538 539 It("returns an error", func() { 540 Expect(executeErr).To(MatchError(ccerror.MultiError{ 541 ResponseCode: http.StatusTeapot, 542 Errors: []ccerror.V3Error{ 543 { 544 Code: 10002, 545 Detail: "Fail", 546 Title: "CF-SomeError", 547 }, 548 { 549 Code: 10001, 550 Detail: "Something went terribly wrong", 551 Title: "CF-UnknownError", 552 }, 553 }, 554 })) 555 Expect(warnings).To(ConsistOf("this is a warning")) 556 }) 557 }) 558 }) 559 560 Describe("UpdateOrganization", func() { 561 var ( 562 orgToUpdate Organization 563 updatedOrg Organization 564 warnings Warnings 565 executeErr error 566 ) 567 568 JustBeforeEach(func() { 569 updatedOrg, warnings, executeErr = client.UpdateOrganization(orgToUpdate) 570 }) 571 572 When("the organization is updated successfully", func() { 573 BeforeEach(func() { 574 response := `{ 575 "guid": "some-org-guid", 576 "name": "some-org-name", 577 "metadata": { 578 "labels": { 579 "k1":"v1", 580 "k2":"v2" 581 } 582 } 583 }` 584 585 expectedBody := map[string]interface{}{ 586 "name": "some-org-name", 587 "metadata": map[string]interface{}{ 588 "labels": map[string]string{ 589 "k1": "v1", 590 "k2": "v2", 591 }, 592 }, 593 } 594 595 server.AppendHandlers( 596 CombineHandlers( 597 VerifyRequest(http.MethodPatch, "/v3/organizations/some-guid"), 598 VerifyJSONRepresenting(expectedBody), 599 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 600 ), 601 ) 602 603 orgToUpdate = Organization{ 604 Name: "some-org-name", 605 GUID: "some-guid", 606 Metadata: &Metadata{ 607 Labels: map[string]types.NullString{ 608 "k1": types.NewNullString("v1"), 609 "k2": types.NewNullString("v2"), 610 }, 611 }, 612 } 613 }) 614 615 It("should include the labels in the JSON", func() { 616 Expect(executeErr).ToNot(HaveOccurred()) 617 Expect(len(warnings)).To(Equal(1)) 618 Expect(updatedOrg.Metadata.Labels).To(BeEquivalentTo( 619 map[string]types.NullString{ 620 "k1": types.NewNullString("v1"), 621 "k2": types.NewNullString("v2"), 622 })) 623 }) 624 }) 625 }) 626 627 Describe("DeleteOrganization", func() { 628 var ( 629 jobURL JobURL 630 warnings Warnings 631 executeErr error 632 ) 633 634 JustBeforeEach(func() { 635 jobURL, warnings, executeErr = client.DeleteOrganization("org-guid") 636 }) 637 638 When("no errors are encountered", func() { 639 BeforeEach(func() { 640 server.AppendHandlers( 641 CombineHandlers( 642 VerifyRequest(http.MethodDelete, "/v3/organizations/org-guid"), 643 RespondWith(http.StatusAccepted, nil, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}, "Location": []string{"job-url"}}), 644 )) 645 }) 646 647 It("deletes the Org and returns all warnings", func() { 648 Expect(executeErr).NotTo(HaveOccurred()) 649 Expect(warnings).To(ConsistOf(Warnings{"warning-1", "warning-2"})) 650 Expect(jobURL).To(Equal(JobURL("job-url"))) 651 }) 652 }) 653 654 When("an error is encountered", func() { 655 BeforeEach(func() { 656 response := `{ 657 "errors": [ 658 { 659 "detail": "Organization not found", 660 "title": "CF-ResourceNotFound", 661 "code": 10010 662 } 663 ] 664 }` 665 server.AppendHandlers( 666 CombineHandlers( 667 VerifyRequest(http.MethodDelete, "/v3/organizations/org-guid"), 668 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1, warning-2"}}), 669 )) 670 }) 671 672 It("returns an error and all warnings", func() { 673 Expect(executeErr).To(MatchError(ccerror.ResourceNotFoundError{ 674 Message: "Organization not found", 675 })) 676 Expect(warnings).To(ConsistOf(Warnings{"warning-1", "warning-2"})) 677 }) 678 }) 679 }) 680 })