import { assert, it, describe } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import type * as PlatformError from "effect/PlatformError"; import type * as Scope from "effect/Scope"; import * as DateTime from "effect/DateTime"; import * as Option from "effect/Option"; import type { VcsDriverKind } from "@t3tools/contracts"; import * as VcsDriver from "../VcsDriver.ts"; function normalizePathForComparison(value: string): string { return value.replaceAll("\\", "/"); } export interface VcsDriverFixture { readonly createRepo: (cwd: string) => Effect.Effect; readonly writeFile: ( cwd: string, relativePath: string, contents: string, ) => Effect.Effect; readonly trackFile?: (cwd: string, relativePath: string) => Effect.Effect; readonly commit?: (cwd: string, message: string) => Effect.Effect; readonly ignorePath: ( cwd: string, pattern: string, ) => Effect.Effect; } export interface VcsDriverContractSuiteInput { readonly name: string; readonly kind: VcsDriverKind; readonly layer: Layer.Layer< VcsDriver.VcsDriver | R | FileSystem.FileSystem | Path.Path, E, never >; readonly fixture: VcsDriverFixture; } export function runVcsDriverContractSuite(input: VcsDriverContractSuiteInput) { const makeTmpDir = ( prefix = `t3-${input.kind}-vcs-contract-`, ): Effect.Effect => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ prefix }); }); it.layer(input.layer)(`${input.name} VCS driver contract`, (it) => { describe("repository detection", () => { it.effect("returns null outside a repository", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* VcsDriver.VcsDriver; assert.equal(yield* driver.detectRepository(cwd), null); assert.equal(yield* driver.isInsideWorkTree(cwd), false); }), ); it.effect("detects repository identity inside a repository and nested directories", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* VcsDriver.VcsDriver; yield* input.fixture.createRepo(cwd); yield* input.fixture.writeFile(cwd, "src/index.ts", "export const value = 1;\n"); const identity = yield* driver.detectRepository(cwd); assert.equal(identity?.kind, input.kind); assert.isTrue( normalizePathForComparison(identity?.rootPath ?? "").endsWith( normalizePathForComparison(cwd), ), ); assert.equal(identity?.freshness.source, "live-local"); assert.isTrue(DateTime.isDateTime(identity?.freshness.observedAt)); assert.isTrue(Option.isNone(identity?.freshness.expiresAt ?? Option.none())); assert.equal(yield* driver.isInsideWorkTree(cwd), true); const path = yield* Path.Path; const nestedDir = path.join(cwd, "src"); const nestedIdentity = yield* driver.detectRepository(nestedDir); assert.equal(nestedIdentity?.rootPath, identity?.rootPath); assert.equal(yield* driver.isInsideWorkTree(nestedDir), true); }), ); }); describe("workspace files", () => { it.effect("lists tracked and untracked non-ignored files", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* VcsDriver.VcsDriver; yield* input.fixture.createRepo(cwd); yield* input.fixture.writeFile(cwd, "tracked.ts", "export const tracked = true;\n"); if (input.fixture.trackFile && input.fixture.commit) { yield* input.fixture.trackFile(cwd, "tracked.ts"); yield* input.fixture.commit(cwd, "Track file"); } yield* input.fixture.writeFile(cwd, "untracked.ts", "export const untracked = true;\n"); const result = yield* driver.listWorkspaceFiles(cwd); assert.include(result.paths, "tracked.ts"); assert.include(result.paths, "untracked.ts"); assert.equal(result.truncated, false); assert.equal(result.freshness.source, "live-local"); assert.isTrue(DateTime.isDateTime(result.freshness.observedAt)); assert.isTrue(Option.isNone(result.freshness.expiresAt)); }), ); it.effect("excludes ignored files from workspace listing", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* VcsDriver.VcsDriver; yield* input.fixture.createRepo(cwd); yield* input.fixture.ignorePath(cwd, "*.log"); yield* input.fixture.writeFile(cwd, "included.ts", "export const included = true;\n"); yield* input.fixture.writeFile(cwd, "debug.log", "ignore me\n"); yield* input.fixture.writeFile(cwd, "nested/error.log", "ignore me too\n"); const result = yield* driver.listWorkspaceFiles(cwd); assert.include(result.paths, "included.ts"); assert.notInclude(result.paths, "debug.log"); assert.notInclude(result.paths, "nested/error.log"); }), ); }); describe("ignored path filtering", () => { it.effect("filters ignored paths", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* VcsDriver.VcsDriver; yield* input.fixture.createRepo(cwd); yield* input.fixture.ignorePath(cwd, "*.log"); const result = yield* driver.filterIgnoredPaths(cwd, [ "keep.ts", "debug.log", "nested/error.log", ]); assert.deepStrictEqual(result, ["keep.ts"]); }), ); it.effect("returns empty input unchanged", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* VcsDriver.VcsDriver; yield* input.fixture.createRepo(cwd); assert.deepStrictEqual(yield* driver.filterIgnoredPaths(cwd, []), []); }), ); }); }); }