github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/organization_quota_test.go (about)

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