import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import net from "node:net"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { captureDirectorySnapshot, mergeDirectoryWithBaseline } from "./workspace-restore-merge.js"; describe("workspace restore merge", () => { const cleanupDirs: string[] = []; afterEach(async () => { while (cleanupDirs.length > 0) { const dir = cleanupDirs.pop(); if (!dir) continue; await rm(dir, { recursive: true, force: true }).catch(() => undefined); } }); it("preserves sibling files when sequential stale-baseline restores create the same nested directory tree", async () => { const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-restore-merge-")); cleanupDirs.push(rootDir); const targetDir = path.join(rootDir, "target"); const sourceADir = path.join(rootDir, "source-a"); const sourceBDir = path.join(rootDir, "source-b"); await mkdir(targetDir, { recursive: true }); await mkdir(path.join(sourceADir, "manual-qa", "environment-matrix", "ssh"), { recursive: true }); await mkdir(path.join(sourceBDir, "manual-qa", "environment-matrix", "ssh"), { recursive: true }); const baseline = await captureDirectorySnapshot(targetDir, { exclude: [] }); await writeFile( path.join(sourceADir, "manual-qa", "environment-matrix", "ssh", "claude_local.md"), "ssh claude\n", "utf8", ); await writeFile( path.join(sourceBDir, "manual-qa", "environment-matrix", "ssh", "codex_local.md"), "ssh codex\n", "utf8", ); await mergeDirectoryWithBaseline({ baseline, sourceDir: sourceADir, targetDir, }); await mergeDirectoryWithBaseline({ baseline, sourceDir: sourceBDir, targetDir, }); await expect( readFile(path.join(targetDir, "manual-qa", "environment-matrix", "ssh", "claude_local.md"), "utf8"), ).resolves.toBe("ssh claude\n"); await expect( readFile(path.join(targetDir, "manual-qa", "environment-matrix", "ssh", "codex_local.md"), "utf8"), ).resolves.toBe("ssh codex\n"); }); it("ignores non-file entries when capturing snapshots", async () => { if (process.platform === "win32") return; const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-restore-merge-")); cleanupDirs.push(rootDir); const socketPath = path.join(rootDir, "runtime.sock"); const server = net.createServer(); try { await new Promise((resolve, reject) => { server.once("error", reject); server.listen(socketPath, resolve); }); const snapshot = await captureDirectorySnapshot(rootDir, { exclude: [] }); expect(snapshot.entries.has("runtime.sock")).toBe(false); } finally { await new Promise((resolve) => server.close(() => resolve())); } }); });