Open History to see the last commit
1import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { GitCommandError } from "@t3tools/contracts";
4import { assert, it } from "@effect/vitest";
5import * as Context from "effect/Context";
6import * as Effect from "effect/Effect";
7import * as FileSystem from "effect/FileSystem";
8import * as Layer from "effect/Layer";
9import * as Queue from "effect/Queue";
10import { ChildProcessSpawner } from "effect/process";
11
12import packageJson from "../../package.json" with { type: "json" };
13import * as ServerConfig from "../config.ts";
14import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
15import * as VcsProcess from "../vcs/VcsProcess.ts";
16import * as TriageService from "./TriageService.ts";
17import { pickSourceRef } from "./TriageService.ts";
18
19const MAIN = "a1dc856ee61278d0199c36d4bcdef5fdea4d21fe\trefs/heads/main";
20
21it("clones the server's release tag, peeling annotated tags to their commit", () => {
22 assert.deepEqual(pickSourceRef(`${MAIN}\n7027b1f\trefs/tags/v0.1.1\n`, "0.1.1"), {
23 ref: "v0.1.1",
24 commit: "7027b1f",
25 });
26 assert.deepEqual(
27 pickSourceRef(`${MAIN}\n1111111\trefs/tags/v0.1.1\n2222222\trefs/tags/v0.1.1^{}\n`, "0.1.1"),
28 { ref: "v0.1.1", commit: "2222222" },
29 );
30});
31
32it("falls back to main when the server's version has no tag", () => {
33 assert.deepEqual(pickSourceRef(`${MAIN}\n`, "0.1.2-dev"), {
34 ref: "main",
35 commit: "a1dc856ee61278d0199c36d4bcdef5fdea4d21fe",
36 });
37 assert.isUndefined(pickSourceRef("", "0.1.1"));
38});
39
40const TestLayer = GitVcsDriver.layer.pipe(
41 Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-triage-service-" })),
42 Layer.provideMerge(VcsProcess.layer),
43 Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
44);
45
46const RELEASE_COMMIT = "7027b1f";
47
48/** The service over a stub git that answers each command with `answer`. */
49const makeTriage = (
50 answer: (command: string) => Effect.Effect<string, GitCommandError> = (command) =>
51 Effect.succeed(
52 command === "ls-remote"
53 ? `${RELEASE_COMMIT}\trefs/tags/v${packageJson.version}\n`
54 : command === "rev-parse"
55 ? `${RELEASE_COMMIT}\n`
56 : "",
57 ),
58) =>
59 Effect.gen(function* () {
60 const git = yield* GitVcsDriver.GitVcsDriver;
61 const commands: string[] = [];
62 const triage = yield* Layer.build(TriageService.layer).pipe(
63 Effect.map(Context.get(TriageService.TriageService)),
64 Effect.provideService(GitVcsDriver.GitVcsDriver, {
65 ...git,
66 execute: (input) => {
67 const command = input.args[0] ?? "";
68 commands.push(command);
69 return answer(command).pipe(
70 Effect.map((stdout) => ({
71 exitCode: ChildProcessSpawner.ExitCode(0),
72 stdout,
73 stderr: "",
74 stdoutTruncated: false,
75 stderrTruncated: false,
76 })),
77 );
78 },
79 }),
80 );
81 return { triage, commands };
82 });
83
84const readContext = Effect.gen(function* () {
85 const fs = yield* FileSystem.FileSystem;
86 const config = yield* ServerConfig.ServerConfig;
87 const sessions = `${config.stateDir}/triage`;
88 const [session] = yield* fs.readDirectory(sessions);
89 return yield* fs.readFileString(`${sessions}/${session}/context.md`);
90});
91
92it.effect("sessions started during a clone share it, and a release tag is looked up once", () =>
93 Effect.gen(function* () {
94 const fetches = yield* Queue.unbounded<void>();
95 const { triage, commands } = yield* makeTriage((command) =>
96 command === "fetch"
97 ? Queue.offer(fetches, undefined).pipe(Effect.andThen(Effect.never))
98 : Effect.succeed(
99 command === "ls-remote" ? `${RELEASE_COMMIT}\trefs/tags/v${packageJson.version}\n` : "",
100 ),
101 );
102
103 yield* triage.prepare({ client: {} });
104 yield* triage.prepare({ client: {} });
105 yield* Queue.take(fetches);
106
107 assert.deepEqual(
108 commands.filter((command) => command === "ls-remote" || command === "init"),
109 ["ls-remote", "init"],
110 );
111 }).pipe(Effect.provide(TestLayer)),
112);
113
114it.effect("reuses a finished clone without fetching", () =>
115 Effect.gen(function* () {
116 const fs = yield* FileSystem.FileSystem;
117 const config = yield* ServerConfig.ServerConfig;
118 yield* fs.makeDirectory(`${config.baseDir}/source/${RELEASE_COMMIT}/.git`, {
119 recursive: true,
120 });
121 const { triage, commands } = yield* makeTriage();
122
123 yield* triage.prepare({ client: {} });
124
125 assert.deepEqual(commands, ["ls-remote", "rev-parse"]);
126 assert.include(yield* readContext, "The server cloned the source for its own version");
127 }).pipe(Effect.provide(TestLayer)),
128);
129
130it.effect("still starts triage when the source lookup fails", () =>
131 Effect.gen(function* () {
132 const { triage } = yield* makeTriage(() =>
133 Effect.fail(
134 new GitCommandError({
135 operation: "TriageService.resolveSourceRef",
136 command: "git ls-remote",
137 cwd: "/",
138 detail: "Could not resolve host: github.com",
139 }),
140 ),
141 );
142
143 const { prompt } = yield* triage.prepare({ client: {} });
144
145 assert.include(prompt, "triage-playbook.md");
146 assert.include(yield* readContext, "Could not resolve host: github.com");
147 }).pipe(Effect.provide(TestLayer)),
148);
149
150it.effect("does not trust a folder without its own .git, even inside a repo at that commit", () =>
151 Effect.gen(function* () {
152 const fs = yield* FileSystem.FileSystem;
153 const config = yield* ServerConfig.ServerConfig;
154 yield* fs.makeDirectory(`${config.baseDir}/source/${RELEASE_COMMIT}`, { recursive: true });
155 // The stub answers rev-parse with the commit, as a parent repository could.
156 const { triage, commands } = yield* makeTriage();
157
158 yield* triage.prepare({ client: {} });
159
160 assert.notInclude(commands, "rev-parse");
161 assert.include(yield* readContext, "is not a complete checkout");
162 }).pipe(Effect.provide(TestLayer)),
163);