github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/space_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("Space Quotas", func() { 17 var ( 18 client *Client 19 executeErr error 20 warnings Warnings 21 inputSpaceQuota resources.SpaceQuota 22 23 createdSpaceQuota resources.SpaceQuota 24 trueValue = true 25 falseValue = false 26 ) 27 28 BeforeEach(func() { 29 client, _ = NewTestClient() 30 }) 31 32 Describe("ApplySpaceQuota", func() { 33 var ( 34 warnings Warnings 35 executeErr error 36 quotaGUID string 37 spaceGUID string 38 relationshipList resources.RelationshipList 39 ) 40 41 BeforeEach(func() { 42 quotaGUID = "some-quota-guid" 43 spaceGUID = "space-guid-1" 44 }) 45 46 JustBeforeEach(func() { 47 relationshipList, warnings, executeErr = client.ApplySpaceQuota(quotaGUID, spaceGUID) 48 }) 49 50 When("applying the quota to a space", func() { 51 BeforeEach(func() { 52 response := `{ "data": [{"guid": "space-guid-1"}] }` 53 54 expectedBody := map[string]interface{}{ 55 "data": []map[string]interface{}{ 56 {"guid": "space-guid-1"}, 57 }, 58 } 59 60 server.AppendHandlers( 61 CombineHandlers( 62 VerifyRequest(http.MethodPost, "/v3/space_quotas/some-quota-guid/relationships/spaces"), 63 VerifyJSONRepresenting(expectedBody), 64 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 65 ), 66 ) 67 }) 68 69 It("returns the a relationship list with the applied space", func() { 70 Expect(executeErr).ToNot(HaveOccurred()) 71 Expect(warnings).To(ConsistOf("this is a warning")) 72 73 Expect(relationshipList).To(Equal(resources.RelationshipList{ 74 GUIDs: []string{"space-guid-1"}, 75 })) 76 }) 77 }) 78 79 When("applying the quota fails", func() { 80 BeforeEach(func() { 81 response := `{ 82 "errors": [ 83 { 84 "detail": "Fail", 85 "title": "CF-SomeError", 86 "code": 10002 87 }, 88 { 89 "detail": "Something went terribly wrong", 90 "title": "CF-UnknownError", 91 "code": 10001 92 } 93 ] 94 }` 95 96 expectedBody := map[string]interface{}{ 97 "data": []map[string]interface{}{ 98 {"guid": "space-guid-1"}, 99 }, 100 } 101 102 server.AppendHandlers( 103 CombineHandlers( 104 VerifyRequest(http.MethodPost, "/v3/space_quotas/some-quota-guid/relationships/spaces"), 105 VerifyJSONRepresenting(expectedBody), 106 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 107 ), 108 ) 109 }) 110 111 It("returns an error", func() { 112 Expect(executeErr).To(MatchError(ccerror.MultiError{ 113 ResponseCode: http.StatusTeapot, 114 Errors: []ccerror.V3Error{ 115 { 116 Code: 10002, 117 Detail: "Fail", 118 Title: "CF-SomeError", 119 }, 120 { 121 Code: 10001, 122 Detail: "Something went terribly wrong", 123 Title: "CF-UnknownError", 124 }, 125 }, 126 })) 127 Expect(warnings).To(ConsistOf("this is a warning")) 128 }) 129 }) 130 131 }) 132 133 Describe("CreateSpaceQuota", func() { 134 JustBeforeEach(func() { 135 createdSpaceQuota, warnings, executeErr = client.CreateSpaceQuota(inputSpaceQuota) 136 }) 137 138 When("successfully creating a space quota without spaces", func() { 139 BeforeEach(func() { 140 inputSpaceQuota = resources.SpaceQuota{ 141 Quota: resources.Quota{ 142 Name: "my-space-quota", 143 Apps: resources.AppLimit{ 144 TotalMemory: &types.NullInt{IsSet: true, Value: 2}, 145 InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, 146 TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, 147 TotalLogVolume: &types.NullInt{IsSet: true, Value: 8}, 148 }, 149 Services: resources.ServiceLimit{ 150 PaidServicePlans: &trueValue, 151 TotalServiceInstances: &types.NullInt{IsSet: true, Value: 5}, 152 }, 153 Routes: resources.RouteLimit{ 154 TotalRoutes: &types.NullInt{IsSet: true, Value: 6}, 155 TotalReservedPorts: &types.NullInt{IsSet: true, Value: 7}, 156 }, 157 }, 158 OrgGUID: "some-org-guid", 159 } 160 161 response := fmt.Sprintf(`{ 162 "guid": "space-quota-guid", 163 "created_at": "2016-05-04T17:00:41Z", 164 "updated_at": "2016-05-04T17:00:41Z", 165 "name": "my-space-quota", 166 "apps": { 167 "total_memory_in_mb": 2, 168 "per_process_memory_in_mb": 3, 169 "total_instances": 4, 170 "per_app_tasks": 900, 171 "log_rate_limit_in_bytes_per_second": 8 172 }, 173 "services": { 174 "paid_services_allowed": true, 175 "total_service_instances": 5, 176 "total_service_keys": 700 177 }, 178 "routes": { 179 "total_routes": 6, 180 "total_reserved_ports": 7 181 }, 182 "relationships": { 183 "organization": { 184 "data": { 185 "guid": "some-org-guid" 186 } 187 }, 188 "spaces": { 189 "data": [] 190 } 191 }, 192 "links": { 193 "self": { 194 "href": "https://api.example.org/v3/space_quotas/space-quota-guid" 195 }, 196 "organization": { 197 "href": "https://api.example.org/v3/organizations/some-org-guid" 198 } 199 } 200 } 201 `) 202 expectedBody := map[string]interface{}{ 203 "name": "my-space-quota", 204 "apps": map[string]interface{}{ 205 "total_memory_in_mb": 2, 206 "per_process_memory_in_mb": 3, 207 "total_instances": 4, 208 "log_rate_limit_in_bytes_per_second": 8, 209 }, 210 "services": map[string]interface{}{ 211 "paid_services_allowed": true, 212 "total_service_instances": 5, 213 }, 214 "routes": map[string]interface{}{ 215 "total_routes": 6, 216 "total_reserved_ports": 7, 217 }, 218 "relationships": map[string]interface{}{ 219 "organization": map[string]interface{}{ 220 "data": map[string]interface{}{ 221 "guid": "some-org-guid", 222 }, 223 }, 224 }, 225 } 226 227 server.AppendHandlers( 228 CombineHandlers( 229 VerifyRequest(http.MethodPost, "/v3/space_quotas"), 230 VerifyJSONRepresenting(expectedBody), 231 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"space-quota-warning"}}), 232 ), 233 ) 234 }) 235 236 It("returns space quota and warnings", func() { 237 Expect(executeErr).NotTo(HaveOccurred()) 238 Expect(warnings).To(ConsistOf("space-quota-warning")) 239 Expect(createdSpaceQuota).To(Equal( 240 resources.SpaceQuota{ 241 Quota: resources.Quota{ 242 GUID: "space-quota-guid", 243 Name: "my-space-quota", 244 Apps: resources.AppLimit{ 245 TotalMemory: &types.NullInt{IsSet: true, Value: 2}, 246 InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, 247 TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, 248 TotalLogVolume: &types.NullInt{IsSet: true, Value: 8}, 249 }, 250 Services: resources.ServiceLimit{ 251 PaidServicePlans: &trueValue, 252 TotalServiceInstances: &types.NullInt{IsSet: true, Value: 5}, 253 }, 254 Routes: resources.RouteLimit{ 255 TotalRoutes: &types.NullInt{IsSet: true, Value: 6}, 256 TotalReservedPorts: &types.NullInt{IsSet: true, Value: 7}, 257 }, 258 }, 259 OrgGUID: "some-org-guid", 260 })) 261 }) 262 }) 263 264 When("successfully creating a space quota with spaces", func() { 265 BeforeEach(func() { 266 inputSpaceQuota = resources.SpaceQuota{ 267 Quota: resources.Quota{ 268 Name: "my-space-quota", 269 Apps: resources.AppLimit{ 270 TotalMemory: &types.NullInt{IsSet: true, Value: 2}, 271 InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, 272 TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, 273 TotalLogVolume: &types.NullInt{IsSet: true, Value: 8}, 274 }, 275 Services: resources.ServiceLimit{ 276 PaidServicePlans: &trueValue, 277 TotalServiceInstances: &types.NullInt{IsSet: true, Value: 6}, 278 }, 279 Routes: resources.RouteLimit{ 280 TotalRoutes: &types.NullInt{IsSet: true, Value: 8}, 281 TotalReservedPorts: &types.NullInt{IsSet: true, Value: 9}, 282 }, 283 }, 284 OrgGUID: "some-org-guid", 285 SpaceGUIDs: []string{"space-guid-1", "space-guid-2"}, 286 } 287 288 response := fmt.Sprintf(`{ 289 "guid": "space-quota-guid", 290 "created_at": "2016-05-04T17:00:41Z", 291 "updated_at": "2016-05-04T17:00:41Z", 292 "name": "my-space-quota", 293 "apps": { 294 "total_memory_in_mb": 2, 295 "per_process_memory_in_mb": 3, 296 "total_instances": 4, 297 "per_app_tasks": 5, 298 "log_rate_limit_in_bytes_per_second": 8 299 }, 300 "services": { 301 "paid_services_allowed": true, 302 "total_service_instances": 6, 303 "total_service_keys": 7 304 }, 305 "routes": { 306 "total_routes": 8, 307 "total_reserved_ports": 9 308 }, 309 "relationships": { 310 "organization": { 311 "data": { 312 "guid": "some-org-guid" 313 } 314 }, 315 "spaces": { 316 "data": [{"guid": "space-guid-1"}, {"guid": "space-guid-2"}] 317 } 318 }, 319 "links": { 320 "self": { 321 "href": "https://api.example.org/v3/space_quotas/9b370018-c38e-44c9-86d6-155c76801104" 322 }, 323 "organization": { 324 "href": "https://api.example.org/v3/organizations/9b370018-c38e-44c9-86d6-155c76801104" 325 } 326 } 327 } 328 `) 329 330 expectedBody := map[string]interface{}{ 331 "name": "my-space-quota", 332 "apps": map[string]interface{}{ 333 "total_memory_in_mb": 2, 334 "per_process_memory_in_mb": 3, 335 "total_instances": 4, 336 "log_rate_limit_in_bytes_per_second": 8, 337 }, 338 "services": map[string]interface{}{ 339 "paid_services_allowed": true, 340 "total_service_instances": 6, 341 }, 342 "routes": map[string]interface{}{ 343 "total_routes": 8, 344 "total_reserved_ports": 9, 345 }, 346 "relationships": map[string]interface{}{ 347 "organization": map[string]interface{}{ 348 "data": map[string]interface{}{ 349 "guid": "some-org-guid", 350 }, 351 }, 352 "spaces": map[string]interface{}{ 353 "data": []map[string]interface{}{ 354 {"guid": "space-guid-1"}, 355 {"guid": "space-guid-2"}, 356 }, 357 }, 358 }, 359 } 360 361 server.AppendHandlers( 362 CombineHandlers( 363 VerifyRequest(http.MethodPost, "/v3/space_quotas"), 364 VerifyJSONRepresenting(expectedBody), 365 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"space-quota-warning"}}), 366 ), 367 ) 368 }) 369 370 It("returns space quota and warnings", func() { 371 Expect(executeErr).NotTo(HaveOccurred()) 372 Expect(warnings).To(ConsistOf("space-quota-warning")) 373 Expect(createdSpaceQuota).To(Equal( 374 resources.SpaceQuota{ 375 Quota: resources.Quota{ 376 GUID: "space-quota-guid", 377 Name: "my-space-quota", 378 Apps: resources.AppLimit{ 379 TotalMemory: &types.NullInt{IsSet: true, Value: 2}, 380 InstanceMemory: &types.NullInt{IsSet: true, Value: 3}, 381 TotalAppInstances: &types.NullInt{IsSet: true, Value: 4}, 382 TotalLogVolume: &types.NullInt{IsSet: true, Value: 8}, 383 }, 384 Services: resources.ServiceLimit{ 385 PaidServicePlans: &trueValue, 386 TotalServiceInstances: &types.NullInt{IsSet: true, Value: 6}, 387 }, 388 Routes: resources.RouteLimit{ 389 TotalRoutes: &types.NullInt{IsSet: true, Value: 8}, 390 TotalReservedPorts: &types.NullInt{IsSet: true, Value: 9}, 391 }, 392 }, 393 OrgGUID: "some-org-guid", 394 SpaceGUIDs: []string{"space-guid-1", "space-guid-2"}, 395 })) 396 }) 397 }) 398 399 When("the cloud controller returns errors and warnings", func() { 400 BeforeEach(func() { 401 response := `{ 402 "errors": [ 403 { 404 "code": 10008, 405 "detail": "The request is semantically invalid: command presence", 406 "title": "CF-UnprocessableEntity" 407 }, 408 { 409 "code": 10010, 410 "detail": "App not found", 411 "title": "CF-ResourceNotFound" 412 } 413 ] 414 }` 415 server.AppendHandlers( 416 CombineHandlers( 417 VerifyRequest(http.MethodPost, "/v3/space_quotas"), 418 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 419 ), 420 ) 421 }) 422 423 It("returns the error and all warnings", func() { 424 Expect(executeErr).To(MatchError(ccerror.MultiError{ 425 ResponseCode: http.StatusTeapot, 426 Errors: []ccerror.V3Error{ 427 { 428 Code: 10008, 429 Detail: "The request is semantically invalid: command presence", 430 Title: "CF-UnprocessableEntity", 431 }, 432 { 433 Code: 10010, 434 Detail: "App not found", 435 Title: "CF-ResourceNotFound", 436 }, 437 }, 438 })) 439 Expect(warnings).To(ConsistOf("this is a warning")) 440 }) 441 }) 442 }) 443 444 Describe("DeleteSpaceQuota", func() { 445 var ( 446 jobURL JobURL 447 warnings Warnings 448 executeErr error 449 450 spaceQuotaGUID = "space-quota-guid" 451 ) 452 453 JustBeforeEach(func() { 454 jobURL, warnings, executeErr = client.DeleteSpaceQuota(spaceQuotaGUID) 455 }) 456 457 When("the cloud controller returns without errors", func() { 458 BeforeEach(func() { 459 server.AppendHandlers( 460 CombineHandlers( 461 VerifyRequest(http.MethodDelete, "/v3/space_quotas/space-quota-guid"), 462 RespondWith(http.StatusAccepted, nil, http.Header{"X-Cf-Warnings": {"some-quota-warning"}, "Location": {"some-job-url"}}), 463 ), 464 ) 465 }) 466 467 It("returns a URL to the deletion job", func() { 468 Expect(jobURL).To(Equal(JobURL("some-job-url"))) 469 Expect(executeErr).NotTo(HaveOccurred()) 470 Expect(warnings).To(ConsistOf("some-quota-warning")) 471 }) 472 }) 473 474 When("the cloud controller returns errors and warnings", func() { 475 BeforeEach(func() { 476 response := `{ 477 "errors": [ 478 { 479 "code": 10010, 480 "detail": "Space quota not found", 481 "title": "CF-ResourceNotFound" 482 } 483 ] 484 }` 485 486 server.AppendHandlers( 487 CombineHandlers( 488 VerifyRequest(http.MethodDelete, "/v3/space_quotas/space-quota-guid"), 489 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 490 ), 491 ) 492 }) 493 494 It("returns the error and all warnings", func() { 495 Expect(executeErr).To(MatchError(ccerror.V3UnexpectedResponseError{ 496 ResponseCode: http.StatusTeapot, 497 V3ErrorResponse: ccerror.V3ErrorResponse{ 498 Errors: []ccerror.V3Error{ 499 { 500 Code: 10010, 501 Detail: "Space quota not found", 502 Title: "CF-ResourceNotFound", 503 }, 504 }, 505 }, 506 })) 507 Expect(warnings).To(ConsistOf("this is a warning")) 508 }) 509 }) 510 }) 511 512 Describe("GetSpaceQuota", func() { 513 var ( 514 spaceQuota resources.SpaceQuota 515 spaceQuotaGUID = "space-quota-guid" 516 ) 517 518 JustBeforeEach(func() { 519 spaceQuota, warnings, executeErr = client.GetSpaceQuota(spaceQuotaGUID) 520 }) 521 When("the cloud controller returns without errors", func() { 522 BeforeEach(func() { 523 524 response := fmt.Sprintf(`{ 525 "guid": "space-quota-guid", 526 "created_at": "2017-05-04T17:00:41Z", 527 "updated_at": "2017-05-04T17:00:41Z", 528 "name": "sancho-panza", 529 "apps": { 530 "total_memory_in_mb": 10240, 531 "per_process_memory_in_mb": 1024, 532 "total_instances": 8, 533 "per_app_tasks": 5, 534 "log_rate_limit_in_bytes_per_second": 8 535 }, 536 "services": { 537 "paid_services_allowed": false, 538 "total_service_instances": 8, 539 "total_service_keys": 20 540 }, 541 "routes": { 542 "total_routes": 10, 543 "total_reserved_ports": 5 544 }, 545 "domains": { 546 "total_private_domains": 7 547 }, 548 "relationships": { 549 "organization": { 550 "data": null 551 } 552 }, 553 "links": { 554 "self": { "href": "%s/v3/space_quotas/space-quota-guid" } 555 } 556 }`, server.URL()) 557 558 server.AppendHandlers( 559 CombineHandlers( 560 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/space_quotas/%s", spaceQuotaGUID)), 561 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"get warning"}}), 562 ), 563 ) 564 }) 565 566 It("queries the API with the given guid", func() { 567 Expect(executeErr).NotTo(HaveOccurred()) 568 Expect(warnings).To(ConsistOf("get warning")) 569 Expect(spaceQuota).To(Equal( 570 resources.SpaceQuota{ 571 Quota: resources.Quota{ 572 GUID: "space-quota-guid", 573 Name: "sancho-panza", 574 Apps: resources.AppLimit{ 575 TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, 576 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 577 TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, 578 TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, 579 }, 580 Services: resources.ServiceLimit{ 581 TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, 582 PaidServicePlans: &falseValue, 583 }, 584 Routes: resources.RouteLimit{ 585 TotalRoutes: &types.NullInt{Value: 10, IsSet: true}, 586 TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, 587 }, 588 }, 589 }, 590 )) 591 }) 592 }) 593 594 When("the cloud controller returns errors and warnings", func() { 595 BeforeEach(func() { 596 response := `{ 597 "errors": [ 598 { 599 "code": 10008, 600 "detail": "The request is semantically invalid: command presence", 601 "title": "CF-UnprocessableEntity" 602 }, 603 { 604 "code": 10010, 605 "detail": "App not found", 606 "title": "CF-ResourceNotFound" 607 } 608 ] 609 }` 610 server.AppendHandlers( 611 CombineHandlers( 612 VerifyRequest(http.MethodGet, fmt.Sprintf("/v3/space_quotas/%s", spaceQuotaGUID)), 613 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 614 ), 615 ) 616 }) 617 618 It("returns the error and all warnings", func() { 619 Expect(executeErr).To(MatchError(ccerror.MultiError{ 620 ResponseCode: http.StatusTeapot, 621 Errors: []ccerror.V3Error{ 622 { 623 Code: 10008, 624 Detail: "The request is semantically invalid: command presence", 625 Title: "CF-UnprocessableEntity", 626 }, 627 { 628 Code: 10010, 629 Detail: "App not found", 630 Title: "CF-ResourceNotFound", 631 }, 632 }, 633 })) 634 Expect(warnings).To(ConsistOf("this is a warning")) 635 }) 636 }) 637 }) 638 639 Describe("GetSpaceQuotas", func() { 640 var ( 641 spaceQuotas []resources.SpaceQuota 642 query Query 643 ) 644 645 JustBeforeEach(func() { 646 spaceQuotas, warnings, executeErr = client.GetSpaceQuotas(query) 647 }) 648 649 When("the cloud controller returns without errors", func() { 650 BeforeEach(func() { 651 response1 := fmt.Sprintf(`{ 652 "pagination": { 653 "total_results": 1, 654 "total_pages": 1, 655 "first": { 656 "href": "%s/v3/space_quotas?page=1&per_page=1" 657 }, 658 "last": { 659 "href": "%s/v3/space_quotas?page=2&per_page=1" 660 }, 661 "next": { 662 "href": "%s/v3/space_quotas?page=2&per_page=1" 663 }, 664 "previous": null 665 }, 666 "resources": [ 667 { 668 "guid": "quota-guid", 669 "created_at": "2016-05-04T17:00:41Z", 670 "updated_at": "2016-05-04T17:00:41Z", 671 "name": "don-quixote", 672 "apps": { 673 "total_memory_in_mb": 5120, 674 "per_process_memory_in_mb": 1024, 675 "total_instances": 10, 676 "per_app_tasks": 5, 677 "log_rate_limit_in_bytes_per_second": 8 678 }, 679 "services": { 680 "paid_services_allowed": true, 681 "total_service_instances": 10, 682 "total_service_keys": 20 683 }, 684 "routes": { 685 "total_routes": 8, 686 "total_reserved_ports": 4 687 }, 688 "domains": { 689 "total_private_domains": 7 690 }, 691 "relationships": { 692 "organization": { 693 "data": { "guid": "org-guid-1" } 694 } 695 }, 696 "links": { 697 "self": { "href": "%s/v3/space_quotas/quota-guid" } 698 } 699 } 700 ] 701 }`, server.URL(), server.URL(), server.URL(), server.URL()) 702 703 response2 := fmt.Sprintf(`{ 704 "pagination": { 705 "next": null 706 }, 707 "resources": [ 708 { 709 "guid": "quota-2-guid", 710 "created_at": "2017-05-04T17:00:41Z", 711 "updated_at": "2017-05-04T17:00:41Z", 712 "name": "sancho-panza", 713 "apps": { 714 "total_memory_in_mb": 10240, 715 "per_process_memory_in_mb": 1024, 716 "total_instances": 8, 717 "per_app_tasks": 5, 718 "log_rate_limit_in_bytes_per_second": 16 719 }, 720 "services": { 721 "paid_services_allowed": false, 722 "total_service_instances": 8, 723 "total_service_keys": 20 724 }, 725 "routes": { 726 "total_routes": 10, 727 "total_reserved_ports": 5 728 }, 729 "domains": { 730 "total_private_domains": 7 731 }, 732 "relationships": { 733 "organization": { 734 "data": null 735 } 736 }, 737 "links": { 738 "self": { "href": "%s/v3/space_quotas/quota-2-guid" } 739 } 740 } 741 ] 742 }`, server.URL()) 743 744 server.AppendHandlers( 745 CombineHandlers( 746 VerifyRequest(http.MethodGet, "/v3/space_quotas"), 747 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"page1 warning"}}), 748 ), 749 ) 750 751 server.AppendHandlers( 752 CombineHandlers( 753 VerifyRequest(http.MethodGet, "/v3/space_quotas", "page=2&per_page=1"), 754 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"page2 warning"}}), 755 ), 756 ) 757 }) 758 759 It("returns space quotas and warnings", func() { 760 Expect(executeErr).NotTo(HaveOccurred()) 761 Expect(warnings).To(ConsistOf("page1 warning", "page2 warning")) 762 Expect(spaceQuotas).To(ConsistOf( 763 resources.SpaceQuota{ 764 Quota: resources.Quota{ 765 GUID: "quota-guid", 766 Name: "don-quixote", 767 Apps: resources.AppLimit{ 768 TotalMemory: &types.NullInt{Value: 5120, IsSet: true}, 769 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 770 TotalAppInstances: &types.NullInt{Value: 10, IsSet: true}, 771 TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, 772 }, 773 Services: resources.ServiceLimit{ 774 TotalServiceInstances: &types.NullInt{Value: 10, IsSet: true}, 775 PaidServicePlans: &trueValue, 776 }, 777 Routes: resources.RouteLimit{ 778 TotalRoutes: &types.NullInt{Value: 8, IsSet: true}, 779 TotalReservedPorts: &types.NullInt{Value: 4, IsSet: true}, 780 }, 781 }, 782 OrgGUID: "org-guid-1", 783 }, 784 resources.SpaceQuota{ 785 Quota: resources.Quota{ 786 GUID: "quota-2-guid", 787 Name: "sancho-panza", 788 Apps: resources.AppLimit{ 789 TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, 790 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 791 TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, 792 TotalLogVolume: &types.NullInt{Value: 16, IsSet: true}, 793 }, 794 Services: resources.ServiceLimit{ 795 TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, 796 PaidServicePlans: &falseValue, 797 }, 798 Routes: resources.RouteLimit{ 799 TotalRoutes: &types.NullInt{Value: 10, IsSet: true}, 800 TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, 801 }, 802 }, 803 }, 804 )) 805 }) 806 }) 807 808 When("requesting quotas by name", func() { 809 BeforeEach(func() { 810 query = Query{ 811 Key: NameFilter, 812 Values: []string{"sancho-panza"}, 813 } 814 815 response := fmt.Sprintf(`{ 816 "pagination": { 817 "next": null 818 }, 819 "resources": [ 820 { 821 "guid": "quota-2-guid", 822 "created_at": "2017-05-04T17:00:41Z", 823 "updated_at": "2017-05-04T17:00:41Z", 824 "name": "sancho-panza", 825 "apps": { 826 "total_memory_in_mb": 10240, 827 "per_process_memory_in_mb": 1024, 828 "total_instances": 8, 829 "per_app_tasks": 5, 830 "log_rate_limit_in_bytes_per_second": 8 831 }, 832 "services": { 833 "paid_services_allowed": false, 834 "total_service_instances": 8, 835 "total_service_keys": 20 836 }, 837 "routes": { 838 "total_routes": 10, 839 "total_reserved_ports": 5 840 }, 841 "domains": { 842 "total_private_domains": 7 843 }, 844 "relationships": { 845 "organization": { 846 "data": null 847 } 848 }, 849 "links": { 850 "self": { "href": "%s/v3/space_quotas/quota-2-guid" } 851 } 852 } 853 ] 854 }`, server.URL()) 855 856 server.AppendHandlers( 857 CombineHandlers( 858 VerifyRequest(http.MethodGet, "/v3/space_quotas", "names=sancho-panza"), 859 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"page1 warning"}}), 860 ), 861 ) 862 }) 863 864 It("queries the API with the given name", func() { 865 Expect(executeErr).NotTo(HaveOccurred()) 866 Expect(warnings).To(ConsistOf("page1 warning")) 867 Expect(spaceQuotas).To(ConsistOf( 868 resources.SpaceQuota{ 869 Quota: resources.Quota{ 870 GUID: "quota-2-guid", 871 Name: "sancho-panza", 872 Apps: resources.AppLimit{ 873 TotalMemory: &types.NullInt{Value: 10240, IsSet: true}, 874 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 875 TotalAppInstances: &types.NullInt{Value: 8, IsSet: true}, 876 TotalLogVolume: &types.NullInt{Value: 8, IsSet: true}, 877 }, 878 Services: resources.ServiceLimit{ 879 TotalServiceInstances: &types.NullInt{Value: 8, IsSet: true}, 880 PaidServicePlans: &falseValue, 881 }, 882 Routes: resources.RouteLimit{ 883 TotalRoutes: &types.NullInt{Value: 10, IsSet: true}, 884 TotalReservedPorts: &types.NullInt{Value: 5, IsSet: true}, 885 }, 886 }, 887 }, 888 )) 889 }) 890 }) 891 892 When("the cloud controller returns errors and warnings", func() { 893 BeforeEach(func() { 894 response := `{ 895 "errors": [ 896 { 897 "code": 10008, 898 "detail": "The request is semantically invalid: command presence", 899 "title": "CF-UnprocessableEntity" 900 }, 901 { 902 "code": 10010, 903 "detail": "App not found", 904 "title": "CF-ResourceNotFound" 905 } 906 ] 907 }` 908 server.AppendHandlers( 909 CombineHandlers( 910 VerifyRequest(http.MethodGet, "/v3/space_quotas"), 911 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 912 ), 913 ) 914 }) 915 916 It("returns the error and all warnings", func() { 917 Expect(executeErr).To(MatchError(ccerror.MultiError{ 918 ResponseCode: http.StatusTeapot, 919 Errors: []ccerror.V3Error{ 920 { 921 Code: 10008, 922 Detail: "The request is semantically invalid: command presence", 923 Title: "CF-UnprocessableEntity", 924 }, 925 { 926 Code: 10010, 927 Detail: "App not found", 928 Title: "CF-ResourceNotFound", 929 }, 930 }, 931 })) 932 Expect(warnings).To(ConsistOf("this is a warning")) 933 }) 934 }) 935 }) 936 937 Describe("UnsetSpaceQuota", func() { 938 var ( 939 spaceGUID = "space-guid" 940 spaceQuotaGUID = "space-quota-guid" 941 warnings Warnings 942 executeErr error 943 ) 944 945 JustBeforeEach(func() { 946 warnings, executeErr = client.UnsetSpaceQuota( 947 spaceQuotaGUID, 948 spaceGUID, 949 ) 950 }) 951 952 When("the request succeeds", func() { 953 BeforeEach(func() { 954 server.AppendHandlers( 955 CombineHandlers( 956 VerifyRequest(http.MethodDelete, fmt.Sprintf("/v3/space_quotas/%s/relationships/spaces/%s", spaceQuotaGUID, spaceGUID)), 957 RespondWith(http.StatusNoContent, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 958 ), 959 ) 960 }) 961 962 It("returns all warnings", func() { 963 Expect(warnings).To(ConsistOf("this is a warning")) 964 Expect(executeErr).To(BeNil()) 965 }) 966 }) 967 968 When("the cloud controller returns errors and warnings", func() { 969 BeforeEach(func() { 970 response := `{ 971 "errors": [ 972 { 973 "code": 10008, 974 "detail": "The request is semantically invalid: command presence", 975 "title": "CF-UnprocessableEntity" 976 }, 977 { 978 "code": 10010, 979 "detail": "Organization not found", 980 "title": "CF-ResourceNotFound" 981 } 982 ] 983 }` 984 server.AppendHandlers( 985 CombineHandlers( 986 VerifyRequest(http.MethodDelete, fmt.Sprintf("/v3/space_quotas/%s/relationships/spaces/%s", spaceQuotaGUID, spaceGUID)), 987 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 988 ), 989 ) 990 }) 991 992 It("returns the error and all warnings", func() { 993 Expect(executeErr).To(MatchError(ccerror.MultiError{ 994 ResponseCode: http.StatusTeapot, 995 Errors: []ccerror.V3Error{ 996 { 997 Code: 10008, 998 Detail: "The request is semantically invalid: command presence", 999 Title: "CF-UnprocessableEntity", 1000 }, 1001 { 1002 Code: 10010, 1003 Detail: "Organization not found", 1004 Title: "CF-ResourceNotFound", 1005 }, 1006 }, 1007 })) 1008 Expect(warnings).To(ConsistOf("this is a warning")) 1009 }) 1010 }) 1011 }) 1012 1013 Describe("UpdateSpaceQuota", func() { 1014 var ( 1015 updatedSpaceQuota resources.SpaceQuota 1016 warnings Warnings 1017 executeErr error 1018 inputQuota resources.SpaceQuota 1019 ) 1020 1021 BeforeEach(func() { 1022 inputQuota = resources.SpaceQuota{ 1023 Quota: resources.Quota{ 1024 GUID: "elephant-trunk-guid", 1025 Name: "elephant-trunk", 1026 Apps: resources.AppLimit{ 1027 TotalMemory: &types.NullInt{Value: 2048, IsSet: true}, 1028 InstanceMemory: &types.NullInt{Value: 1024, IsSet: true}, 1029 TotalAppInstances: &types.NullInt{Value: 0, IsSet: false}, 1030 TotalLogVolume: &types.NullInt{Value: 0, IsSet: false}, 1031 }, 1032 Services: resources.ServiceLimit{ 1033 TotalServiceInstances: &types.NullInt{Value: 0, IsSet: true}, 1034 PaidServicePlans: &trueValue, 1035 }, 1036 }, 1037 } 1038 }) 1039 1040 JustBeforeEach(func() { 1041 updatedSpaceQuota, warnings, executeErr = client.UpdateSpaceQuota(inputQuota) 1042 }) 1043 1044 When("updating the quota succeeds", func() { 1045 BeforeEach(func() { 1046 response := `{ 1047 "guid": "elephant-trunk-guid", 1048 "created_at": "2020-01-16T19:44:47Z", 1049 "updated_at": "2020-01-16T19:44:47Z", 1050 "name": "elephant-trunk", 1051 "apps": { 1052 "total_memory_in_mb": 2048, 1053 "per_process_memory_in_mb": 1024, 1054 "total_instances": null, 1055 "per_app_tasks": null, 1056 "log_rate_limit_in_bytes_per_second": null 1057 }, 1058 "services": { 1059 "paid_services_allowed": true, 1060 "total_service_instances": 0, 1061 "total_service_keys": null 1062 }, 1063 "routes": { 1064 "total_routes": null, 1065 "total_reserved_ports": null 1066 }, 1067 "links": { 1068 "self": { 1069 "href": "https://api.foil-venom.lite.cli.fun/v3/space_quotas/08357710-8106-4d14-b0ea-03154a36fb79" 1070 } 1071 } 1072 }` 1073 1074 expectedBody := map[string]interface{}{ 1075 "name": "elephant-trunk", 1076 "apps": map[string]interface{}{ 1077 "total_memory_in_mb": 2048, 1078 "per_process_memory_in_mb": 1024, 1079 "total_instances": nil, 1080 "log_rate_limit_in_bytes_per_second": nil, 1081 }, 1082 "services": map[string]interface{}{ 1083 "paid_services_allowed": true, 1084 "total_service_instances": 0, 1085 }, 1086 "routes": map[string]interface{}{}, 1087 } 1088 1089 server.AppendHandlers( 1090 CombineHandlers( 1091 VerifyRequest(http.MethodPatch, "/v3/space_quotas/elephant-trunk-guid"), 1092 VerifyJSONRepresenting(expectedBody), 1093 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 1094 ), 1095 ) 1096 }) 1097 1098 It("returns the updated space quota", func() { 1099 Expect(executeErr).ToNot(HaveOccurred()) 1100 Expect(warnings).To(ConsistOf("this is a warning")) 1101 1102 Expect(updatedSpaceQuota).To(Equal(resources.SpaceQuota{ 1103 Quota: resources.Quota{ 1104 GUID: "elephant-trunk-guid", 1105 Name: "elephant-trunk", 1106 Apps: resources.AppLimit{ 1107 TotalMemory: &types.NullInt{IsSet: true, Value: 2048}, 1108 InstanceMemory: &types.NullInt{IsSet: true, Value: 1024}, 1109 TotalAppInstances: &types.NullInt{IsSet: false, Value: 0}, 1110 TotalLogVolume: &types.NullInt{IsSet: false, Value: 0}, 1111 }, 1112 Services: resources.ServiceLimit{ 1113 TotalServiceInstances: &types.NullInt{IsSet: true, Value: 0}, 1114 PaidServicePlans: &trueValue, 1115 }, 1116 Routes: resources.RouteLimit{ 1117 TotalRoutes: &types.NullInt{IsSet: false, Value: 0}, 1118 TotalReservedPorts: &types.NullInt{IsSet: false, Value: 0}, 1119 }, 1120 }, 1121 })) 1122 }) 1123 }) 1124 1125 When("updating the quota fails", func() { 1126 BeforeEach(func() { 1127 response := `{ 1128 "errors": [ 1129 { 1130 "detail": "Fail", 1131 "title": "CF-SomeError", 1132 "code": 10002 1133 }, 1134 { 1135 "detail": "Something went terribly wrong", 1136 "title": "CF-UnknownError", 1137 "code": 10001 1138 } 1139 ] 1140 }` 1141 1142 server.AppendHandlers( 1143 CombineHandlers( 1144 VerifyRequest(http.MethodPatch, "/v3/space_quotas/elephant-trunk-guid"), 1145 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 1146 ), 1147 ) 1148 }) 1149 1150 It("returns an error", func() { 1151 Expect(executeErr).To(MatchError(ccerror.MultiError{ 1152 ResponseCode: http.StatusTeapot, 1153 Errors: []ccerror.V3Error{ 1154 { 1155 Code: 10002, 1156 Detail: "Fail", 1157 Title: "CF-SomeError", 1158 }, 1159 { 1160 Code: 10001, 1161 Detail: "Something went terribly wrong", 1162 Title: "CF-UnknownError", 1163 }, 1164 }, 1165 })) 1166 Expect(warnings).To(ConsistOf("this is a warning")) 1167 }) 1168 }) 1169 }) 1170 })