1import { EnvironmentId, ProjectId } from "@t3tools/contracts";
2import { describe, expect, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as Option from "effect/Option";
5
6import { PrimaryConnectionTarget, type PreparedConnection } from "../connection/model.ts";
7import * as RpcHttp from "../rpc/http.ts";
8import {
9 fetchEnvironmentPullRequestDiff,
10 PullRequestDiffCredentialRejectedError,
11} from "./pullRequestDiffHttp.ts";
12
13const TARGET = new PrimaryConnectionTarget({
14 environmentId: EnvironmentId.make("environment-1"),
15 label: "Test environment",
16 httpBaseUrl: "https://environment.example.test/base",
17 wsBaseUrl: "wss://environment.example.test",
18});
19
20const PREPARED: PreparedConnection = {
21 environmentId: TARGET.environmentId,
22 label: TARGET.label,
23 httpBaseUrl: TARGET.httpBaseUrl,
24 socketUrl: "wss://environment.example.test/ws",
25 httpAuthorization: null,
26 target: TARGET,
27};
28
29describe("fetchEnvironmentPullRequestDiff", () => {
30 it.effect("posts the diff input to the prepared environment over authenticated HTTP", () =>
31 Effect.gen(function* () {
32 const calls: Array<readonly [RequestInfo | URL, RequestInit]> = [];
33 const fetchFn = ((request, init) => {
34 calls.push([request, init ?? {}]);
35 return Promise.resolve(
36 Response.json({
37 patch: "diff --git a/file.ts b/file.ts",
38 truncated: false,
39 nextCursor: null,
40 }),
41 );
42 }) satisfies typeof fetch;
43
44 const result = yield* fetchEnvironmentPullRequestDiff({
45 prepared: PREPARED,
46 signer: Option.none(),
47 diff: {
48 projectId: ProjectId.make("project-1"),
49 repository: "owner/repository",
50 number: 42,
51 cursor: "next-page",
52 },
53 }).pipe(Effect.provide(RpcHttp.layerRemoteHttpClient(fetchFn)));
54
55 expect(result).toEqual({
56 patch: "diff --git a/file.ts b/file.ts",
57 truncated: false,
58 nextCursor: null,
59 });
60 expect(calls).toHaveLength(1);
61 const [request, init] = calls[0]!;
62 expect(String(request)).toBe("https://environment.example.test/api/pull-requests/diff");
63 expect(init.method).toBe("POST");
64 expect(init.credentials).toBe("include");
65
66 const body =
67 typeof init.body === "string"
68 ? init.body
69 : init.body instanceof Uint8Array
70 ? new TextDecoder().decode(init.body)
71 : "";
72 // The assertion deliberately inspects the serialized wire body rather than decoding a
73 // domain value for use in application code.
74 expect(JSON.parse(body)).toEqual({
75 projectId: "project-1",
76 repository: "owner/repository",
77 number: 42,
78 cursor: "next-page",
79 });
80 }),
81 );
82
83 it.effect("gives rejected diff sessions a recovery action", () =>
84 Effect.gen(function* () {
85 const fetchFn = (() =>
86 Promise.resolve(
87 Response.json(
88 {
89 _tag: "EnvironmentAuthInvalidError",
90 code: "auth_invalid",
91 reason: "invalid_credential",
92 traceId: "trace-auth-test",
93 },
94 { status: 401 },
95 ),
96 )) satisfies typeof fetch;
97
98 const error = yield* fetchEnvironmentPullRequestDiff({
99 prepared: PREPARED,
100 signer: Option.none(),
101 diff: {
102 projectId: ProjectId.make("project-1"),
103 repository: "owner/repository",
104 number: 42,
105 },
106 }).pipe(Effect.provide(RpcHttp.layerRemoteHttpClient(fetchFn)), Effect.flip);
107
108 expect(error).toBeInstanceOf(PullRequestDiffCredentialRejectedError);
109 expect(error).toMatchObject({
110 repository: "owner/repository",
111 number: 42,
112 traceId: "trace-auth-test",
113 });
114 expect(error.message).toBe(
115 "This environment session is no longer valid (invalid_credential). Refresh the page or quit and reopen The Knife.",
116 );
117 }),
118 );
119});