315 lines · 12.6 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import { describe, expect, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Path from "effect/Path";import * as PlatformError from "effect/PlatformError";import * as Schema from "effect/Schema"; import { CodexSettings } from "@t3tools/contracts";import {  CodexShadowHomeEntryConflictError,  CodexShadowHomePathConflictError,  materializeCodexShadowHome,  resolveCodexHomeLayout,} from "./CodexHomeLayout.ts";import { symlinksSupported } from "@t3tools/shared/testing/symlinks";const decodeCodexSettingsValue = Schema.decodeSync(CodexSettings); const decodeCodexSettings = (input: {  readonly enabled?: boolean;  readonly homePath?: string;  readonly shadowHomePath?: string;  readonly customModels?: readonly string[];  readonly binaryPath?: string;}): CodexSettings => decodeCodexSettingsValue(input); const makeTempDir = Effect.fn("CodexHomeLayout.test.makeTempDir")(function* (prefix: string) {  const fileSystem = yield* FileSystem.FileSystem;  return yield* fileSystem.makeTempDirectoryScoped({ prefix });}); const writeTextFile = Effect.fn("CodexHomeLayout.test.writeTextFile")(function* (  filePath: string,  contents: string,) {  const fileSystem = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  yield* fileSystem.makeDirectory(path.dirname(filePath), { recursive: true });  yield* fileSystem.writeFileString(filePath, contents);}); it.layer(NodeServices.layer)("CodexHomeLayout", (it) => {  describe("resolveCodexHomeLayout", () => {    it.effect("uses direct CODEX_HOME when no shadow home is configured", () =>      Effect.gen(function* () {        const homePath = yield* makeTempDir("t3code-codex-home-");         const layout = yield* resolveCodexHomeLayout(          decodeCodexSettings({            homePath,          }),        );         expect(layout).toMatchObject({          mode: "direct",          sharedHomePath: homePath,          effectiveHomePath: homePath,          continuationKey: `codex:home:${homePath}`,        });      }),    );     it.effect("uses the shared home for continuation and the shadow home for runtime", () =>      Effect.gen(function* () {        const path = yield* Path.Path;        const sharedHome = yield* makeTempDir("t3code-codex-shared-");        const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");        const shadowHome = path.join(shadowRoot, "shadow");         const layout = yield* resolveCodexHomeLayout(          decodeCodexSettings({            homePath: sharedHome,            shadowHomePath: shadowHome,          }),        );         expect(layout).toMatchObject({          mode: "authOverlay",          sharedHomePath: sharedHome,          effectiveHomePath: shadowHome,          continuationKey: `codex:home:${sharedHome}`,        });      }),    );  });   describe("materializeCodexShadowHome", () => {    it.effect.skipIf(!symlinksSupported)(      "materializes a shadow home with shared state links and private auth",      () =>        Effect.gen(function* () {          const fileSystem = yield* FileSystem.FileSystem;          const path = yield* Path.Path;          const sharedHome = yield* makeTempDir("t3code-codex-shared-");          const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");          const shadowHome = path.join(shadowRoot, "shadow");           yield* fileSystem.makeDirectory(path.join(sharedHome, "sessions"));          yield* writeTextFile(path.join(sharedHome, "config.toml"), 'model = "gpt-5-codex"\n');          yield* writeTextFile(            path.join(sharedHome, "models_cache.json"),            '{"models":["shared"]}\n',          );          yield* writeTextFile(path.join(sharedHome, "auth.json"), '{"shared":true}\n');          yield* fileSystem.makeDirectory(shadowHome, { recursive: true });          yield* writeTextFile(path.join(shadowHome, "auth.json"), '{"shadow":true}\n');          yield* fileSystem.symlink(            path.join(sharedHome, "models_cache.json"),            path.join(shadowHome, "models_cache.json"),          );           const layout = yield* resolveCodexHomeLayout(            decodeCodexSettings({              homePath: sharedHome,              shadowHomePath: shadowHome,            }),          );           yield* materializeCodexShadowHome(layout);           const sessionsTarget = yield* fileSystem.readLink(path.join(shadowHome, "sessions"));          const configTarget = yield* fileSystem.readLink(path.join(shadowHome, "config.toml"));          const mcpOauthLocksTarget = yield* fileSystem.readLink(            path.join(shadowHome, "mcp-oauth-locks"),          );          const modelsCacheExists = yield* fileSystem.exists(            path.join(shadowHome, "models_cache.json"),          );          const authLinkResult = yield* fileSystem            .readLink(path.join(shadowHome, "auth.json"))            .pipe(Effect.result);          const authContents = yield* fileSystem.readFileString(path.join(shadowHome, "auth.json"));           expect(sessionsTarget).toBe(path.join(sharedHome, "sessions"));          expect(configTarget).toBe(path.join(sharedHome, "config.toml"));          expect(mcpOauthLocksTarget).toBe(path.join(sharedHome, "mcp-oauth-locks"));          expect(modelsCacheExists).toBe(false);          expect(authLinkResult._tag).toBe("Failure");          expect(authContents).toContain("shadow");        }),    );     it.effect.skipIf(!symlinksSupported)(      "replaces Codex-created local MCP OAuth locks with the shared lock directory",      () =>        Effect.gen(function* () {          const fileSystem = yield* FileSystem.FileSystem;          const path = yield* Path.Path;          const sharedHome = yield* makeTempDir("t3code-codex-shared-");          const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");          const shadowHome = path.join(shadowRoot, "shadow");          const sharedLocks = path.join(sharedHome, "mcp-oauth-locks");          const shadowLocks = path.join(shadowHome, "mcp-oauth-locks");           yield* writeTextFile(path.join(sharedLocks, "file-store.lock"), "");          yield* writeTextFile(path.join(shadowLocks, "file-store.lock"), "");           const layout = yield* resolveCodexHomeLayout(            decodeCodexSettings({              homePath: sharedHome,              shadowHomePath: shadowHome,            }),          );           yield* materializeCodexShadowHome(layout);           const locksTarget = yield* fileSystem.readLink(shadowLocks);          const sharedLockExists = yield* fileSystem.exists(            path.join(sharedLocks, "file-store.lock"),          );           expect(locksTarget).toBe(sharedLocks);          expect(sharedLockExists).toBe(true);        }),    );     it.effect.skipIf(!symlinksSupported)(      "accepts Codex-created shadow-local runtime directories",      () =>        Effect.gen(function* () {          const fileSystem = yield* FileSystem.FileSystem;          const path = yield* Path.Path;          const sharedHome = yield* makeTempDir("t3code-codex-shared-");          const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");          const shadowHome = path.join(shadowRoot, "shadow");           yield* fileSystem.makeDirectory(path.join(sharedHome, "log"));          yield* fileSystem.makeDirectory(path.join(sharedHome, "memories"));          yield* fileSystem.makeDirectory(path.join(sharedHome, "tmp"));          yield* writeTextFile(path.join(sharedHome, "config.toml"), 'model = "gpt-5-codex"\n');          yield* writeTextFile(path.join(shadowHome, "auth.json"), '{"shadow":true}\n');          yield* fileSystem.makeDirectory(path.join(shadowHome, "log"), { recursive: true });          yield* fileSystem.makeDirectory(path.join(shadowHome, "memories"), { recursive: true });          yield* fileSystem.makeDirectory(path.join(shadowHome, "tmp"), { recursive: true });           const layout = yield* resolveCodexHomeLayout(            decodeCodexSettings({              homePath: sharedHome,              shadowHomePath: shadowHome,            }),          );           yield* materializeCodexShadowHome(layout);           const configTarget = yield* fileSystem.readLink(path.join(shadowHome, "config.toml"));          const logLinkResult = yield* fileSystem            .readLink(path.join(shadowHome, "log"))            .pipe(Effect.result);          const memoriesLinkResult = yield* fileSystem            .readLink(path.join(shadowHome, "memories"))            .pipe(Effect.result);          const tmpLinkResult = yield* fileSystem            .readLink(path.join(shadowHome, "tmp"))            .pipe(Effect.result);           expect(configTarget).toBe(path.join(sharedHome, "config.toml"));          expect(logLinkResult._tag).toBe("Failure");          expect(memoriesLinkResult._tag).toBe("Failure");          expect(tmpLinkResult._tag).toBe("Failure");        }),    );     it.effect("rejects shadow homes that point at the shared home", () =>      Effect.gen(function* () {        const sharedHome = yield* makeTempDir("t3code-codex-shared-");        const layout = yield* resolveCodexHomeLayout(          decodeCodexSettings({            homePath: sharedHome,            shadowHomePath: sharedHome,          }),        );         const error = yield* materializeCodexShadowHome(layout).pipe(Effect.flip);         expect(error).toBeInstanceOf(CodexShadowHomePathConflictError);        expect(error).toMatchObject({          sharedHomePath: sharedHome,          effectiveHomePath: sharedHome,        });        expect(error.message).toBe(          `Codex shadow home path '${sharedHome}' must be different from the shared home path '${sharedHome}'.`,        );      }),    );     it.effect.skipIf(!symlinksSupported)(      "rejects shared entries that already exist in the shadow home as real files",      () =>        Effect.gen(function* () {          const path = yield* Path.Path;          const sharedHome = yield* makeTempDir("t3code-codex-shared-");          const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");          const shadowHome = path.join(shadowRoot, "shadow");          yield* writeTextFile(path.join(sharedHome, "config.toml"), 'model = "gpt-5-codex"\n');          yield* writeTextFile(path.join(shadowHome, "config.toml"), 'model = "local"\n');           const layout = yield* resolveCodexHomeLayout(            decodeCodexSettings({              homePath: sharedHome,              shadowHomePath: shadowHome,            }),          );           const error = yield* materializeCodexShadowHome(layout).pipe(Effect.flip);           expect(error).toBeInstanceOf(CodexShadowHomeEntryConflictError);          expect(error).toMatchObject({            sharedHomePath: sharedHome,            effectiveHomePath: shadowHome,            entryName: "config.toml",            linkPath: path.join(shadowHome, "config.toml"),            targetPath: path.join(sharedHome, "config.toml"),          });          expect(error.message).toBe(            `Cannot create Codex shadow home entry 'config.toml' because '${path.join(shadowHome, "config.toml")}' already exists and is not a symlink.`,          );        }),    );     it.effect("preserves filesystem operation, paths, and cause", () =>      Effect.gen(function* () {        const path = yield* Path.Path;        const sharedRoot = yield* makeTempDir("t3code-codex-shared-root-");        const sharedHome = path.join(sharedRoot, "shared-home");        const shadowRoot = yield* makeTempDir("t3code-codex-shadow-root-");        const shadowHome = path.join(shadowRoot, "shadow");        yield* writeTextFile(sharedHome, "not a directory\n");         const layout = yield* resolveCodexHomeLayout(          decodeCodexSettings({            homePath: sharedHome,            shadowHomePath: shadowHome,          }),        );         const error = yield* materializeCodexShadowHome(layout).pipe(Effect.flip);         expect(error._tag).toBe("CodexShadowHomeFileSystemError");        if (error._tag !== "CodexShadowHomeFileSystemError") {          return expect.fail("Expected CodexShadowHomeFileSystemError");        }        expect(error).toMatchObject({          operation: "makeDirectory",          sharedHomePath: sharedHome,          effectiveHomePath: shadowHome,        });        expect(error.path.startsWith(sharedHome)).toBe(true);        expect(error.cause).toBeInstanceOf(PlatformError.PlatformError);        expect(error.message).toBe(          `Codex shadow home filesystem operation 'makeDirectory' failed for '${error.path}'.`,        );      }),    );  });});