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