import fs from "node:fs";import os from "node:os";import path from "node:path"; import { Effect, Layer, ManagedRuntime } from "effect";import { afterEach, describe, expect, it } from "vitest"; import { ProjectRepository } from "../Services/Projects.ts";import { ProjectRepositoryLive } from "./Projects.ts";import { makeSqlitePersistenceLive } from "./Sqlite.ts"; function makeProjectRepositoryTest(dbPath: string) { return ProjectRepositoryLive.pipe(Layer.provide(makeSqlitePersistenceLive(dbPath)));} const tempDirs: string[] = []; function makeTempDir(prefix: string): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); tempDirs.push(dir); return dir;} afterEach(() => { for (const dir of tempDirs.splice(0, tempDirs.length)) { fs.rmSync(dir, { recursive: true, force: true }); }}); describe("ProjectRepository", () => { it("persists projects and deduplicates adds by cwd", async () => { const stateDir = makeTempDir("t3code-project-repo-state-"); const projectDir = makeTempDir("t3code-project-repo-project-"); const dbPath = path.join(stateDir, "orchestration.sqlite"); const firstRuntime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath)); const first = await firstRuntime.runPromise(Effect.service(ProjectRepository)); const created = await firstRuntime.runPromise(first.add({ cwd: projectDir })); expect(created.created).toBe(true); const duplicate = await firstRuntime.runPromise(first.add({ cwd: projectDir })); expect(duplicate.created).toBe(false); expect(duplicate.project.id).toBe(created.project.id); await firstRuntime.dispose(); const secondRuntime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath)); const second = await secondRuntime.runPromise(Effect.service(ProjectRepository)); const listed = await secondRuntime.runPromise(second.list()); expect(listed).toHaveLength(1); expect(listed[0]?.cwd).toBe(projectDir); await secondRuntime.dispose(); }); it("prunes missing project paths", async () => { const stateDir = makeTempDir("t3code-project-repo-prune-state-"); const existing = makeTempDir("t3code-project-repo-prune-existing-"); const missing = makeTempDir("t3code-project-repo-prune-missing-"); const dbPath = path.join(stateDir, "orchestration.sqlite"); const runtime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath)); const repository = await runtime.runPromise(Effect.service(ProjectRepository)); const existingProject = await runtime.runPromise(repository.add({ cwd: existing })); const missingProject = await runtime.runPromise(repository.add({ cwd: missing })); expect(existingProject.created).toBe(true); expect(missingProject.created).toBe(true); fs.rmSync(missing, { recursive: true, force: true }); await runtime.runPromise(repository.pruneMissing()); const listed = await runtime.runPromise(repository.list()); expect(listed).toHaveLength(1); expect(listed[0]?.cwd).toBe(existing); await runtime.dispose(); });});
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { Effect, Layer, ManagedRuntime } from "effect";
import { afterEach, describe, expect, it } from "vitest";
import { ProjectRepository } from "../Services/Projects.ts";
import { ProjectRepositoryLive } from "./Projects.ts";
import { makeSqlitePersistenceLive } from "./Sqlite.ts";
function makeProjectRepositoryTest(dbPath: string) {
return ProjectRepositoryLive.pipe(Layer.provide(makeSqlitePersistenceLive(dbPath)));
}
const tempDirs: string[] = [];
function makeTempDir(prefix: string): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
tempDirs.push(dir);
return dir;
}
afterEach(() => {
for (const dir of tempDirs.splice(0, tempDirs.length)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
describe("ProjectRepository", () => {
it("persists projects and deduplicates adds by cwd", async () => {
const stateDir = makeTempDir("t3code-project-repo-state-");
const projectDir = makeTempDir("t3code-project-repo-project-");
const dbPath = path.join(stateDir, "orchestration.sqlite");
const firstRuntime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath));
const first = await firstRuntime.runPromise(Effect.service(ProjectRepository));
const created = await firstRuntime.runPromise(first.add({ cwd: projectDir }));
expect(created.created).toBe(true);
const duplicate = await firstRuntime.runPromise(first.add({ cwd: projectDir }));
expect(duplicate.created).toBe(false);
expect(duplicate.project.id).toBe(created.project.id);
await firstRuntime.dispose();
const secondRuntime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath));
const second = await secondRuntime.runPromise(Effect.service(ProjectRepository));
const listed = await secondRuntime.runPromise(second.list());
expect(listed).toHaveLength(1);
expect(listed[0]?.cwd).toBe(projectDir);
await secondRuntime.dispose();
});
it("prunes missing project paths", async () => {
const stateDir = makeTempDir("t3code-project-repo-prune-state-");
const existing = makeTempDir("t3code-project-repo-prune-existing-");
const missing = makeTempDir("t3code-project-repo-prune-missing-");
const dbPath = path.join(stateDir, "orchestration.sqlite");
const runtime = ManagedRuntime.make(makeProjectRepositoryTest(dbPath));
const repository = await runtime.runPromise(Effect.service(ProjectRepository));
const existingProject = await runtime.runPromise(repository.add({ cwd: existing }));
const missingProject = await runtime.runPromise(repository.add({ cwd: missing }));
expect(existingProject.created).toBe(true);
expect(missingProject.created).toBe(true);
fs.rmSync(missing, { recursive: true, force: true });
await runtime.runPromise(repository.pruneMissing());
const listed = await runtime.runPromise(repository.list());
expect(listed).toHaveLength(1);
expect(listed[0]?.cwd).toBe(existing);
await runtime.dispose();
});
});