github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/organization_quota_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/types" 11 . "github.com/onsi/ginkgo" 12 . "github.com/onsi/gomega" 13 . "github.com/onsi/gomega/ghttp" 14 ) 15 16 var _ = Describe("Organization Quotas", func() { 17 var ( 18 client *Client 19 executeErr error 20 warnings Warnings 21 orgQuotas []resources.OrganizationQuota 22 query Query 23 trueValue = true 24 falseValue = false 25 ) 26 27 BeforeEach(func() { 28 client, _ = NewTestClient() 29 }) 30 31 Describe("GetOrganizationQuotas", func() { 32 JustBeforeEach(func() { 33 orgQuotas, warnings, executeErr = client.GetOrganizationQuotas(query) 34 }) 35 36 When("the cloud controller returns without errors", func() { 37 BeforeEach(func() { 38 response1 := fmt.Sprintf(`{ 39 "pagination": { 40 "total_results": 1, 41 "total_pages": 1, 42 "first": { 43 "href": "%s/v3/organization_quotas?page=1&per_page=1" 44 }, 45 "last": { 46 "href": "%s/v3/organization_quotas?page=2&per_page=1" 47 }, 48 "next": { 49 "href": "%s/v3/organization_quotas?page=2&per_page=1" 50 }, 51 "previous": null 52 }, 53 "resources": [ 54 { 55 "guid": "quota-guid", 56 "created_at": "2016-05-04T17:00:41Z", 57 "updated_at": "2016-05-04T17:00:41Z", 58 "name": "don-quixote", 59 "apps": { 60 "total_memory_in_mb": 5120, 61 "per_process_memory_in_mb": 1024, 62 "total_instances": 10, 63 "per_app_tasks": 5, 64 "log_rate_limit_in_bytes_per_second": 8 65 }, 66 "services": { 67 "paid_services_allowed": true, 68 "total_service_instances": 10, 69 "total_service_keys": 20 70 }, 71 "routes": { 72 "total_routes": 8, 73 "total_reserved_ports": 4 74 }, 75 "domains": { 76 "total_private_domains": 7 77 }, 78 "relationships": { 79 "organizations": { 80 "data": [ 81 { "guid": "org-guid1" }, 82 { "guid": "org-guid2" } 83 ] 84 } 85 }, 86 "links": { 87 "self": { "href": "%s/v3/organization_quotas/quota-guid" } 88 } 89 } 90 ] 91 }`, server.URL(), server.URL(), server.URL(), server.URL()) 92 93 response2 := fmt.Sprintf(`{ 94 "pagination": { 95 "next": null 96 }, 97 "resources": [ 98 { 99 "guid": "quota-2-guid", 100 "created_at": "2017-05-04T17:00:41Z", 101 "updated_at": "2017-05-04T17:00:41Z", 102 "name": "sancho-panza", 103 "apps": { 104 "total_memory_in_mb": 10240, 105 "per_process_memory_in_mb": 1024, 106 "total_instances": 8, 107 "per_app_tasks": 5, 108 "log_rate_limit_in_bytes_per_second": 16 109 }, 110 "services": { 111 "paid_services_allowed": false, 112 "total_service_instances": 8, 113 "total_service_keys": 20 114 }, 115 "routes": { 116 "total_routes": 10, 117 "total_reserved_ports": 5 118 }, 119 "domains": { 120 "total_private_domains": 7 121 }, 122 "relationships": { 123 "organizations": { 124 "data": [] 125 } 126 }, 127 "links": { 128 "self": { "href": "%s/v3/organization_quotas/quota-2-guid" } 129 } 130 } 131 ] 132 }`, server.URL()) 133 134 server.AppendHandlers( 135 CombineHandlers( 136 VerifyRequest(http.MethodGet, "/v3/organization_quotas"), 137 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"page1 warning"}}), 138 ), 139 ) 140 141 server.AppendHandlers( 142 CombineHandlers( 143 VerifyRequest(http.MethodGet, "/v3/organization_quotas", "page=2&per_page=1"), 144 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"page2 warning"}}), 145 ), 146 ) 147 }) 148 149 It("returns org quotas and warnings", func() { 150 Expect(executeErr).NotTo(HaveOccurred()) 151 Expect(warnings).To(ConsistOf("page1 warning", "page2 warning")) 152 Expect(orgQuotas).To(ConsistOf( 153 resources.OrganizationQuota{ 154 Quota: resources.Quota{ 155 GUID: "quota-guid", 156 Name: "don-quixote", 157 Apps: resources.AppLimit{ 158 TotalMemory: &types.NullInt{Value: 5120, IsSet: true}, 159 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 160 TotalAppInstances: &types.NullInt{Value: 10, IsSet: true}, 161 TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, 162 }, 163 Services: resources.ServiceLimit{ 164 TotalServiceInstances: &types.NullInt{Value: 10, IsSet: true}, 165 PaidServicePlans: &trueValue, 166 }, 167 Routes: resources.RouteLimit{ 168 TotalRoutes: &types.NullInt{Value: 8, IsSet: true}, 169 TotalReservedPorts: &types.NullInt{Value: 4, IsSet: true}, 170 }, 171 }, 172 }, 173 resources.OrganizationQuota{ 174 Quota: resources.Quota{ 175 GUID: "quota-2-guid", 176 Name: "sancho-panza", 177 Apps: resources.AppLimit{ 178 TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, 179 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 180 TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, 181 TotalLogVolume: &types.NullInt{Value: 16, IsSet: true}, 182 }, 183 Services: resources.ServiceLimit{ 184 TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, 185 PaidServicePlans: &falseValue, 186 }, 187 Routes: resources.RouteLimit{ 188 TotalRoutes: &types.NullInt{Value: 10, IsSet: true}, 189 TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, 190 }, 191 }, 192 }, 193 )) 194 }) 195 }) 196 197 When("requesting quotas by name", func() { 198 BeforeEach(func() { 199 query = Query{ 200 Key: NameFilter, 201 Values: []string{"sancho-panza"}, 202 } 203 204 response := fmt.Sprintf(`{ 205 "pagination": { 206 "next": null 207 }, 208 "resources": [ 209 { 210 "guid": "quota-2-guid", 211 "created_at": "2017-05-04T17:00:41Z", 212 "updated_at": "2017-05-04T17:00:41Z", 213 "name": "sancho-panza", 214 "apps": { 215 "total_memory_in_mb": 10240, 216 "per_process_memory_in_mb": 1024, 217 "total_instances": 8, 218 "per_app_tasks": 5, 219 "log_rate_limit_in_bytes_per_second": 8 220 }, 221 "services": { 222 "paid_services_allowed": false, 223 "total_service_instances": 8, 224 "total_service_keys": 20 225 }, 226 "routes": { 227 "total_routes": 10, 228 "total_reserved_ports": 5 229 }, 230 "domains": { 231 "total_private_domains": 7 232 }, 233 "relationships": { 234 "organizations": { 235 "data": [] 236 } 237 }, 238 "links": { 239 "self": { "href": "%s/v3/organization_quotas/quota-2-guid" } 240 } 241 } 242 ] 243 }`, server.URL()) 244 245 server.AppendHandlers( 246 CombineHandlers( 247 VerifyRequest(http.MethodGet, "/v3/organization_quotas", "names=sancho-panza"), 248 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"page1 warning"}}), 249 ), 250 ) 251 }) 252 253 It("queries the API with the given name", func() { 254 Expect(executeErr).NotTo(HaveOccurred()) 255 Expect(warnings).To(ConsistOf("page1 warning")) 256 Expect(orgQuotas).To(ConsistOf( 257 resources.OrganizationQuota{ 258 Quota: resources.Quota{ 259 GUID: "quota-2-guid", 260 Name: "sancho-panza", 261 Apps: resources.AppLimit{ 262 TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, 263 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 264 TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, 265 TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, 266 }, 267 Services: resources.ServiceLimit{ 268 TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, 269 PaidServicePlans: &falseValue, 270 }, 271 Routes: resources.RouteLimit{ 272 TotalRoutes: &types.NullInt{Value: 10, IsSet: true}, 273 TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, 274 }, 275 }, 276 }, 277 )) 278 }) 279 }) 280 281 When("the cloud controller returns errors and warnings", func() { 282 BeforeEach(func() { 283 response := `{ 284 "errors": [ 285 { 286 "code": 10008, 287 "detail": "The request is semantically invalid: command presence", 288 "title": "CF-UnprocessableEntity" 289 }, 290 { 291 "code": 10010, 292 "detail": "App not found", 293 "title": "CF-ResourceNotFound" 294 } 295 ] 296 }` 297 server.AppendHandlers( 298 CombineHandlers( 299 VerifyRequest(http.MethodGet, "/v3/organization_quotas"), 300 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 301 ), 302 ) 303 }) 304 305 It("returns the error and all warnings", func() { 306 Expect(executeErr).To(MatchError(ccerror.MultiError{ 307 ResponseCode: http.StatusTeapot, 308 Errors: []ccerror.V3Error{ 309 { 310 Code: 10008, 311 Detail: "The request is semantically invalid: command presence", 312 Title: "CF-UnprocessableEntity", 313 }, 314 { 315 Code: 10010, 316 Detail: "App not found", 317 Title: "CF-ResourceNotFound", 318 }, 319 }, 320 })) 321 Expect(warnings).To(ConsistOf("this is a warning")) 322 }) 323 }) 324 }) 325 326 Describe("GetOrganizationQuota", func() { 327 var ( 328 returnedOrgQuota resources.OrganizationQuota 329 orgQuotaGUID = "quota_guid" 330 ) 331 JustBeforeEach(func() { 332 returnedOrgQuota, warnings, executeErr = client.GetOrganizationQuota(orgQuotaGUID) 333 }) 334 335 When("the cloud controller returns without errors", func() { 336 BeforeEach(func() { 337 response := fmt.Sprintf(`{ 338 "guid": "quota-guid", 339 "created_at": "2016-05-04T17:00:41Z", 340 "updated_at": "2016-05-04T17:00:41Z", 341 "name": "don-quixote", 342 "apps": { 343 "total_memory_in_mb": 5120, 344 "per_process_memory_in_mb": 1024, 345 "total_instances": 10, 346 "per_app_tasks": 5, 347 "log_rate_limit_in_bytes_per_second": 8 348 }, 349 "services": { 350 "paid_services_allowed": true, 351 "total_service_instances": 10, 352 "total_service_keys": 20 353 }, 354 "routes": { 355 "total_routes": 8, 356 "total_reserved_ports": 4 357 }, 358 "domains": { 359 "total_private_domains": 7 360 }, 361 "relationships": { 362 "organizations": { 363 "data": [ 364 { "guid": "org-guid1" }, 365 { "guid": "org-guid2" } 366 ] 367 } 368 }, 369 "links": { 370 "self": { "href": "%s/v3/organization_quotas/quota-guid" } 371 } 372 }`, server.URL()) 373 374 server.AppendHandlers( 375 CombineHandlers( 376 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/organization_quotas/%s", orgQuotaGUID)), 377 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"show warning"}}), 378 ), 379 ) 380 }) 381 382 It("returns org quotas and warnings", func() { 383 Expect(executeErr).NotTo(HaveOccurred()) 384 Expect(warnings).To(ConsistOf("show warning")) 385 Expect(returnedOrgQuota).To(Equal( 386 resources.OrganizationQuota{ 387 Quota: resources.Quota{ 388 GUID: "quota-guid", 389 Name: "don-quixote", 390 Apps: resources.AppLimit{ 391 TotalMemory: &types.NullInt{Value: 5120, IsSet: true}, 392 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 393 TotalAppInstances: &types.NullInt{Value: 10, IsSet: true}, 394 TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, 395 }, 396 Services: resources.ServiceLimit{ 397 TotalServiceInstances: &types.NullInt{Value: 10, IsSet: true}, 398 PaidServicePlans: &trueValue, 399 }, 400 Routes: resources.RouteLimit{ 401 TotalRoutes: &types.NullInt{Value: 8, IsSet: true}, 402 TotalReservedPorts: &types.NullInt{Value: 4, IsSet: true}, 403 }, 404 }, 405 }, 406 )) 407 }) 408 }) 409 410 When("the cloud controller returns errors and warnings", func() { 411 BeforeEach(func() { 412 response := `{ 413 "errors": [ 414 { 415 "code": 10008, 416 "detail": "The request is semantically invalid: command presence", 417 "title": "CF-UnprocessableEntity" 418 }, 419 { 420 "code": 10010, 421 "detail": "Quota not found", 422 "title": "CF-ResourceNotFound" 423 } 424 ] 425 }` 426 server.AppendHandlers( 427 CombineHandlers( 428 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/organization_quotas/%s", orgQuotaGUID)), 429 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 430 ), 431 ) 432 }) 433 434 It("returns the error and all warnings", func() { 435 Expect(executeErr).To(MatchError(ccerror.MultiError{ 436 ResponseCode: http.StatusTeapot, 437 Errors: []ccerror.V3Error{ 438 { 439 Code: 10008, 440 Detail: "The request is semantically invalid: command presence", 441 Title: "CF-UnprocessableEntity", 442 }, 443 { 444 Code: 10010, 445 Detail: "Quota not found", 446 Title: "CF-ResourceNotFound", 447 }, 448 }, 449 })) 450 Expect(warnings).To(ConsistOf("this is a warning")) 451 }) 452 }) 453 }) 454 455 Describe("CreateOrganizationQuota", func() { 456 var ( 457 createdOrgQuota resources.OrganizationQuota 458 warnings Warnings 459 executeErr error 460 inputQuota resources.OrganizationQuota 461 ) 462 463 BeforeEach(func() { 464 inputQuota = resources.OrganizationQuota{ 465 Quota: resources.Quota{ 466 Name: "elephant-trunk", 467 Apps: resources.AppLimit{ 468 TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, 469 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 470 TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, 471 TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, 472 }, 473 Services: resources.ServiceLimit{ 474 TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, 475 PaidServicePlans: &trueValue, 476 }, 477 Routes: resources.RouteLimit{ 478 TotalRoutes: &types.NullInt{Value: 6, IsSet: true}, 479 TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, 480 }, 481 }, 482 } 483 }) 484 485 JustBeforeEach(func() { 486 createdOrgQuota, warnings, executeErr = client.CreateOrganizationQuota(inputQuota) 487 }) 488 489 When("the organization quota is created successfully", func() { 490 BeforeEach(func() { 491 response := `{ 492 "guid": "elephant-trunk-guid", 493 "created_at": "2020-01-16T19:44:47Z", 494 "updated_at": "2020-01-16T19:44:47Z", 495 "name": "elephant-trunk", 496 "apps": { 497 "total_memory_in_mb": 2048, 498 "per_process_memory_in_mb": 1024, 499 "total_instances": null, 500 "per_app_tasks": null, 501 "log_rate_limit_in_bytes_per_second": null 502 }, 503 "services": { 504 "paid_services_allowed": true, 505 "total_service_instances": 0, 506 "total_service_keys": null 507 }, 508 "routes": { 509 "total_routes": 6, 510 "total_reserved_ports": 5 511 }, 512 "domains": { 513 "total_domains": null 514 }, 515 "links": { 516 "self": { 517 "href": "https://api.foil-venom.lite.cli.fun/v3/organization_quotas/08357710-8106-4d14-b0ea-03154a36fb79" 518 } 519 } 520 }` 521 522 expectedBody := map[string]interface{}{ 523 "name": "elephant-trunk", 524 "apps": map[string]interface{}{ 525 "total_memory_in_mb": 2048, 526 "per_process_memory_in_mb": 1024, 527 "total_instances": nil, 528 "log_rate_limit_in_bytes_per_second": nil, 529 }, 530 "services": map[string]interface{}{ 531 "paid_services_allowed": true, 532 "total_service_instances": 0, 533 }, 534 "routes": map[string]interface{}{ 535 "total_routes": 6, 536 "total_reserved_ports": 5, 537 }, 538 } 539 540 server.AppendHandlers( 541 CombineHandlers( 542 VerifyRequest(http.MethodPost, "/v3/organization_quotas"), 543 VerifyJSONRepresenting(expectedBody), 544 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 545 ), 546 ) 547 }) 548 549 It("returns the created org", func() { 550 Expect(executeErr).ToNot(HaveOccurred()) 551 Expect(warnings).To(ConsistOf("this is a warning")) 552 553 expectedOrgQuota := inputQuota 554 expectedOrgQuota.GUID = "elephant-trunk-guid" 555 Expect(createdOrgQuota).To(Equal(expectedOrgQuota)) 556 }) 557 }) 558 559 When("an organization quota with the same name already exists", func() { 560 BeforeEach(func() { 561 response := `{ 562 "errors": [ 563 { 564 "detail": "Organization Quota 'anteater-snout' already exists.", 565 "title": "CF-UnprocessableEntity", 566 "code": 10008 567 } 568 ] 569 }` 570 571 server.AppendHandlers( 572 CombineHandlers( 573 VerifyRequest(http.MethodPost, "/v3/organization_quotas"), 574 RespondWith(http.StatusUnprocessableEntity, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 575 ), 576 ) 577 }) 578 579 It("returns a meaningful organization quota-name-taken error", func() { 580 Expect(executeErr).To(MatchError(ccerror.QuotaAlreadyExists{ 581 Message: "Organization Quota 'anteater-snout' already exists.", 582 })) 583 Expect(warnings).To(ConsistOf("this is a warning")) 584 }) 585 }) 586 587 When("creating the quota fails", func() { 588 BeforeEach(func() { 589 response := `{ 590 "errors": [ 591 { 592 "detail": "Fail", 593 "title": "CF-SomeError", 594 "code": 10002 595 }, 596 { 597 "detail": "Something went terribly wrong", 598 "title": "CF-UnknownError", 599 "code": 10001 600 } 601 ] 602 }` 603 604 server.AppendHandlers( 605 CombineHandlers( 606 VerifyRequest(http.MethodPost, "/v3/organization_quotas"), 607 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 608 ), 609 ) 610 }) 611 612 It("returns an error", func() { 613 Expect(executeErr).To(MatchError(ccerror.MultiError{ 614 ResponseCode: http.StatusTeapot, 615 Errors: []ccerror.V3Error{ 616 { 617 Code: 10002, 618 Detail: "Fail", 619 Title: "CF-SomeError", 620 }, 621 { 622 Code: 10001, 623 Detail: "Something went terribly wrong", 624 Title: "CF-UnknownError", 625 }, 626 }, 627 })) 628 Expect(warnings).To(ConsistOf("this is a warning")) 629 }) 630 }) 631 }) 632 633 Describe("DeleteOrganizationQuota", func() { 634 var ( 635 jobURL JobURL 636 orgQuotaGUID = "quota_guid" 637 ) 638 639 JustBeforeEach(func() { 640 jobURL, warnings, executeErr = client.DeleteOrganizationQuota(orgQuotaGUID) 641 }) 642 643 When("the cloud controller returns without errors", func() { 644 BeforeEach(func() { 645 server.AppendHandlers( 646 CombineHandlers( 647 VerifyRequest(http.MethodDelete, fmt.Sprintf("/v3/organization_quotas/%s", orgQuotaGUID)), 648 RespondWith(http.StatusAccepted, nil, http.Header{ 649 "X-Cf-Warnings": {"delete warning"}, 650 "Location": {"/v3/jobs/some-job"}, 651 }), 652 ), 653 ) 654 }) 655 656 It("returns org quotas and warnings", func() { 657 Expect(executeErr).NotTo(HaveOccurred()) 658 Expect(warnings).To(ConsistOf("delete warning")) 659 Expect(jobURL).To(Equal(JobURL("/v3/jobs/some-job"))) 660 }) 661 }) 662 663 When("the cloud controller returns errors and warnings", func() { 664 BeforeEach(func() { 665 response := `{ 666 "errors": [ 667 { 668 "code": 10008, 669 "detail": "The request is semantically invalid: command presence", 670 "title": "CF-UnprocessableEntity" 671 }, 672 { 673 "code": 10010, 674 "detail": "Quota not found", 675 "title": "CF-ResourceNotFound" 676 } 677 ] 678 }` 679 server.AppendHandlers( 680 CombineHandlers( 681 VerifyRequest(http.MethodDelete, fmt.Sprintf("/v3/organization_quotas/%s", orgQuotaGUID)), 682 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 683 ), 684 ) 685 }) 686 687 It("returns the error and all warnings", func() { 688 Expect(executeErr).To(MatchError(ccerror.MultiError{ 689 ResponseCode: http.StatusTeapot, 690 Errors: []ccerror.V3Error{ 691 { 692 Code: 10008, 693 Detail: "The request is semantically invalid: command presence", 694 Title: "CF-UnprocessableEntity", 695 }, 696 { 697 Code: 10010, 698 Detail: "Quota not found", 699 Title: "CF-ResourceNotFound", 700 }, 701 }, 702 })) 703 Expect(warnings).To(ConsistOf("this is a warning")) 704 }) 705 }) 706 }) 707 708 Describe("UpdateOrganizationQuota", func() { 709 var ( 710 updatedOrgQuota resources.OrganizationQuota 711 warnings Warnings 712 executeErr error 713 inputQuota resources.OrganizationQuota 714 ) 715 716 BeforeEach(func() { 717 inputQuota = resources.OrganizationQuota{ 718 Quota: resources.Quota{ 719 GUID: "elephant-trunk-guid", 720 Name: "elephant-trunk", 721 Apps: resources.AppLimit{ 722 TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, 723 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 724 TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, 725 TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, 726 }, 727 Services: resources.ServiceLimit{ 728 TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, 729 PaidServicePlans: &trueValue, 730 }, 731 }, 732 } 733 }) 734 735 JustBeforeEach(func() { 736 updatedOrgQuota, warnings, executeErr = client.UpdateOrganizationQuota(inputQuota) 737 }) 738 739 When("updating the quota succeeds", func() { 740 BeforeEach(func() { 741 response := `{ 742 "guid": "elephant-trunk-guid", 743 "created_at": "2020-01-16T19:44:47Z", 744 "updated_at": "2020-01-16T19:44:47Z", 745 "name": "elephant-trunk", 746 "apps": { 747 "total_memory_in_mb": 2048, 748 "per_process_memory_in_mb": 1024, 749 "total_instances": null, 750 "per_app_tasks": null, 751 "log_rate_limit_in_bytes_per_second": 8 752 }, 753 "services": { 754 "paid_services_allowed": true, 755 "total_service_instances": 0, 756 "total_service_keys": null 757 }, 758 "routes": { 759 "total_routes": null, 760 "total_reserved_ports": null 761 }, 762 "domains": { 763 "total_domains": null 764 }, 765 "links": { 766 "self": { 767 "href": "https://api.foil-venom.lite.cli.fun/v3/organization_quotas/08357710-8106-4d14-b0ea-03154a36fb79" 768 } 769 } 770 }` 771 772 expectedBody := map[string]interface{}{ 773 "name": "elephant-trunk", 774 "apps": map[string]interface{}{ 775 "total_memory_in_mb": 2048, 776 "per_process_memory_in_mb": 1024, 777 "total_instances": nil, 778 "log_rate_limit_in_bytes_per_second": 8, 779 }, 780 "services": map[string]interface{}{ 781 "paid_services_allowed": true, 782 "total_service_instances": 0, 783 }, 784 "routes": map[string]interface{}{}, 785 } 786 787 server.AppendHandlers( 788 CombineHandlers( 789 VerifyRequest(http.MethodPatch, "/v3/organization_quotas/elephant-trunk-guid"), 790 VerifyJSONRepresenting(expectedBody), 791 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 792 ), 793 ) 794 }) 795 796 It("returns the updated org quota", func() { 797 Expect(executeErr).ToNot(HaveOccurred()) 798 Expect(warnings).To(ConsistOf("this is a warning")) 799 800 Expect(updatedOrgQuota).To(Equal(resources.OrganizationQuota{ 801 Quota: resources.Quota{ 802 GUID: "elephant-trunk-guid", 803 Name: "elephant-trunk", 804 Apps: resources.AppLimit{ 805 TotalMemory: &types.NullInt{IsSet: true, Value: 2048}, 806 InstanceMemory: &types.NullInt{IsSet: true, Value: 1024}, 807 TotalAppInstances: &types.NullInt{IsSet: false, Value: 0}, 808 TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, 809 }, 810 Services: resources.ServiceLimit{ 811 TotalServiceInstances: &types.NullInt{IsSet: true, Value: 0}, 812 PaidServicePlans: &trueValue, 813 }, 814 Routes: resources.RouteLimit{ 815 TotalRoutes: &types.NullInt{IsSet: false, Value: 0}, 816 TotalReservedPorts: &types.NullInt{IsSet: false, Value: 0}, 817 }, 818 }, 819 })) 820 }) 821 }) 822 823 When("updating the quota fails", func() { 824 BeforeEach(func() { 825 response := `{ 826 "errors": [ 827 { 828 "detail": "Fail", 829 "title": "CF-SomeError", 830 "code": 10002 831 }, 832 { 833 "detail": "Something went terribly wrong", 834 "title": "CF-UnknownError", 835 "code": 10001 836 } 837 ] 838 }` 839 840 server.AppendHandlers( 841 CombineHandlers( 842 VerifyRequest(http.MethodPatch, "/v3/organization_quotas/elephant-trunk-guid"), 843 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 844 ), 845 ) 846 }) 847 848 It("returns an error", func() { 849 Expect(executeErr).To(MatchError(ccerror.MultiError{ 850 ResponseCode: http.StatusTeapot, 851 Errors: []ccerror.V3Error{ 852 { 853 Code: 10002, 854 Detail: "Fail", 855 Title: "CF-SomeError", 856 }, 857 { 858 Code: 10001, 859 Detail: "Something went terribly wrong", 860 Title: "CF-UnknownError", 861 }, 862 }, 863 })) 864 Expect(warnings).To(ConsistOf("this is a warning")) 865 }) 866 }) 867 }) 868 869 Describe("ApplyOrganizationQuota", func() { 870 var ( 871 warnings Warnings 872 executeErr error 873 AppliedOrgQuotaGUIDS resources.RelationshipList 874 quotaGuid = "quotaGuid" 875 orgGuid = "orgGuid" 876 ) 877 878 JustBeforeEach(func() { 879 AppliedOrgQuotaGUIDS, warnings, executeErr = client.ApplyOrganizationQuota(quotaGuid, orgGuid) 880 }) 881 882 When("the organization quota is applied successfully", func() { 883 BeforeEach(func() { 884 response := `{ 885 "data": [ 886 { 887 "guid": "orgGuid" 888 } 889 ] 890 }` 891 892 expectedBody := map[string][]map[string]string{ 893 "data": {{"guid": "orgGuid"}}, 894 } 895 896 server.AppendHandlers( 897 CombineHandlers( 898 VerifyRequest(http.MethodPost, fmt.Sprintf("/v3/organization_quotas/%s/relationships/organizations", quotaGuid)), 899 VerifyJSONRepresenting(expectedBody), 900 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 901 ), 902 ) 903 }) 904 905 It("returns the created org", func() { 906 Expect(executeErr).ToNot(HaveOccurred()) 907 Expect(warnings).To(ConsistOf("this is a warning")) 908 909 Expect(AppliedOrgQuotaGUIDS).To(Equal(resources.RelationshipList{GUIDs: []string{orgGuid}})) 910 }) 911 }) 912 913 When("the cloud controller returns errors and warnings", func() { 914 BeforeEach(func() { 915 response := `{ 916 "errors": [ 917 { 918 "code": 10008, 919 "detail": "The request is semantically invalid: command presence", 920 "title": "CF-UnprocessableEntity" 921 } 922 ] 923 }` 924 server.AppendHandlers( 925 CombineHandlers( 926 VerifyRequest(http.MethodPost, fmt.Sprintf("/v3/organization_quotas/%s/relationships/organizations", quotaGuid)), 927 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 928 ), 929 ) 930 }) 931 932 It("returns the error and all warnings", func() { 933 Expect(executeErr).To(MatchError(ccerror.V3UnexpectedResponseError{ 934 ResponseCode: http.StatusTeapot, 935 V3ErrorResponse: ccerror.V3ErrorResponse{ 936 Errors: []ccerror.V3Error{ 937 { 938 Code: 10008, 939 Detail: "The request is semantically invalid: command presence", 940 Title: "CF-UnprocessableEntity", 941 }, 942 }, 943 }, 944 })) 945 Expect(warnings).To(ConsistOf("this is a warning")) 946 }) 947 }) 948 949 }) 950 })