github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/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  				Context("(401) Unauthorized", func() {
   112  					BeforeEach(func() {
   113  						serverResponseCode = http.StatusUnauthorized
   114  					})
   115  
   116  					Context("generic 401", func() {
   117  						It("returns a UnauthorizedError", func() {
   118  							Expect(makeError).To(MatchError(ccerror.UnauthorizedError{Message: "SomeCC Error Message"}))
   119  						})
   120  					})
   121  
   122  					Context("invalid token", func() {
   123  						BeforeEach(func() {
   124  							serverResponse = `{
   125  							"errors": [
   126  								{
   127  									"code": 1000,
   128  									"detail": "Invalid Auth Token",
   129  									"title": "CF-InvalidAuthToken"
   130  								}
   131  							]
   132  						}`
   133  						})
   134  
   135  						It("returns an InvalidAuthTokenError", func() {
   136  							Expect(makeError).To(MatchError(ccerror.InvalidAuthTokenError{Message: "Invalid Auth Token"}))
   137  						})
   138  					})
   139  				})
   140  
   141  				Context("(403) Forbidden", func() {
   142  					BeforeEach(func() {
   143  						serverResponseCode = http.StatusForbidden
   144  					})
   145  
   146  					It("returns a ForbiddenError", func() {
   147  						Expect(makeError).To(MatchError(ccerror.ForbiddenError{Message: "SomeCC Error Message"}))
   148  					})
   149  				})
   150  
   151  				Context("(404) Not Found", func() {
   152  					BeforeEach(func() {
   153  						serverResponseCode = http.StatusNotFound
   154  					})
   155  
   156  					Context("API is not found", func() {
   157  
   158  						BeforeEach(func() {
   159  							serverResponse = `{
   160  								"errors": [
   161  									{
   162  										"detail": "Unknown request",
   163  										"title": "CF-NotFound",
   164  										"code": 10000
   165  									}
   166  								]
   167  							}`
   168  						})
   169  
   170  						It("returns a APINotFoundError", func() {
   171  							Expect(makeError).To(MatchError(ccerror.APINotFoundError{URL: server.URL() + "/v3/apps"}))
   172  						})
   173  					})
   174  
   175  					When("a process is not found", func() {
   176  						BeforeEach(func() {
   177  							serverResponse = `
   178  {
   179    "errors": [
   180      {
   181        "code": 10010,
   182        "detail": "Process not found",
   183        "title": "CF-ResourceNotFound"
   184      }
   185    ]
   186  }`
   187  						})
   188  
   189  						It("returns a ProcessNotFoundError", func() {
   190  							Expect(makeError).To(MatchError(ccerror.ProcessNotFoundError{}))
   191  						})
   192  					})
   193  
   194  					When("an instance is not found", func() {
   195  						BeforeEach(func() {
   196  							serverResponse = `
   197  {
   198    "errors": [
   199      {
   200        "code": 10010,
   201        "detail": "Instance not found",
   202        "title": "CF-ResourceNotFound"
   203      }
   204    ]
   205  }`
   206  						})
   207  
   208  						It("returns an InstanceNotFoundError", func() {
   209  							Expect(makeError).To(MatchError(ccerror.InstanceNotFoundError{}))
   210  						})
   211  					})
   212  
   213  					When("an application is not found", func() {
   214  						BeforeEach(func() {
   215  							serverResponse = `
   216  {
   217    "errors": [
   218      {
   219        "code": 10010,
   220        "detail": "App not found",
   221        "title": "CF-ResourceNotFound"
   222      }
   223    ]
   224  }`
   225  						})
   226  
   227  						It("returns an AppNotFoundError", func() {
   228  							Expect(makeError).To(MatchError(ccerror.ApplicationNotFoundError{}))
   229  						})
   230  					})
   231  
   232  					When("a droplet is not found", func() {
   233  						BeforeEach(func() {
   234  							serverResponse = `
   235  {
   236    "errors": [
   237      {
   238        "code": 10010,
   239        "detail": "Droplet not found",
   240        "title": "CF-ResourceNotFound"
   241      }
   242    ]
   243  }`
   244  						})
   245  
   246  						It("returns a DropletNotFoundError", func() {
   247  							Expect(makeError).To(MatchError(ccerror.DropletNotFoundError{}))
   248  						})
   249  					})
   250  
   251  					When("a user is not found", func() {
   252  						BeforeEach(func() {
   253  							serverResponse = `
   254  							{
   255  							  "errors": [
   256  							    {
   257  							      "code": 10010,
   258  							      "detail": "User not found",
   259  							      "title": "CF-ResourceNotFound"
   260  							    }
   261  							  ]
   262  							}`
   263  						})
   264  
   265  						It("returns a UserNotFoundError", func() {
   266  							Expect(makeError).To(MatchError(ccerror.UserNotFoundError{}))
   267  						})
   268  					})
   269  
   270  					Context("generic not found", func() {
   271  						It("returns a ResourceNotFoundError", func() {
   272  							Expect(makeError).To(MatchError(ccerror.ResourceNotFoundError{Message: "SomeCC Error Message"}))
   273  						})
   274  					})
   275  				})
   276  
   277  				Context("(422) Unprocessable Entity", func() {
   278  					BeforeEach(func() {
   279  						serverResponseCode = http.StatusUnprocessableEntity
   280  					})
   281  
   282  					When("the name isn't unique to space (old error message)", func() {
   283  						BeforeEach(func() {
   284  							serverResponse = `
   285  {
   286    "errors": [
   287      {
   288        "code": 10008,
   289        "detail": "name must be unique in space",
   290        "title": "CF-UnprocessableEntity"
   291      }
   292    ]
   293  }`
   294  						})
   295  
   296  						It("returns a NameNotUniqueInSpaceError", func() {
   297  							Expect(makeError).To(Equal(
   298  								ccerror.NameNotUniqueInSpaceError{
   299  									UnprocessableEntityError: ccerror.UnprocessableEntityError{
   300  										Message: "name must be unique in space",
   301  									},
   302  								},
   303  							))
   304  						})
   305  					})
   306  
   307  					When("the name isn't unique to space (new error message)", func() {
   308  						BeforeEach(func() {
   309  							serverResponse = `
   310  {
   311    "errors": [
   312      {
   313        "code": 10008,
   314        "detail": "App with the name 'eli' already exists.",
   315        "title": "CF-UnprocessableEntity"
   316      }
   317    ]
   318  }`
   319  						})
   320  
   321  						It("returns a NameNotUniqueInSpaceError", func() {
   322  							Expect(makeError).To(Equal(
   323  								ccerror.NameNotUniqueInSpaceError{
   324  									UnprocessableEntityError: ccerror.UnprocessableEntityError{
   325  										Message: "App with the name 'eli' already exists.",
   326  									},
   327  								},
   328  							))
   329  						})
   330  					})
   331  
   332  					When("the name isn't unique to organization", func() {
   333  						BeforeEach(func() {
   334  							serverResponse = `
   335  {
   336    "errors": [
   337      {
   338        "code": 10008,
   339        "detail": "Name must be unique per organization",
   340        "title": "CF-UnprocessableEntity"
   341      }
   342    ]
   343  }`
   344  						})
   345  
   346  						It("returns a NameNotUniqueInOrgError", func() {
   347  							Expect(makeError).To(MatchError(ccerror.NameNotUniqueInOrgError{}))
   348  						})
   349  					})
   350  
   351  					When("the role already exists", func() {
   352  						BeforeEach(func() {
   353  							serverResponse = `
   354  {
   355    "errors": [
   356      {
   357        "code": 10008,
   358        "detail": "User 'wow' already has 'organization_auditor' role in organization 'wow'.",
   359        "title": "CF-UnprocessableEntity"
   360      }
   361    ]
   362  }`
   363  						})
   364  
   365  						It("returns a RoleAlreadyExistsError", func() {
   366  							Expect(makeError).To(Equal(
   367  								ccerror.RoleAlreadyExistsError{
   368  									UnprocessableEntityError: ccerror.UnprocessableEntityError{
   369  										Message: "User 'wow' already has 'organization_auditor' role in organization 'wow'.",
   370  									},
   371  								}),
   372  							)
   373  						})
   374  					})
   375  
   376  					When("the buildpack is invalid", func() {
   377  						BeforeEach(func() {
   378  							serverResponse = `
   379  {
   380    "errors": [
   381      {
   382        "code": 10008,
   383        "detail": "Buildpack must be an existing admin buildpack or a valid git URI",
   384        "title": "CF-UnprocessableEntity"
   385      }
   386    ]
   387  }`
   388  						})
   389  
   390  						It("returns an InvalidBuildpackError", func() {
   391  							Expect(makeError).To(MatchError(ccerror.InvalidBuildpackError{}))
   392  						})
   393  					})
   394  
   395  					When("the buildpack is invalid", func() {
   396  						BeforeEach(func() {
   397  							serverResponse = `
   398  {
   399    "errors": [
   400      {
   401        "code": 10008,
   402        "detail": "Assign a droplet before starting this app.",
   403        "title": "CF-UnprocessableEntity"
   404      }
   405    ]
   406  }`
   407  						})
   408  
   409  						It("returns an InvalidStartError", func() {
   410  							Expect(makeError).To(MatchError(ccerror.InvalidStartError{}))
   411  						})
   412  					})
   413  
   414  					When("the detail describes something else", func() {
   415  						BeforeEach(func() {
   416  							serverResponse = `
   417  {
   418    "errors": [
   419      {
   420        "code": 10008,
   421        "detail": "SomeCC Error Message",
   422        "title": "CF-UnprocessableEntity"
   423      }
   424    ]
   425  }`
   426  						})
   427  
   428  						It("returns a UnprocessableEntityError", func() {
   429  							Expect(makeError).To(MatchError(ccerror.UnprocessableEntityError{Message: "SomeCC Error Message"}))
   430  						})
   431  					})
   432  				})
   433  			})
   434  
   435  			When("the error is a 5XX error", func() {
   436  				Context("(503) Service Unavailable", func() {
   437  					BeforeEach(func() {
   438  						serverResponseCode = http.StatusServiceUnavailable
   439  					})
   440  
   441  					It("returns a ServiceUnavailableError", func() {
   442  						Expect(makeError).To(MatchError(ccerror.ServiceUnavailableError{Message: "SomeCC Error Message"}))
   443  					})
   444  
   445  					When("the title is 'CF-TaskWorkersUnavailable'", func() {
   446  						BeforeEach(func() {
   447  							serverResponse = `{
   448    "errors": [
   449      {
   450        "code": 170020,
   451        "detail": "Task workers are unavailable: Failed to open TCP connection to nsync.service.cf.internal:8787 (getaddrinfo: Name or service not known)",
   452        "title": "CF-TaskWorkersUnavailable"
   453      }
   454    ]
   455  }`
   456  						})
   457  
   458  						It("returns a TaskWorkersUnavailableError", func() {
   459  							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)"}))
   460  						})
   461  					})
   462  				})
   463  
   464  				Context("all other 5XX", func() {
   465  					BeforeEach(func() {
   466  						serverResponseCode = http.StatusBadGateway
   467  						serverResponse = "I am some text"
   468  					})
   469  
   470  					It("returns a ServiceUnavailableError", func() {
   471  						Expect(makeError).To(MatchError(ccerror.V3UnexpectedResponseError{
   472  							ResponseCode: http.StatusBadGateway,
   473  							RequestIDs: []string{
   474  								"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95",
   475  								"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f",
   476  							},
   477  							V3ErrorResponse: ccerror.V3ErrorResponse{
   478  								Errors: []ccerror.V3Error{{
   479  									Detail: serverResponse,
   480  								}},
   481  							},
   482  						}))
   483  					})
   484  				})
   485  			})
   486  
   487  			Context("Unhandled Error Codes", func() {
   488  				BeforeEach(func() {
   489  					serverResponseCode = http.StatusTeapot
   490  				})
   491  
   492  				It("returns an UnexpectedResponseError", func() {
   493  					Expect(makeError).To(MatchError(ccerror.V3UnexpectedResponseError{
   494  						ResponseCode: http.StatusTeapot,
   495  						V3ErrorResponse: ccerror.V3ErrorResponse{
   496  							Errors: []ccerror.V3Error{
   497  								{
   498  									Code:   777,
   499  									Detail: "SomeCC Error Message",
   500  									Title:  "CF-SomeError",
   501  								},
   502  							},
   503  						},
   504  						RequestIDs: []string{
   505  							"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95",
   506  							"6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f",
   507  						},
   508  					}))
   509  				})
   510  			})
   511  
   512  			Context("multiple errors", func() {
   513  				BeforeEach(func() {
   514  					serverResponseCode = http.StatusTeapot
   515  					serverResponse = `{
   516  							"errors": [
   517  								{
   518  									"code": 1000,
   519  									"detail": "Some CC Error Message",
   520  									"title": "CF-UnprocessableEntity"
   521  								},
   522  								{
   523  									"code": 1001,
   524  									"detail": "Some CC Error Message",
   525  									"title": "CF-UnprocessableEntity"
   526  								}
   527  							]
   528  						}`
   529  				})
   530  
   531  				It("returns a MultiError", func() {
   532  					Expect(makeError).To(MatchError(ccerror.MultiError{
   533  						ResponseCode: http.StatusTeapot,
   534  						Errors: []ccerror.V3Error{
   535  							{
   536  								Code:   1000,
   537  								Detail: "Some CC Error Message",
   538  								Title:  "CF-UnprocessableEntity",
   539  							},
   540  							{
   541  								Code:   1001,
   542  								Detail: "Some CC Error Message",
   543  								Title:  "CF-UnprocessableEntity",
   544  							},
   545  						},
   546  					}))
   547  				})
   548  			})
   549  		})
   550  	})
   551  })