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<R, E> { readonly createRepo: (cwd: string) => Effect.Effect<void, E, R>; readonly writeFile: ( cwd: string, relativePath: string, contents: string, ) => Effect.Effect<void, E, R | FileSystem.FileSystem | Path.Path>; readonly trackFile?: (cwd: string, relativePath: string) => Effect.Effect<void, E, R>; readonly commit?: (cwd: string, message: string) => Effect.Effect<void, E, R>; readonly ignorePath: ( cwd: string, pattern: string, ) => Effect.Effect<void, E, R | FileSystem.FileSystem | Path.Path>;} export interface VcsDriverContractSuiteInput<R, E> { readonly name: string; readonly kind: VcsDriverKind; readonly layer: Layer.Layer< VcsDriver.VcsDriver | R | FileSystem.FileSystem | Path.Path, E, never >; readonly fixture: VcsDriverFixture<R, E>;} export function runVcsDriverContractSuite<R, E>(input: VcsDriverContractSuiteInput<R, E>) { const makeTmpDir = ( prefix = `t3-${input.kind}-vcs-contract-`, ): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> => 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, []), []); }), ); }); });}
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<R, E> {
readonly createRepo: (cwd: string) => Effect.Effect<void, E, R>;
readonly writeFile: (
cwd: string,
relativePath: string,
contents: string,
) => Effect.Effect<void, E, R | FileSystem.FileSystem | Path.Path>;
readonly trackFile?: (cwd: string, relativePath: string) => Effect.Effect<void, E, R>;
readonly commit?: (cwd: string, message: string) => Effect.Effect<void, E, R>;
readonly ignorePath: (
cwd: string,
pattern: string,
) => Effect.Effect<void, E, R | FileSystem.FileSystem | Path.Path>;
}
export interface VcsDriverContractSuiteInput<R, E> {
readonly name: string;
readonly kind: VcsDriverKind;
readonly layer: Layer.Layer<
VcsDriver.VcsDriver | R | FileSystem.FileSystem | Path.Path,
E,
never
>;
readonly fixture: VcsDriverFixture<R, E>;
}
export function runVcsDriverContractSuite<R, E>(input: VcsDriverContractSuiteInput<R, E>) {
const makeTmpDir = (
prefix = `t3-${input.kind}-vcs-contract-`,
): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> =>
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, []), []);
}),
);
});
});
}