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