Skip to content
Source
Sign inSign up

ReviewService.test.ts

262 lines11.5 KB

1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as SqlClient from "effect/sql/SqlClient";
7import { ProjectId } from "@t3tools/contracts";
8​
9import * as ServerConfig from "../config.ts";
10import * as ProjectStore from "../orchestration-v2/ProjectStore.ts";
11import * as SqlitePersistence from "../persistence/Sqlite.ts";
12import * as ServerSettings from "../serverSettings.ts";
13import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
14import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
15import * as KnownWorkspaces from "../workspace/KnownWorkspaces.ts";
16import * as ReviewService from "./ReviewService.ts";
17​
18function layer(input: {
19 readonly workspaceRoot: string;
20 readonly baseDir: string;
21 readonly detectCalls?: Array<{ readonly cwd: string }>;
22 readonly worktreesDirectory?: string;
23 readonly previousWorktreesDirectories?: ReadonlyArray<string>;
24 readonly projectRoots?: ReadonlyArray<string> | "unavailable";
25 readonly threadWorktrees?: ReadonlyArray<{ worktreePath: string; deleted?: boolean }>;
26 readonly gitWorktrees?: ReadonlyArray<string>;
27}) {
28 const projectRoots = input.projectRoots ?? [];
29 return ReviewService.layer.pipe(
30 Layer.provide(KnownWorkspaces.layer),
31 Layer.provide(
32 Layer.effectDiscard(
33 Effect.gen(function* () {
34 const sql = yield* SqlClient.SqlClient;
35 for (const [index, thread] of (input.threadWorktrees ?? []).entries()) {
36 yield* sql`
37 INSERT INTO orchestration_v2_projection_threads (
38 thread_id, project_id, title, default_provider, runtime_mode, interaction_mode,
39 created_at, updated_at, deleted_at, payload_json
40 ) VALUES (
41 ${`thread-${index}`}, 'project-0', 'Thread', 'codex', 'full-access', 'default',
42 '2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z',
43 ${thread.deleted ? "2026-01-02T00:00:00.000Z" : null},
44 ${JSON.stringify({ worktreePath: thread.worktreePath })}
45 )
46 `;
47 }
48 }),
49 ).pipe(Layer.provideMerge(SqlitePersistence.layerMemory)),
50 ),
51 Layer.provide(
52 Layer.mock(ProjectStore.ProjectStoreV2)({
53 listShells: () =>
54 projectRoots === "unavailable"
55 ? Effect.fail(
56 new ProjectStore.ProjectStoreV2Error({ operation: "list", cause: "offline" }),
57 )
58 : Effect.succeed(
59 projectRoots.map((workspaceRoot, index) => ({
60 id: ProjectId.make(`project-${index}`),
61 title: `Project ${index}`,
62 workspaceRoot,
63 defaultModelSelection: null,
64 scripts: [],
65 createdAt: "2026-01-01T00:00:00.000Z",
66 updatedAt: "2026-01-01T00:00:00.000Z",
67 })),
68 ),
69 }),
70 ),
71 Layer.provide(
72 Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({
73 get: () => Effect.die("unexpected VCS registry get"),
74 resolve: () => Effect.die("unexpected VCS registry resolve"),
75 detect: (request) =>
76 Effect.sync(() => {
77 input.detectCalls?.push({ cwd: request.cwd });
78 return null;
79 }),
80 }),
81 ),
82 Layer.provide(
83 Layer.mock(GitVcsDriver.GitVcsDriver)({
84 listWorktreePaths: () => Effect.succeed([...(input.gitWorktrees ?? [])]),
85 }),
86 ),
87 Layer.provide(
88 ServerSettings.ServerSettingsService.layerTest({
89 worktreesDirectory: input.worktreesDirectory ?? "",
90 previousWorktreesDirectories: [...(input.previousWorktreesDirectories ?? [])],
91 }),
92 ),
93 Layer.provide(ServerConfig.layerTest(input.workspaceRoot, input.baseDir)),
94 Layer.provideMerge(NodeServices.layer),
95 );
97​
98describe("ReviewService", () => {
99 it.effect("rejects diff preview cwd outside the configured workspace roots", () =>
100 Effect.gen(function* () {
101 const fs = yield* FileSystem.FileSystem;
102 const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" });
103 const outsideRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-outside-" });
104 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" });
105 const detectCalls: Array<{ readonly cwd: string }> = [];
106​
107 const error = yield* Effect.gen(function* () {
108 const review = yield* ReviewService.ReviewService;
109 return yield* review.getDiffPreview({ cwd: outsideRoot }).pipe(Effect.flip);
110 }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, detectCalls })));
111​
112 assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
113 assert.strictEqual(error.operation, "ReviewService.getDiffPreview");
114 assert.match(
115 "detail" in error ? error.detail : "",
116 /must stay within the configured workspace root/,
117 );
118 assert.deepStrictEqual(detectCalls, []);
119 }).pipe(Effect.provide(NodeServices.layer)),
120 );
121​
122 it.effect("allows previous custom worktree locations but never a filesystem root", () =>
123 Effect.gen(function* () {
124 const fs = yield* FileSystem.FileSystem;
125 const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" });
126 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" });
127 const previous = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-old-worktrees-" });
128 const outsideRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-outside-" });
129​
130 const result = yield* Effect.gen(function* () {
131 const review = yield* ReviewService.ReviewService;
132 return yield* review.getDiffPreview({ cwd: previous });
133 }).pipe(
134 Effect.provide(
135 layer({
136 workspaceRoot,
137 baseDir,
138 worktreesDirectory: "/",
139 previousWorktreesDirectories: [previous],
140 }),
141 ),
142 );
143 assert.strictEqual(result.cwd, previous);
144​
145 const rootLink = `${baseDir}/root-link`;
146 yield* fs.symlink("/", rootLink);
147 for (const worktreesDirectory of ["/", rootLink]) {
148 const error = yield* Effect.gen(function* () {
149 const review = yield* ReviewService.ReviewService;
150 return yield* review.getDiffPreview({ cwd: outsideRoot }).pipe(Effect.flip);
151 }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, worktreesDirectory })));
152 assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
153 }
154 }).pipe(Effect.provide(NodeServices.layer)),
155 );
156​
157 it.effect("allows registered project roots outside the server cwd", () =>
158 Effect.gen(function* () {
159 const fs = yield* FileSystem.FileSystem;
160 const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" });
161 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" });
162 const projectRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-project-" });
163 const projectChild = `${projectRoot}/packages/app`;
164 yield* fs.makeDirectory(projectChild, { recursive: true });
165 const sibling = `${projectRoot}-sibling`;
166 yield* fs.makeDirectory(sibling);
167 yield* Effect.addFinalizer(() => fs.remove(sibling, { recursive: true }).pipe(Effect.ignore));
168​
169 const review = (cwd: string, projectRoots: ReadonlyArray<string> | "unavailable") =>
170 Effect.gen(function* () {
171 const service = yield* ReviewService.ReviewService;
172 return yield* service.getDiffPreview({ cwd });
173 }).pipe(Effect.provide(layer({ workspaceRoot, baseDir, projectRoots })));
174​
175 assert.strictEqual((yield* review(projectRoot, [projectRoot])).cwd, projectRoot);
176 assert.strictEqual((yield* review(projectChild, [projectRoot])).cwd, projectChild);
177 const dottedChild = `${projectRoot}/..feature`;
178 yield* fs.makeDirectory(dottedChild);
179 assert.strictEqual((yield* review(dottedChild, [projectRoot])).cwd, dottedChild);
180 for (const [cwd, projectRoots] of [
181 [sibling, [projectRoot]],
182 [projectRoot, []],
183 [projectRoot, "unavailable"],
184 ] as const) {
185 const error = yield* review(cwd, projectRoots).pipe(Effect.flip);
186 assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
187 }
188 }).pipe(Effect.provide(NodeServices.layer)),
189 );
190​
191 it.effect("allows the worktree a thread is bound to, and nothing else outside the project", () =>
192 Effect.gen(function* () {
193 const fs = yield* FileSystem.FileSystem;
194 const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" });
195 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" });
196 const project = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-project-" });
197 const bound = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-bound-" });
198 const deleted = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-deleted-" });
199 const unbound = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-unbound-" });
200 const claimed = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-claimed-" });
201 yield* fs.makeDirectory(`${bound}/sub`);
202​
203 const review = (cwd: string) =>
204 Effect.gen(function* () {
205 const service = yield* ReviewService.ReviewService;
206 return yield* service.getDiffPreview({ cwd });
207 }).pipe(
208 Effect.provide(
209 layer({
210 workspaceRoot,
211 baseDir,
212 projectRoots: [project],
213 gitWorktrees: [project, bound, deleted],
214 threadWorktrees: [
215 { worktreePath: bound },
216 { worktreePath: claimed },
217 { worktreePath: deleted, deleted: true },
218 { worktreePath: "t3-review-relative" },
219 { worktreePath: "/" },
220 ],
221 }),
222 ),
223 );
224​
225 for (const cwd of [bound, `${bound}/sub`]) {
226 assert.strictEqual((yield* review(cwd)).cwd, cwd);
227 }
228 for (const cwd of [deleted, unbound, claimed, `${process.cwd()}/t3-review-relative`]) {
229 const error = yield* review(cwd).pipe(Effect.flip);
230 assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
231 }
232 }).pipe(Effect.provide(NodeServices.layer)),
233 );
234​
235 it.effect("allows a thread's worktree linked from a managed folder to a checkout elsewhere", () =>
236 Effect.gen(function* () {
237 const fs = yield* FileSystem.FileSystem;
238 const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" });
239 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" });
240 const managed = yield* fs.realPath(
241 yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-worktrees-" }),
242 );
243 const checkout = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-checkout-" });
244 yield* fs.symlink(checkout, `${managed}/feature`);
245​
246 const result = yield* Effect.gen(function* () {
247 const review = yield* ReviewService.ReviewService;
248 return yield* review.getDiffPreview({ cwd: checkout });
249 }).pipe(
250 Effect.provide(
251 layer({
252 workspaceRoot,
253 baseDir,
254 worktreesDirectory: managed,
255 threadWorktrees: [{ worktreePath: `${managed}/feature` }],
256 }),
257 ),
258 );
259 assert.strictEqual(result.cwd, checkout);
260 }).pipe(Effect.provide(NodeServices.layer)),
261 );
262});