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

     1  package ccv3_test
     2  
     3  import (
     4  	"net/http"
     5  
     6  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
     7  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
     8  
     9  	. "github.com/onsi/ginkgo"
    10  	. "github.com/onsi/gomega"
    11  	. "github.com/onsi/gomega/ghttp"
    12  )
    13  
    14  var _ = Describe("Error Wrapper", func() {
    15  	var client *Client
    16  
    17  	BeforeEach(func() {
    18  		client, _ = NewTestClient()
    19  	})
    20  
    21  	Describe("Make", func() {
    22  		var (
    23  			serverResponse     string
    24  			serverResponseCode int
    25  			makeError          error
    26  		)
    27  
    28  		BeforeEach(func() {
    29  			serverResponse = `
    30  {
    31    "errors": [
    32      {
    33        "code": 777,
    34        "detail": "SomeCC Error Message",
    35        "title": "CF-SomeError"
    36      }
    37    ]
    38  }`
    39  
    40  		})
    41  
    42  		JustBeforeEach(func() {
    43  			server.AppendHandlers(
    44  				CombineHandlers(
    45  					VerifyRequest(http.MethodGet, "/v3/apps"),
    46  					RespondWith(serverResponseCode, serverResponse, http.Header{
    47  						"X-Vcap-Request-Id": {
    48  							"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95",
    49  							"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f",
    50  						},
    51  					},
    52  					),
    53  				),
    54  			)
    55  
    56  			_, _, makeError = client.GetApplications()
    57  		})
    58  
    59  		When("we can't unmarshal the response successfully", func() {
    60  			BeforeEach(func() {
    61  				serverResponse = "I am not unmarshallable"
    62  				serverResponseCode = http.StatusNotFound
    63  			})
    64  
    65  			It("returns an unknown http source error", func() {
    66  				Expect(makeError).To(MatchError(ccerror.UnknownHTTPSourceError{StatusCode: serverResponseCode, RawResponse: []byte(serverResponse)}))
    67  			})
    68  		})
    69  
    70  		When("the error is from the cloud controller", func() {
    71  			When("an empty list of errors is returned", func() {
    72  				BeforeEach(func() {
    73  					serverResponseCode = http.StatusUnauthorized
    74  					serverResponse = `{ "errors": [] }`
    75  				})
    76  
    77  				It("returns an UnexpectedResponseError", func() {
    78  					Expect(makeError).To(MatchError(ccerror.V3UnexpectedResponseError{
    79  						ResponseCode:    http.StatusUnauthorized,
    80  						V3ErrorResponse: ccerror.V3ErrorResponse{Errors: []ccerror.V3Error{}},
    81  					}))
    82  				})
    83  			})
    84  
    85  			When("the error is a 4XX error", func() {
    86  				Context("(400) Bad Request", func() {
    87  					BeforeEach(func() {
    88  						serverResponseCode = http.StatusBadRequest
    89  					})
    90  
    91  					When("the query parameter is invalid", func() {
    92  						BeforeEach(func() {
    93  							serverResponse = `
    94  {
    95     "errors": [
    96        {
    97           "detail": "The query parameter is invalid: Missing label_selector value",
    98           "title": "CF-BadQueryParameter",
    99           "code": 10005
   100        }
   101     ]
   102  }`
   103  						})
   104  
   105  						It("returns a BadRequestError", func() {
   106  							Expect(makeError).To(MatchError(ccerror.BadRequestError{Message: "The query parameter is invalid: Missing label_selector value"}))
   107  						})
   108  
   109  					})
   110  
   111  					When("service instance fetch params not supported", func() {
   112  						BeforeEach(func() {
   113  							serverResponse = `
   114  {
   115     "errors": [
   116        {
   117           "detail": "This service does not support fetching service instance parameters.",
   118           "title": "CF-ServiceFetchInstanceParametersNotSupported",
   119           "code": 120004
   120        }
   121     ]
   122  }`
   123  						})
   124  
   125  						It("returns a ServiceInstanceParametersFetchNotSupportedError", func() {
   126  							Expect(makeError).To(MatchError(ccerror.ServiceInstanceParametersFetchNotSupportedError{
   127  								Message: "This service does not support fetching service instance parameters."}))
   128  						})
   129  					})
   130  				})
   131  
   132  				Context("(401) Unauthorized", func() {
   133  					BeforeEach(func() {
   134  						serverResponseCode = http.StatusUnauthorized
   135  					})
   136  
   137  					Context("generic 401", func() {
   138  						It("returns a UnauthorizedError", func() {
   139  							Expect(makeError).To(MatchError(ccerror.UnauthorizedError{Message: "SomeCC Error Message"}))
   140  						})
   141  					})
   142  
   143  					Context("invalid token", func() {
   144  						BeforeEach(func() {
   145  							serverResponse = `{
   146  							"errors": [
   147  								{
   148  									"code": 1000,
   149  									"detail": "Invalid Auth Token",
   150  									"title": "CF-InvalidAuthToken"
   151  								}
   152  							]
   153  						}`
   154  						})
   155  
   156  						It("returns an InvalidAuthTokenError", func() {
   157  							Expect(makeError).To(MatchError(ccerror.InvalidAuthTokenError{Message: "Invalid Auth Token"}))
   158  						})
   159  					})
   160  				})
   161  
   162  				Context("(403) Forbidden", func() {
   163  					BeforeEach(func() {
   164  						serverResponseCode = http.StatusForbidden
   165  					})
   166  
   167  					It("returns a ForbiddenError", func() {
   168  						Expect(makeError).To(MatchError(ccerror.ForbiddenError{Message: "SomeCC Error Message"}))
   169  					})
   170  				})
   171  
   172  				Context("(404) Not Found", func() {
   173  					BeforeEach(func() {
   174  						serverResponseCode = http.StatusNotFound
   175  					})
   176  
   177  					Context("API is not found", func() {
   178  
   179  						BeforeEach(func() {
   180  							serverResponse = `{
   181  								"errors": [
   182  									{
   183  										"detail": "Unknown request",
   184  										"title": "CF-NotFound",
   185  										"code": 10000
   186  									}
   187  								]
   188  							}`
   189  						})
   190  
   191  						It("returns a APINotFoundError", func() {
   192  							Expect(makeError).To(MatchError(ccerror.APINotFoundError{URL: server.URL() + "/v3/apps"}))
   193  						})
   194  					})
   195  
   196  					When("a process is not found", func() {
   197  						BeforeEach(func() {
   198  							serverResponse = `
   199  {
   200    "errors": [
   201      {
   202        "code": 10010,
   203        "detail": "Process not found",
   204        "title": "CF-ResourceNotFound"
   205      }
   206    ]
   207  }`
   208  						})
   209  
   210  						It("returns a ProcessNotFoundError", func() {
   211  							Expect(makeError).To(MatchError(ccerror.ProcessNotFoundError{}))
   212  						})
   213  					})
   214  
   215  					When("an instance is not found", func() {
   216  						BeforeEach(func() {
   217  							serverResponse = `
   218  {
   219    "errors": [
   220      {
   221        "code": 10010,
   222        "detail": "Instance not found",
   223        "title": "CF-ResourceNotFound"
   224      }
   225    ]
   226  }`
   227  						})
   228  
   229  						It("returns an InstanceNotFoundError", func() {
   230  							Expect(makeError).To(MatchError(ccerror.InstanceNotFoundError{}))
   231  						})
   232  					})
   233  
   234  					When("an application is not found", func() {
   235  						BeforeEach(func() {
   236  							serverResponse = `
   237  {
   238    "errors": [
   239      {
   240        "code": 10010,
   241        "detail": "App not found",
   242        "title": "CF-ResourceNotFound"
   243      }
   244    ]
   245  }`
   246  						})
   247  
   248  						It("returns an AppNotFoundError", func() {
   249  							Expect(makeError).To(MatchError(ccerror.ApplicationNotFoundError{}))
   250  						})
   251  					})
   252  
   253  					When("a droplet is not found", func() {
   254  						BeforeEach(func() {
   255  							serverResponse = `
   256  {
   257    "errors": [
   258      {
   259        "code": 10010,
   260        "detail": "Droplet not found",
   261        "title": "CF-ResourceNotFound"
   262      }
   263    ]
   264  }`
   265  						})
   266  
   267  						It("returns a DropletNotFoundError", func() {
   268  							Expect(makeError).To(MatchError(ccerror.DropletNotFoundError{}))
   269  						})
   270  					})
   271  
   272  					When("a user is not found", func() {
   273  						BeforeEach(func() {
   274  							serverResponse = `
   275  							{
   276  							  "errors": [
   277  							    {
   278  							      "code": 10010,
   279  							      "detail": "User not found",
   280  							      "title": "CF-ResourceNotFound"
   281  							    }
   282  							  ]
   283  							}`
   284  						})
   285  
   286  						It("returns a UserNotFoundError", func() {
   287  							Expect(makeError).To(MatchError(ccerror.UserNotFoundError{}))
   288  						})
   289  					})
   290  
   291  					Context("generic not found", func() {
   292  						It("returns a ResourceNotFoundError", func() {
   293  							Expect(makeError).To(MatchError(ccerror.ResourceNotFoundError{Message: "SomeCC Error Message"}))
   294  						})
   295  					})
   296  				})
   297  
   298  				Context("(409) Conflict", func() {
   299  					BeforeEach(func() {
   300  						serverResponseCode = http.StatusConflict
   301  					})
   302  
   303  					When("a service instance operation is in progress", func() {
   304  						BeforeEach(func() {
   305  							serverResponse = `
   306  {
   307    "errors": [
   308      {
   309        "code": 60016,
   310        "detail": "An operation for service instance foo is in progress.",
   311        "title": "CF-AsyncServiceInstanceOperationInProgress"
   312      }
   313    ]
   314  }`
   315  						})
   316  
   317  						It("returns a ServiceInstanceOperationInProgressError", func() {
   318  							Expect(makeError).To(MatchError(ccerror.ServiceInstanceOperationInProgressError{
   319  								Message: "An operation for service instance foo is in progress.",
   320  							}))
   321  						})
   322  					})
   323  				})
   324  
   325  				Context("(422) Unprocessable Entity", func() {
   326  					BeforeEach(func() {
   327  						serverResponseCode = http.StatusUnprocessableEntity
   328  					})
   329  
   330  					When("the name isn't unique to space (old error message)", func() {
   331  						BeforeEach(func() {
   332  							serverResponse = `
   333  {
   334    "errors": [
   335      {
   336        "code": 10008,
   337        "detail": "name must be unique in space",
   338        "title": "CF-UnprocessableEntity"
   339      }
   340    ]
   341  }`
   342  						})
   343  
   344  						It("returns a NameNotUniqueInSpaceError", func() {
   345  							Expect(makeError).To(Equal(
   346  								ccerror.NameNotUniqueInSpaceError{
   347  									UnprocessableEntityError: ccerror.UnprocessableEntityError{
   348  										Message: "name must be unique in space",
   349  									},
   350  								},
   351  							))
   352  						})
   353  					})
   354  
   355  					When("the name isn't unique to space (new error message)", func() {
   356  						BeforeEach(func() {
   357  							serverResponse = `
   358  {
   359    "errors": [
   360      {
   361        "code": 10008,
   362        "detail": "App with the name 'eli' already exists.",
   363        "title": "CF-UnprocessableEntity"
   364      }
   365    ]
   366  }`
   367  						})
   368  
   369  						It("returns a NameNotUniqueInSpaceError", func() {
   370  							Expect(makeError).To(Equal(
   371  								ccerror.NameNotUniqueInSpaceError{
   372  									UnprocessableEntityError: ccerror.UnprocessableEntityError{
   373  										Message: "App with the name 'eli' already exists.",
   374  									},
   375  								},
   376  							))
   377  						})
   378  					})
   379  
   380  					When("the name isn't unique to organization", func() {
   381  						BeforeEach(func() {
   382  							serverResponse = `
   383  {
   384    "errors": [
   385      {
   386        "code": 10008,
   387        "detail": "Name must be unique per organization",
   388        "title": "CF-UnprocessableEntity"
   389      }
   390    ]
   391  }`
   392  						})
   393  
   394  						It("returns a NameNotUniqueInOrgError", func() {
   395  							Expect(makeError).To(MatchError(ccerror.NameNotUniqueInOrgError{}))
   396  						})
   397  					})
   398  
   399  					When("the role already exists", func() {
   400  						BeforeEach(func() {
   401  							serverResponse = `
   402  {
   403    "errors": [
   404      {
   405        "code": 10008,
   406        "detail": "User 'wow' already has 'organization_auditor' role in organization 'wow'.",
   407        "title": "CF-UnprocessableEntity"
   408      }
   409    ]
   410  }`
   411  						})
   412  
   413  						It("returns a RoleAlreadyExistsError", func() {
   414  							Expect(makeError).To(Equal(
   415  								ccerror.RoleAlreadyExistsError{
   416  									UnprocessableEntityError: ccerror.UnprocessableEntityError{
   417  										Message: "User 'wow' already has 'organization_auditor' role in organization 'wow'.",
   418  									},
   419  								}),
   420  							)
   421  						})
   422  					})
   423  
   424  					When("the quota already exists", func() {
   425  						BeforeEach(func() {
   426  							serverResponse = `
   427  {
   428    "errors": [
   429      {
   430        "code": 10008,
   431        "detail": "Organization Quota 'default' already exists.",
   432        "title": "CF-UnprocessableEntity"
   433      }
   434    ]
   435  }`
   436  						})
   437  
   438  						It("returns a QuotaAlreadyExists error", func() {
   439  							Expect(makeError).To(Equal(
   440  								ccerror.QuotaAlreadyExists{
   441  									Message: "Organization Quota 'default' already exists.",
   442  								}),
   443  							)
   444  						})
   445  					})
   446  
   447  					When("the security group already exists", func() {
   448  						BeforeEach(func() {
   449  							serverResponse = `
   450  {
   451    "errors": [
   452      {
   453        "detail": "Security group with name 'sec-group' already exists.",
   454        "title": "CF-UnprocessableEntity",
   455        "code": 10008
   456      }
   457    ]
   458  }`
   459  						})
   460  
   461  						It("returns a SecurityGroupAlreadyExists error", func() {
   462  							Expect(makeError).To(Equal(
   463  								ccerror.SecurityGroupAlreadyExists{
   464  									Message: "Security group with name 'sec-group' already exists.",
   465  								}),
   466  							)
   467  						})
   468  					})
   469  
   470  					When("the buildpack is invalid", func() {
   471  						BeforeEach(func() {
   472  							serverResponse = `
   473  {
   474    "errors": [
   475      {
   476        "code": 10008,
   477        "detail": "Buildpack must be an existing admin buildpack or a valid git URI",
   478        "title": "CF-UnprocessableEntity"
   479      }
   480    ]
   481  }`
   482  						})
   483  
   484  						It("returns an InvalidBuildpackError", func() {
   485  							Expect(makeError).To(MatchError(ccerror.InvalidBuildpackError{}))
   486  						})
   487  					})
   488  
   489  					When("the service instance name is taken", func() {
   490  						BeforeEach(func() {
   491  							serverResponse = `
   492  {
   493    "errors": [
   494      {
   495        "code": 10008,
   496        "detail": "The service instance name is taken",
   497        "title": "CF-UnprocessableEntity"
   498      }
   499    ]
   500  }`
   501  						})
   502  
   503  						It("returns an ServiceInstanceNameTakenError", func() {
   504  							Expect(makeError).To(MatchError(ccerror.ServiceInstanceNameTakenError{
   505  								Message: "The service instance name is taken",
   506  							}))
   507  						})
   508  					})
   509  
   510  					When("the buildpack is invalid", func() {
   511  						BeforeEach(func() {
   512  							serverResponse = `
   513  {
   514    "errors": [
   515      {
   516        "code": 10008,
   517        "detail": "Assign a droplet before starting this app.",
   518        "title": "CF-UnprocessableEntity"
   519      }
   520    ]
   521  }`
   522  						})
   523  
   524  						It("returns an InvalidStartError", func() {
   525  							Expect(makeError).To(MatchError(ccerror.InvalidStartError{}))
   526  						})
   527  					})
   528  
   529  					When("a route binding already exists", func() {
   530  						BeforeEach(func() {
   531  							serverResponse = `
   532  {
   533    "errors": [
   534      {
   535        "code": 130008,
   536        "detail": "The route and service instance are already bound.",
   537        "title": "CF-ServiceInstanceAlreadyBoundToSameRoute"
   538      }
   539    ]
   540  }`
   541  						})
   542  
   543  						It("returns an ResourceAlreadyExistsError", func() {
   544  							Expect(makeError).To(MatchError(ccerror.ResourceAlreadyExistsError{
   545  								Message: "The route and service instance are already bound.",
   546  							}))
   547  						})
   548  					})
   549  
   550  					When("the detail describes something else", func() {
   551  						BeforeEach(func() {
   552  							serverResponse = `
   553  {
   554    "errors": [
   555      {
   556        "code": 10008,
   557        "detail": "SomeCC Error Message",
   558        "title": "CF-UnprocessableEntity"
   559      }
   560    ]
   561  }`
   562  						})
   563  
   564  						It("returns a UnprocessableEntityError", func() {
   565  							Expect(makeError).To(MatchError(ccerror.UnprocessableEntityError{Message: "SomeCC Error Message"}))
   566  						})
   567  					})
   568  
   569  					When("a service app binding already exists", func() {
   570  						BeforeEach(func() {
   571  							serverResponse = `
   572  {
   573    "errors": [
   574      {
   575        "code": 10008,
   576        "detail": "The app is already bound to the service instance",
   577        "title": "CF-UnprocessableEntity"
   578      }
   579    ]
   580  }`
   581  						})
   582  
   583  						It("returns an ResourceAlreadyExistsError", func() {
   584  							Expect(makeError).To(MatchError(ccerror.ResourceAlreadyExistsError{
   585  								Message: "The app is already bound to the service instance",
   586  							}))
   587  						})
   588  					})
   589  
   590  					When("the service key name already exists", func() {
   591  						BeforeEach(func() {
   592  							serverResponse = `
   593  {
   594    "errors": [
   595      {
   596        "code": 10008,
   597        "detail": "The binding name is invalid. Key binding names must be unique. The service instance already has a key binding with name 'my-key'.",
   598        "title": "CF-UnprocessableEntity"
   599      }
   600    ]
   601  }`
   602  						})
   603  
   604  						It("returns an ServiceKeyTakenError", func() {
   605  							Expect(makeError).To(MatchError(ccerror.ServiceKeyTakenError{
   606  								Message: "The binding name is invalid. Key binding names must be unique. The service instance already has a key binding with name 'my-key'.",
   607  							}))
   608  						})
   609  					})
   610  				})
   611  			})
   612  
   613  			When("the error is a 5XX error", func() {
   614  				Context("(503) Service Unavailable", func() {
   615  					BeforeEach(func() {
   616  						serverResponseCode = http.StatusServiceUnavailable
   617  					})
   618  
   619  					It("returns a ServiceUnavailableError", func() {
   620  						Expect(makeError).To(MatchError(ccerror.ServiceUnavailableError{Message: "SomeCC Error Message"}))
   621  					})
   622  
   623  					When("the title is 'CF-TaskWorkersUnavailable'", func() {
   624  						BeforeEach(func() {
   625  							serverResponse = `{
   626    "errors": [
   627      {
   628        "code": 170020,
   629        "detail": "Task workers are unavailable: Failed to open TCP connection to nsync.service.cf.internal:8787 (getaddrinfo: Name or service not known)",
   630        "title": "CF-TaskWorkersUnavailable"
   631      }
   632    ]
   633  }`
   634  						})
   635  
   636  						It("returns a TaskWorkersUnavailableError", func() {
   637  							Expect(makeError).To(MatchError(ccerror.TaskWorkersUnavailableError{Message: "Task workers are unavailable: Failed to open TCP connection to nsync.service.cf.internal:8787 (getaddrinfo: Name or service not known)"}))
   638  						})
   639  					})
   640  				})
   641  
   642  				Context("all other 5XX", func() {
   643  					BeforeEach(func() {
   644  						serverResponseCode = http.StatusBadGateway
   645  						serverResponse = "I am some text"
   646  					})
   647  
   648  					It("returns a ServiceUnavailableError", func() {
   649  						Expect(makeError).To(MatchError(ccerror.V3UnexpectedResponseError{
   650  							ResponseCode: http.StatusBadGateway,
   651  							RequestIDs: []string{
   652  								"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95",
   653  								"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f",
   654  							},
   655  							V3ErrorResponse: ccerror.V3ErrorResponse{
   656  								Errors: []ccerror.V3Error{{
   657  									Detail: serverResponse,
   658  								}},
   659  							},
   660  						}))
   661  					})
   662  				})
   663  			})
   664  
   665  			Context("Unhandled Error Codes", func() {
   666  				BeforeEach(func() {
   667  					serverResponseCode = http.StatusTeapot
   668  				})
   669  
   670  				It("returns an UnexpectedResponseError", func() {
   671  					Expect(makeError).To(MatchError(ccerror.V3UnexpectedResponseError{
   672  						ResponseCode: http.StatusTeapot,
   673  						V3ErrorResponse: ccerror.V3ErrorResponse{
   674  							Errors: []ccerror.V3Error{
   675  								{
   676  									Code:   777,
   677  									Detail: "SomeCC Error Message",
   678  									Title:  "CF-SomeError",
   679  								},
   680  							},
   681  						},
   682  						RequestIDs: []string{
   683  							"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95",
   684  							"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f",
   685  						},
   686  					}))
   687  				})
   688  			})
   689  
   690  			Context("multiple errors", func() {
   691  				BeforeEach(func() {
   692  					serverResponseCode = http.StatusTeapot
   693  					serverResponse = `{
   694  							"errors": [
   695  								{
   696  									"code": 1000,
   697  									"detail": "Some CC Error Message",
   698  									"title": "CF-UnprocessableEntity"
   699  								},
   700  								{
   701  									"code": 1001,
   702  									"detail": "Some CC Error Message",
   703  									"title": "CF-UnprocessableEntity"
   704  								}
   705  							]
   706  						}`
   707  				})
   708  
   709  				It("returns a MultiError", func() {
   710  					Expect(makeError).To(MatchError(ccerror.MultiError{
   711  						ResponseCode: http.StatusTeapot,
   712  						Errors: []ccerror.V3Error{
   713  							{
   714  								Code:   1000,
   715  								Detail: "Some CC Error Message",
   716  								Title:  "CF-UnprocessableEntity",
   717  							},
   718  							{
   719  								Code:   1001,
   720  								Detail: "Some CC Error Message",
   721  								Title:  "CF-UnprocessableEntity",
   722  							},
   723  						},
   724  					}))
   725  				})
   726  			})
   727  		})
   728  	})
   729  })