github.com/swisscom/cloudfoundry-cli@v7.1.0+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 quota already exists", func() { 377 BeforeEach(func() { 378 serverResponse = ` 379 { 380 "errors": [ 381 { 382 "code": 10008, 383 "detail": "Organization Quota 'default' already exists.", 384 "title": "CF-UnprocessableEntity" 385 } 386 ] 387 }` 388 }) 389 390 It("returns a QuotaAlreadyExists error", func() { 391 Expect(makeError).To(Equal( 392 ccerror.QuotaAlreadyExists{ 393 Message: "Organization Quota 'default' already exists.", 394 }), 395 ) 396 }) 397 }) 398 399 When("the security group already exists", func() { 400 BeforeEach(func() { 401 serverResponse = ` 402 { 403 "errors": [ 404 { 405 "detail": "Security group with name 'sec-group' already exists.", 406 "title": "CF-UnprocessableEntity", 407 "code": 10008 408 } 409 ] 410 }` 411 }) 412 413 It("returns a SecurityGroupAlreadyExists error", func() { 414 Expect(makeError).To(Equal( 415 ccerror.SecurityGroupAlreadyExists{ 416 Message: "Security group with name 'sec-group' already exists.", 417 }), 418 ) 419 }) 420 }) 421 422 When("the buildpack is invalid", func() { 423 BeforeEach(func() { 424 serverResponse = ` 425 { 426 "errors": [ 427 { 428 "code": 10008, 429 "detail": "Buildpack must be an existing admin buildpack or a valid git URI", 430 "title": "CF-UnprocessableEntity" 431 } 432 ] 433 }` 434 }) 435 436 It("returns an InvalidBuildpackError", func() { 437 Expect(makeError).To(MatchError(ccerror.InvalidBuildpackError{})) 438 }) 439 }) 440 441 When("the buildpack is invalid", func() { 442 BeforeEach(func() { 443 serverResponse = ` 444 { 445 "errors": [ 446 { 447 "code": 10008, 448 "detail": "Assign a droplet before starting this app.", 449 "title": "CF-UnprocessableEntity" 450 } 451 ] 452 }` 453 }) 454 455 It("returns an InvalidStartError", func() { 456 Expect(makeError).To(MatchError(ccerror.InvalidStartError{})) 457 }) 458 }) 459 460 When("the detail describes something else", func() { 461 BeforeEach(func() { 462 serverResponse = ` 463 { 464 "errors": [ 465 { 466 "code": 10008, 467 "detail": "SomeCC Error Message", 468 "title": "CF-UnprocessableEntity" 469 } 470 ] 471 }` 472 }) 473 474 It("returns a UnprocessableEntityError", func() { 475 Expect(makeError).To(MatchError(ccerror.UnprocessableEntityError{Message: "SomeCC Error Message"})) 476 }) 477 }) 478 }) 479 }) 480 481 When("the error is a 5XX error", func() { 482 Context("(503) Service Unavailable", func() { 483 BeforeEach(func() { 484 serverResponseCode = http.StatusServiceUnavailable 485 }) 486 487 It("returns a ServiceUnavailableError", func() { 488 Expect(makeError).To(MatchError(ccerror.ServiceUnavailableError{Message: "SomeCC Error Message"})) 489 }) 490 491 When("the title is 'CF-TaskWorkersUnavailable'", func() { 492 BeforeEach(func() { 493 serverResponse = `{ 494 "errors": [ 495 { 496 "code": 170020, 497 "detail": "Task workers are unavailable: Failed to open TCP connection to nsync.service.cf.internal:8787 (getaddrinfo: Name or service not known)", 498 "title": "CF-TaskWorkersUnavailable" 499 } 500 ] 501 }` 502 }) 503 504 It("returns a TaskWorkersUnavailableError", func() { 505 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)"})) 506 }) 507 }) 508 }) 509 510 Context("all other 5XX", func() { 511 BeforeEach(func() { 512 serverResponseCode = http.StatusBadGateway 513 serverResponse = "I am some text" 514 }) 515 516 It("returns a ServiceUnavailableError", func() { 517 Expect(makeError).To(MatchError(ccerror.V3UnexpectedResponseError{ 518 ResponseCode: http.StatusBadGateway, 519 RequestIDs: []string{ 520 "6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95", 521 "6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f", 522 }, 523 V3ErrorResponse: ccerror.V3ErrorResponse{ 524 Errors: []ccerror.V3Error{{ 525 Detail: serverResponse, 526 }}, 527 }, 528 })) 529 }) 530 }) 531 }) 532 533 Context("Unhandled Error Codes", func() { 534 BeforeEach(func() { 535 serverResponseCode = http.StatusTeapot 536 }) 537 538 It("returns an UnexpectedResponseError", func() { 539 Expect(makeError).To(MatchError(ccerror.V3UnexpectedResponseError{ 540 ResponseCode: http.StatusTeapot, 541 V3ErrorResponse: ccerror.V3ErrorResponse{ 542 Errors: []ccerror.V3Error{ 543 { 544 Code: 777, 545 Detail: "SomeCC Error Message", 546 Title: "CF-SomeError", 547 }, 548 }, 549 }, 550 RequestIDs: []string{ 551 "6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95", 552 "6e0b4379-f5f7-4b2b-56b0-9ab7e96eed95::7445d9db-c31e-410d-8dc5-9f79ec3fc26f", 553 }, 554 })) 555 }) 556 }) 557 558 Context("multiple errors", func() { 559 BeforeEach(func() { 560 serverResponseCode = http.StatusTeapot 561 serverResponse = `{ 562 "errors": [ 563 { 564 "code": 1000, 565 "detail": "Some CC Error Message", 566 "title": "CF-UnprocessableEntity" 567 }, 568 { 569 "code": 1001, 570 "detail": "Some CC Error Message", 571 "title": "CF-UnprocessableEntity" 572 } 573 ] 574 }` 575 }) 576 577 It("returns a MultiError", func() { 578 Expect(makeError).To(MatchError(ccerror.MultiError{ 579 ResponseCode: http.StatusTeapot, 580 Errors: []ccerror.V3Error{ 581 { 582 Code: 1000, 583 Detail: "Some CC Error Message", 584 Title: "CF-UnprocessableEntity", 585 }, 586 { 587 Code: 1001, 588 Detail: "Some CC Error Message", 589 Title: "CF-UnprocessableEntity", 590 }, 591 }, 592 })) 593 }) 594 }) 595 }) 596 }) 597 })