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 })