239 lines · 6.8 KB · TypeScript
RawDownload
import * as fs from "node:fs";import * as os from "node:os";import * as path from "node:path"; import {  EnvironmentId,  type ClientSettings,  type PersistedSavedEnvironmentRecord,} from "@t3tools/contracts";import { afterEach, describe, expect, it } from "vitest"; import {  readClientSettings,  readSavedEnvironmentRegistry,  readSavedEnvironmentSecret,  removeSavedEnvironmentSecret,  writeClientSettings,  writeSavedEnvironmentRegistry,  writeSavedEnvironmentSecret,  type DesktopSecretStorage,} from "./clientPersistence.ts"; const tempDirectories: string[] = []; afterEach(() => {  for (const directory of tempDirectories.splice(0)) {    fs.rmSync(directory, { recursive: true, force: true });  }}); function makeTempPath(fileName: string): string {  const directory = fs.mkdtempSync(path.join(os.tmpdir(), "t3-client-persistence-test-"));  tempDirectories.push(directory);  return path.join(directory, fileName);} function makeSecretStorage(available: boolean): DesktopSecretStorage {  return {    isEncryptionAvailable: () => available,    encryptString: (value) => Buffer.from(`enc:${value}`, "utf8"),    decryptString: (value) => {      const decoded = value.toString("utf8");      if (!decoded.startsWith("enc:")) {        throw new Error("invalid secret");      }      return decoded.slice("enc:".length);    },  };} const clientSettings: ClientSettings = {  confirmThreadArchive: true,  confirmThreadDelete: false,  diffWordWrap: true,  favorites: [],  sidebarProjectGroupingMode: "repository_path",  sidebarProjectGroupingOverrides: {    "environment-1:/tmp/project-a": "separate",  },  sidebarProjectSortOrder: "manual",  sidebarThreadSortOrder: "created_at",  timestampFormat: "24-hour",}; const savedRegistryRecord: PersistedSavedEnvironmentRecord = {  environmentId: EnvironmentId.make("environment-1"),  label: "Remote environment",  httpBaseUrl: "https://remote.example.com/",  wsBaseUrl: "wss://remote.example.com/",  createdAt: "2026-04-09T00:00:00.000Z",  lastConnectedAt: "2026-04-09T01:00:00.000Z",}; describe("clientPersistence", () => {  it("persists and reloads client settings", () => {    const settingsPath = makeTempPath("client-settings.json");     writeClientSettings(settingsPath, clientSettings);     expect(readClientSettings(settingsPath)).toEqual(clientSettings);  });   it("persists and reloads saved environment metadata", () => {    const registryPath = makeTempPath("saved-environments.json");     writeSavedEnvironmentRegistry(registryPath, [savedRegistryRecord]);     expect(readSavedEnvironmentRegistry(registryPath)).toEqual([savedRegistryRecord]);  });   it("persists encrypted saved environment secrets when encryption is available", () => {    const registryPath = makeTempPath("saved-environments.json");    const secretStorage = makeSecretStorage(true);     writeSavedEnvironmentRegistry(registryPath, [savedRegistryRecord]);     expect(      writeSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,        secret: "bearer-token",        secretStorage,      }),    ).toBe(true);     expect(      readSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,        secretStorage,      }),    ).toBe("bearer-token");     expect(JSON.parse(fs.readFileSync(registryPath, "utf8"))).toEqual({      records: [        {          ...savedRegistryRecord,          encryptedBearerToken: Buffer.from("enc:bearer-token", "utf8").toString("base64"),        },      ],    });  });   it("preserves existing secrets when encryption is unavailable", () => {    const registryPath = makeTempPath("saved-environments.json");    const availableSecretStorage = makeSecretStorage(true);     writeSavedEnvironmentRegistry(registryPath, [savedRegistryRecord]);     writeSavedEnvironmentSecret({      registryPath,      environmentId: savedRegistryRecord.environmentId,      secret: "bearer-token",      secretStorage: availableSecretStorage,    });     expect(      writeSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,        secret: "next-token",        secretStorage: makeSecretStorage(false),      }),    ).toBe(false);     expect(      readSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,        secretStorage: availableSecretStorage,      }),    ).toBe("bearer-token");  });   it("removes saved environment secrets", () => {    const registryPath = makeTempPath("saved-environments.json");    const secretStorage = makeSecretStorage(true);     writeSavedEnvironmentRegistry(registryPath, [savedRegistryRecord]);     writeSavedEnvironmentSecret({      registryPath,      environmentId: savedRegistryRecord.environmentId,      secret: "bearer-token",      secretStorage,    });     removeSavedEnvironmentSecret({      registryPath,      environmentId: savedRegistryRecord.environmentId,    });     expect(      readSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,        secretStorage,      }),    ).toBeNull();  });   it("treats malformed secrets documents as empty", () => {    const registryPath = makeTempPath("saved-environments.json");    fs.writeFileSync(registryPath, "{}\n", "utf8");     expect(      readSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,        secretStorage: makeSecretStorage(true),      }),    ).toBeNull();     expect(() =>      removeSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,      }),    ).not.toThrow();  });   it("returns false when writing a secret without metadata", () => {    const registryPath = makeTempPath("saved-environments.json");     expect(      writeSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,        secret: "bearer-token",        secretStorage: makeSecretStorage(true),      }),    ).toBe(false);  });   it("preserves encrypted secrets when metadata is rewritten", () => {    const registryPath = makeTempPath("saved-environments.json");    const secretStorage = makeSecretStorage(true);     writeSavedEnvironmentRegistry(registryPath, [savedRegistryRecord]);     writeSavedEnvironmentSecret({      registryPath,      environmentId: savedRegistryRecord.environmentId,      secret: "bearer-token",      secretStorage,    });     writeSavedEnvironmentRegistry(registryPath, [savedRegistryRecord]);     expect(readSavedEnvironmentRegistry(registryPath)).toEqual([savedRegistryRecord]);    expect(      readSavedEnvironmentSecret({        registryPath,        environmentId: savedRegistryRecord.environmentId,        secretStorage,      }),    ).toBe("bearer-token");  });});