// tests/unit/application/recovery/append-recovery-failed-entry.spec.ts import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { appendRecoveryFailedEntry } from "../../../../src/application/recovery/append-recovery-failed-entry.js"; const RECOVERY_EVENTS_FILE = "recovery-events.jsonl"; let repo: string; let home: string; beforeEach(() => { repo = mkdtempSync(join(tmpdir(), "tff-events-")); home = join(repo, ".tff"); }); afterEach(() => rmSync(repo, { recursive: true, force: true })); describe("appendRecoveryFailedEntry", () => { it("appends a recovery-failed event when .tff/ exists", async () => { mkdirSync(home, { recursive: true }); await appendRecoveryFailedEntry(home, new Error("boom")); const raw = readFileSync(join(home, RECOVERY_EVENTS_FILE), "utf-8"); const line = raw.trim().split("\n").at(-1) as string; const parsed = JSON.parse(line) as Record; expect(parsed.type).toBe("recovery-failed"); expect(parsed.error).toBe("boom"); expect(parsed.stack).toBeTypeOf("string"); expect(typeof parsed.timestamp).toBe("string"); expect((parsed.context as Record).platform).toBe(process.platform); }); it("does not create .tff/ when absent (skips append silently)", async () => { await appendRecoveryFailedEntry(home, new Error("boom")); expect(existsSync(home)).toBe(false); }); it("appends multiple events without truncating prior ones", async () => { mkdirSync(home, { recursive: true }); await appendRecoveryFailedEntry(home, new Error("first")); await appendRecoveryFailedEntry(home, new Error("second")); const lines = readFileSync(join(home, RECOVERY_EVENTS_FILE), "utf-8").trim().split("\n"); expect(lines).toHaveLength(2); }); it("handles non-Error thrown values (string, plain object)", async () => { mkdirSync(home, { recursive: true }); // Pass a plain string — exercises the `err instanceof Error ? ... : new Error(String(err))` false branch await appendRecoveryFailedEntry(home, "string error"); const raw = readFileSync(join(home, RECOVERY_EVENTS_FILE), "utf-8"); const parsed = JSON.parse(raw.trim()) as Record; expect(parsed.type).toBe("recovery-failed"); expect(typeof parsed.error).toBe("string"); }); it("swallows internal failures and creates no side effects", async () => { // Point homeDir at a regular file so stat(...).isDirectory() is false. const file = join(repo, "blocker"); writeFileSync(file, ""); await expect(appendRecoveryFailedEntry(file, new Error("x"))).resolves.toBeUndefined(); // Assert no side effects: no sibling events file, blocker untouched. expect(existsSync(join(repo, RECOVERY_EVENTS_FILE))).toBe(false); expect(readFileSync(file, "utf-8")).toBe(""); }); });