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