/*
Unit tests for drift guard covering guardTrackedOverwrite (RFC-1126). It also guards the documented public contract and regression-sensitive edge cases.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the drift suite's contract for test-file scaffold coverage.
*/
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { guardTrackedOverwrite } from "../drift-guard.ts";
describe("guardTrackedOverwrite (RFC-1126)", () => {
let dir: string;
const logger = { warn: vi.fn() };
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), "drift-guard-"));
logger.warn.mockClear();
});
afterEach(() => {
rmSync(dir, { recursive: true, force: true });
});
it("reports nothing to lose when the file does not exist", async () => {
const report = await guardTrackedOverwrite(
join(dir, "missing.json"),
"{}",
{ format: "json" },
logger,
);
expect(report.wouldLose).toEqual([]);
expect(report.identical).toBe(false);
expect(logger.warn).not.toHaveBeenCalled();
});
it("reports identical when content matches", async () => {
const file = join(dir, "same.json");
writeFileSync(file, '{"a":1}\n');
const report = await guardTrackedOverwrite(file, '{"a":1}\n', { format: "json" }, logger);
expect(report.identical).toBe(true);
expect(logger.warn).not.toHaveBeenCalled();
});
it("warns with the lost key path for JSON drift", async () => {
const file = join(dir, "package.json");
writeFileSync(file, JSON.stringify({ dependencies: { "extra-dep": "^1.0.0" } }));
const report = await guardTrackedOverwrite(
file,
JSON.stringify({ dependencies: {} }),
{ format: "json", fixHint: "edit the manifest" },
logger,
);
expect(report.wouldLose).toContain("dependencies.extra-dep");
expect(logger.warn).toHaveBeenCalledOnce();
expect(logger.warn.mock.calls[0]![0]).toContain("DRIFT-GUARD-01");
expect(logger.warn.mock.calls[0]![0]).toContain("dependencies.extra-dep");
expect(logger.warn.mock.calls[0]![0]).toContain("edit the manifest");
});
it("detects changed scalar values as lost edits", async () => {
const file = join(dir, "cfg.json");
writeFileSync(file, JSON.stringify({ version: "2.0.0" }));
const report = await guardTrackedOverwrite(
file,
JSON.stringify({ version: "1.0.0" }),
{ format: "json" },
logger,
);
expect(report.wouldLose).toContain("version");
});
it("warns for YAML drift", async () => {
const file = join(dir, "config.yaml");
writeFileSync(file, "keep: yes\ndrop: gone\n");
const report = await guardTrackedOverwrite(file, "keep: yes\n", { format: "yaml" }, logger);
expect(report.wouldLose).toContain("drop");
expect(logger.warn).toHaveBeenCalledOnce();
});
it("warns for text drift listing removed lines", async () => {
const file = join(dir, "list.txt");
writeFileSync(file, "alpha\nbeta\ngamma\n");
const report = await guardTrackedOverwrite(file, "alpha\n", { format: "text" }, logger);
expect(report.wouldLose).toEqual(expect.arrayContaining(["beta", "gamma"]));
});
it("does not warn when new content only adds keys", async () => {
const file = join(dir, "grow.json");
writeFileSync(file, JSON.stringify({ a: 1 }));
const report = await guardTrackedOverwrite(
file,
JSON.stringify({ a: 1, b: 2 }),
{ format: "json" },
logger,
);
expect(report.wouldLose).toEqual([]);
expect(report.identical).toBe(false);
expect(logger.warn).not.toHaveBeenCalled();
});
it("throws under strict instead of warning", async () => {
const file = join(dir, "strict.json");
writeFileSync(file, JSON.stringify({ manual: true }));
await expect(
guardTrackedOverwrite(file, "{}", { format: "json", strict: true }, logger),
).rejects.toThrow("DRIFT-GUARD-01");
expect(logger.warn).not.toHaveBeenCalled();
});
it("falls back to line diff when existing JSON is unparseable", async () => {
const file = join(dir, "broken.json");
writeFileSync(file, "{ not json, custom-line\n");
const report = await guardTrackedOverwrite(file, "{}", { format: "json" }, logger);
expect(report.wouldLose.length).toBeGreaterThan(0);
});
});