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