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  })