import { test, expect } from "vitest"; import { closeSync, existsSync, fsyncSync, mkdtempSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { captureSettingsSnapshot, loadSettings, restoreSettingsSnapshot, saveSettings, } from "../src/settings/settings-store.js"; import { loadConfig } from "../src/config.js"; function cfgWith(file: string) { return loadConfig({ THT_HARNESS_DIR: "../harness", SETTINGS_FILE: file }); } test("loadSettings returns {} when the file does not exist", () => { const dir = mkdtempSync(join(tmpdir(), "tht-set-")); try { expect(loadSettings(cfgWith(join(dir, "settings.json")))).toEqual({}); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("saveSettings writes the file and loadSettings reads it back", () => { const dir = mkdtempSync(join(tmpdir(), "tht-set-")); try { const cfg = cfgWith(join(dir, "nested", "settings.json")); const saved = saveSettings(cfg, { workspace: "psd", thinking: "medium" }); expect(saved.thinking).toBe("medium"); expect(loadSettings(cfg)).toEqual({ workspace: "psd", thinking: "medium" }); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("loadSettings returns {} on corrupt JSON (no throw)", () => { const dir = mkdtempSync(join(tmpdir(), "tht-set-")); try { const file = join(dir, "settings.json"); writeFileSync(file, "{ not json"); expect(loadSettings(cfgWith(file))).toEqual({}); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("loadConfig sets settingsFile from SETTINGS_FILE, default data/settings.json", () => { expect(loadConfig({}).settingsFile).toBe("data/settings.json"); expect(loadConfig({ SETTINGS_FILE: "/x/y.json" }).settingsFile).toBe("/x/y.json"); }); test("saveSettings restores the previous file when post-rename directory durability fails", () => { const dir = mkdtempSync(join(tmpdir(), "tht-set-transaction-")); try { const cfg = cfgWith(join(dir, "settings.json")); saveSettings(cfg, { thinking: "low" }); let syncs = 0; expect(() => saveSettings( cfg, { thinking: "high" }, { syncDirectory(directory: string) { syncs += 1; if (syncs === 1) throw new Error("injected directory fsync failure"); const fd = openSync(directory, "r"); try { fsyncSync(fd); } finally { closeSync(fd); } }, }, )).toThrow(/directory fsync failure/); expect(loadSettings(cfg)).toEqual({ thinking: "low" }); expect(syncs).toBeGreaterThanOrEqual(2); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("settings snapshots restore exact absent and empty-file states", () => { const dir = mkdtempSync(join(tmpdir(), "tht-settings-snapshot-")); try { const file = join(dir, "settings.json"); const cfg = cfgWith(file); const absent = captureSettingsSnapshot(cfg); expect(absent).toEqual({ exists: false, rawBase64: "" }); saveSettings(cfg, { provider: "new", model: "model", thinking: "high" }); restoreSettingsSnapshot(cfg, absent); expect(existsSync(file)).toBe(false); writeFileSync(file, Buffer.alloc(0), { mode: 0o600 }); const empty = captureSettingsSnapshot(cfg); expect(empty.exists).toBe(true); saveSettings(cfg, { provider: "new", model: "model", thinking: "high" }); restoreSettingsSnapshot(cfg, empty); expect(readFileSync(file)).toEqual(Buffer.alloc(0)); } finally { rmSync(dir, { recursive: true, force: true }); } });