| ... | @@ -6,8 +6,14 @@ import * as Effect from "effect/Effect"; | ... | @@ -6,8 +6,14 @@ import * as Effect from "effect/Effect"; |
| 6 | import * as FileSystem from "effect/FileSystem"; | 6 | import * as FileSystem from "effect/FileSystem"; |
| 7 | import * as Layer from "effect/Layer"; | 7 | import * as Layer from "effect/Layer"; |
| 8 | import * as Option from "effect/Option"; | 8 | import * as Option from "effect/Option"; |
| 9 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; | 9 | import { |
| 10 | | 10 | HttpClient, |
| | 11 | HttpClientError, |
| | 12 | HttpClientRequest, |
| | 13 | HttpClientResponse, |
| | 14 | } from "effect/unstable/http"; |
| | 15 | |
| | 16 | import { GitCommandError } from "@t3tools/contracts"; |
| 11 | import * as BitbucketApi from "./BitbucketApi.ts"; | 17 | import * as BitbucketApi from "./BitbucketApi.ts"; |
| 12 | import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; | 18 | import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; |
| 13 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; | 19 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| ... | @@ -53,10 +59,15 @@ const repositoryJson = { | ... | @@ -53,10 +59,15 @@ const repositoryJson = { |
| 53 | | 59 | |
| 54 | function makeLayer(input: { | 60 | function makeLayer(input: { |
| 55 | readonly response: (request: HttpClientRequest.HttpClientRequest) => Response; | 61 | readonly response: (request: HttpClientRequest.HttpClientRequest) => Response; |
| | 62 | readonly requestFailure?: ( |
| | 63 | request: HttpClientRequest.HttpClientRequest, |
| | 64 | ) => HttpClientError.HttpClientError; |
| 56 | readonly git?: Partial<GitVcsDriver.GitVcsDriver["Service"]>; | 65 | readonly git?: Partial<GitVcsDriver.GitVcsDriver["Service"]>; |
| 57 | }) { | 66 | }) { |
| 58 | const execute = vi.fn((request: HttpClientRequest.HttpClientRequest) => | 67 | const execute = vi.fn((request: HttpClientRequest.HttpClientRequest) => |
| 59 | Effect.succeed(HttpClientResponse.fromWeb(request, input.response(request))), | 68 | input.requestFailure |
| | 69 | ? Effect.fail(input.requestFailure(request)) |
| | 70 | : Effect.succeed(HttpClientResponse.fromWeb(request, input.response(request))), |
| 60 | ); | 71 | ); |
| 61 | const gitMock = { | 72 | const gitMock = { |
| 62 | readConfigValue: vi.fn<GitVcsDriver.GitVcsDriver["Service"]["readConfigValue"]>(() => | 73 | readConfigValue: vi.fn<GitVcsDriver.GitVcsDriver["Service"]["readConfigValue"]>(() => |
| ... | @@ -497,6 +508,42 @@ it.effect("reports auth status through the Bitbucket REST /user endpoint", () => | ... | @@ -497,6 +508,42 @@ it.effect("reports auth status through the Bitbucket REST /user endpoint", () => |
| 497 | }).pipe(Effect.provide(layer)); | 508 | }).pipe(Effect.provide(layer)); |
| 498 | }); | 509 | }); |
| 499 | | 510 | |
| | 511 | it.effect("preserves the HTTP client failure without deriving the domain message from it", () => { |
| | 512 | const transportCause = new Error("socket reset by peer"); |
| | 513 | let requestFailure: HttpClientError.HttpClientError | undefined; |
| | 514 | const { layer } = makeLayer({ |
| | 515 | response: () => Response.json({}), |
| | 516 | requestFailure: (request) => { |
| | 517 | requestFailure = new HttpClientError.HttpClientError({ |
| | 518 | reason: new HttpClientError.TransportError({ |
| | 519 | request, |
| | 520 | cause: transportCause, |
| | 521 | }), |
| | 522 | }); |
| | 523 | return requestFailure; |
| | 524 | }, |
| | 525 | }); |
| | 526 | |
| | 527 | return Effect.gen(function* () { |
| | 528 | const bitbucket = yield* BitbucketApi.BitbucketApi; |
| | 529 | const error = yield* Effect.flip( |
| | 530 | bitbucket.getPullRequest({ |
| | 531 | cwd: "/repo", |
| | 532 | reference: "42", |
| | 533 | }), |
| | 534 | ); |
| | 535 | |
| | 536 | assert.strictEqual(error.operation, "getPullRequest"); |
| | 537 | assert.strictEqual(error.detail, "Failed to send the Bitbucket request."); |
| | 538 | assert.strictEqual( |
| | 539 | error.message, |
| | 540 | "Bitbucket API failed in getPullRequest: Failed to send the Bitbucket request.", |
| | 541 | ); |
| | 542 | assert.strictEqual(error.cause, requestFailure); |
| | 543 | assert.strictEqual(requestFailure?.cause, transportCause); |
| | 544 | }).pipe(Effect.provide(layer)); |
| | 545 | }); |
| | 546 | |
| 500 | it.effect("checks out same-repository pull requests with the existing Bitbucket remote", () => { | 547 | it.effect("checks out same-repository pull requests with the existing Bitbucket remote", () => { |
| 501 | const { git, layer } = makeLayer({ | 548 | const { git, layer } = makeLayer({ |
| 502 | response: () => | 549 | response: () => |
| ... | @@ -549,6 +596,50 @@ it.effect("checks out same-repository pull requests with the existing Bitbucket | ... | @@ -549,6 +596,50 @@ it.effect("checks out same-repository pull requests with the existing Bitbucket |
| 549 | }).pipe(Effect.provide(layer)); | 596 | }).pipe(Effect.provide(layer)); |
| 550 | }); | 597 | }); |
| 551 | | 598 | |
| | 599 | it.effect("preserves Git checkout failures without deriving the domain message from them", () => { |
| | 600 | const gitCause = new GitCommandError({ |
| | 601 | operation: "fetchRemoteBranch", |
| | 602 | command: "git fetch origin feature/source-control", |
| | 603 | cwd: "/repo", |
| | 604 | detail: "remote rejected the request", |
| | 605 | }); |
| | 606 | const { layer } = makeLayer({ |
| | 607 | response: () => |
| | 608 | Response.json({ |
| | 609 | ...bitbucketPullRequest, |
| | 610 | source: { |
| | 611 | branch: { name: "feature/source-control" }, |
| | 612 | repository: { |
| | 613 | full_name: "pingdotgg/t3code", |
| | 614 | workspace: { slug: "pingdotgg" }, |
| | 615 | }, |
| | 616 | }, |
| | 617 | }), |
| | 618 | git: { |
| | 619 | fetchRemoteBranch: () => Effect.fail(gitCause), |
| | 620 | }, |
| | 621 | }); |
| | 622 | |
| | 623 | return Effect.gen(function* () { |
| | 624 | const bitbucket = yield* BitbucketApi.BitbucketApi; |
| | 625 | const error = yield* Effect.flip( |
| | 626 | bitbucket.checkoutPullRequest({ |
| | 627 | cwd: "/repo", |
| | 628 | reference: "42", |
| | 629 | force: true, |
| | 630 | }), |
| | 631 | ); |
| | 632 | |
| | 633 | assert.strictEqual(error.operation, "checkoutPullRequest"); |
| | 634 | assert.strictEqual(error.detail, "Failed to check out the Bitbucket pull request."); |
| | 635 | assert.strictEqual( |
| | 636 | error.message, |
| | 637 | "Bitbucket API failed in checkoutPullRequest: Failed to check out the Bitbucket pull request.", |
| | 638 | ); |
| | 639 | assert.strictEqual(error.cause, gitCause); |
| | 640 | }).pipe(Effect.provide(layer)); |
| | 641 | }); |
| | 642 | |
| 552 | it.effect("checks out fork pull requests through an ensured fork remote", () => { | 643 | it.effect("checks out fork pull requests through an ensured fork remote", () => { |
| 553 | const { git, layer } = makeLayer({ | 644 | const { git, layer } = makeLayer({ |
| 554 | response: (request) => { | 645 | response: (request) => { |