225 lines · 8.5 KB · TypeScript
RawDownload
import { assert, it, describe } from "@effect/vitest";import * as NodeServices from "@effect/platform-node/NodeServices";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Logger from "effect/Logger";import * as Path from "effect/Path"; import * as VcsProjectConfig from "./VcsProjectConfig.ts"; const TestLayer = VcsProjectConfig.layer.pipe(  Layer.provide(NodeServices.layer),  Layer.provideMerge(NodeServices.layer),); describe("VcsProjectConfig", () => {  it("keeps operation context and the original cause on config errors", () => {    const cause = new Error("permission denied");    const error = new VcsProjectConfig.VcsProjectConfigError({      operation: "read",      cwd: "/repo/packages/app",      configPath: "/repo/.t3code/vcs.json",      cause,    });     assert.equal(error.operation, "read");    assert.equal(error.cwd, "/repo/packages/app");    assert.equal(error.configPath, "/repo/.t3code/vcs.json");    assert.strictEqual(error.cause, cause);    assert.equal(error.message, "Failed to read VCS project config at /repo/.t3code/vcs.json.");  });   it.layer(TestLayer)("uses an explicit requested VCS kind before config", (it) => {    it.effect("returns the requested kind", () =>      Effect.gen(function* () {        const config = yield* VcsProjectConfig.VcsProjectConfig;        const kind = yield* config.resolveKind({          cwd: "/repo",          requestedKind: "jj",        });         assert.equal(kind, "jj");      }),    );  });   it.layer(TestLayer)("discovers .t3code/vcs.json from nested workspaces", (it) => {    it.effect("returns the configured kind", () =>      Effect.gen(function* () {        const fileSystem = yield* FileSystem.FileSystem;        const path = yield* Path.Path;        const root = yield* fileSystem.makeTempDirectoryScoped({          prefix: "t3-vcs-config-test-",        });        const configDir = path.join(root, ".t3code");        const nested = path.join(root, "packages", "app");        yield* fileSystem.makeDirectory(configDir, { recursive: true });        yield* fileSystem.makeDirectory(nested, { recursive: true });        yield* fileSystem.writeFileString(          path.join(configDir, "vcs.json"),          // @effect-diagnostics-next-line preferSchemaOverJson:off          JSON.stringify({ vcs: { kind: "jj" } }),        );         const config = yield* VcsProjectConfig.VcsProjectConfig;        const kind = yield* config.resolveKind({ cwd: nested });         assert.equal(kind, "jj");      }),    );  });   it.layer(TestLayer)("continues to parent configs after a candidate inspect failure", (it) => {    it.effect("logs the failed candidate and returns the parent config", () => {      const messages: unknown[] = [];      const logger = Logger.make<unknown, void>(({ message }) => {        messages.push(message);      });       return Effect.gen(function* () {        const fileSystem = yield* FileSystem.FileSystem;        const path = yield* Path.Path;        const root = yield* fileSystem.makeTempDirectoryScoped({          prefix: "t3-vcs-config-test-",        });        const configDir = path.join(root, ".t3code");        const cwd = path.join(root, "invalid\0child");        yield* fileSystem.makeDirectory(configDir, { recursive: true });        yield* fileSystem.writeFileString(          path.join(configDir, "vcs.json"),          // @effect-diagnostics-next-line preferSchemaOverJson:off          JSON.stringify({ vcs: { kind: "jj" } }),        );         const config = yield* VcsProjectConfig.VcsProjectConfig;        const kind = yield* config.resolveKind({ cwd });         assert.equal(kind, "jj");        const failedCandidate = path.join(cwd, ".t3code", "vcs.json");        const [error] = messages[0] as ReadonlyArray<unknown>;        assert.instanceOf(error, VcsProjectConfig.VcsProjectConfigError);        assert.equal(          error.message,          "Failed to inspect VCS project config at " + failedCandidate + ".",        );        assert.deepInclude(error, {          operation: "inspect",          cwd,          configPath: failedCandidate,          _tag: "VcsProjectConfigError",        });      }).pipe(Effect.provide(Logger.layer([logger], { mergeWithExisting: false })));    });  });   it.layer(TestLayer)("falls back to auto when no config exists", (it) => {    it.effect("returns auto", () =>      Effect.gen(function* () {        const fileSystem = yield* FileSystem.FileSystem;        const root = yield* fileSystem.makeTempDirectoryScoped({          prefix: "t3-vcs-config-test-",        });        const config = yield* VcsProjectConfig.VcsProjectConfig;        const kind = yield* config.resolveKind({ cwd: root });         assert.equal(kind, "auto");      }),    );  });   it.layer(TestLayer)("falls back to auto when config JSON is malformed", (it) => {    it.effect("returns auto and logs the failed operation and path", () => {      const messages: unknown[] = [];      const logger = Logger.make<unknown, void>(({ message }) => {        messages.push(message);      });       return Effect.gen(function* () {        const fileSystem = yield* FileSystem.FileSystem;        const path = yield* Path.Path;        const root = yield* fileSystem.makeTempDirectoryScoped({          prefix: "t3-vcs-config-test-",        });        const configDir = path.join(root, ".t3code");        yield* fileSystem.makeDirectory(configDir, { recursive: true });        yield* fileSystem.writeFileString(path.join(configDir, "vcs.json"), "{not json");         const config = yield* VcsProjectConfig.VcsProjectConfig;        const kind = yield* config.resolveKind({ cwd: root });         assert.equal(kind, "auto");        const [error] = messages[0] as ReadonlyArray<unknown>;        assert.instanceOf(error, VcsProjectConfig.VcsProjectConfigError);        assert.equal(          error.message,          "Failed to decode VCS project config at " + path.join(configDir, "vcs.json") + ".",        );        assert.deepInclude(error.cause, { _tag: "SchemaError" });        assert.deepInclude(error, {          operation: "decode",          cwd: root,          configPath: path.join(configDir, "vcs.json"),          _tag: "VcsProjectConfigError",        });      }).pipe(Effect.provide(Logger.layer([logger], { mergeWithExisting: false })));    });  });   it.layer(TestLayer)("falls back to auto when the config path cannot be read", (it) => {    it.effect("retains the read failure context", () => {      const messages: unknown[] = [];      const logger = Logger.make<unknown, void>(({ message }) => {        messages.push(message);      });       return Effect.gen(function* () {        const fileSystem = yield* FileSystem.FileSystem;        const path = yield* Path.Path;        const root = yield* fileSystem.makeTempDirectoryScoped({          prefix: "t3-vcs-config-test-",        });        const configPath = path.join(root, ".t3code", "vcs.json");        yield* fileSystem.makeDirectory(configPath, { recursive: true });         const config = yield* VcsProjectConfig.VcsProjectConfig;        const kind = yield* config.resolveKind({ cwd: root });         assert.equal(kind, "auto");        const [error] = messages[0] as ReadonlyArray<unknown>;        assert.instanceOf(error, VcsProjectConfig.VcsProjectConfigError);        assert.equal(error.message, "Failed to read VCS project config at " + configPath + ".");        assert.deepInclude(error.cause, { _tag: "PlatformError" });        assert.deepInclude(error, {          operation: "read",          cwd: root,          configPath,          _tag: "VcsProjectConfigError",        });      }).pipe(Effect.provide(Logger.layer([logger], { mergeWithExisting: false })));    });  });   it.layer(TestLayer)("falls back to auto when config kind is invalid", (it) => {    it.effect("returns auto", () =>      Effect.gen(function* () {        const fileSystem = yield* FileSystem.FileSystem;        const path = yield* Path.Path;        const root = yield* fileSystem.makeTempDirectoryScoped({          prefix: "t3-vcs-config-test-",        });        const configDir = path.join(root, ".t3code");        yield* fileSystem.makeDirectory(configDir, { recursive: true });        yield* fileSystem.writeFileString(          path.join(configDir, "vcs.json"),          `{"vcs":{"kind":"svn"}}`,        );         const config = yield* VcsProjectConfig.VcsProjectConfig;        const kind = yield* config.resolveKind({ cwd: root });         assert.equal(kind, "auto");      }),    );  });});