Skip to content

Commit c3c4e95

Browse files
committed
test: cover atomicWriteFileAsync symlink preservation
The symlink regression tests only exercised the synchronous atomicWriteFile, so an async-only regression could have slipped through while the sync suite stayed green. Mirror all four cases against atomicWriteFileAsync: an existing symlink survives and its target receives the write, no temp file is left beside either the link or its target, a plain destination is unaffected, and a dangling symlink is replaced rather than followed into nothing. Verified by reverting the async resolveWriteTarget call in isolation -- the symlink case fails, and passes again once restored.
1 parent a391b3c commit c3c4e95

1 file changed

Lines changed: 51 additions & 1 deletion

File tree

tests/config.test.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
} from "../src/config";
2828

2929
import * as windowsAcl from "../src/lib/windows-secret-acl";
30-
import { AtomicWriteResidualTempError, atomicWriteFile, hardenConfigDir, hardenExistingSecret, renameAtomicFile, saveConfig } from "../src/config";
30+
import { AtomicWriteResidualTempError, atomicWriteFile, atomicWriteFileAsync, hardenConfigDir, hardenExistingSecret, renameAtomicFile, saveConfig } from "../src/config";
3131
let testDir = "";
3232

3333
beforeEach(() => {
@@ -1729,3 +1729,53 @@ describe("config.ts – atomic writes preserve symlinked destinations", () => {
17291729
expect(readFileSync(link, "utf8")).toBe("recovered");
17301730
});
17311731
});
1732+
1733+
describe("config.ts – async atomic writes preserve symlinked destinations", () => {
1734+
test("a symlinked destination survives the write and the real file receives it", async () => {
1735+
const repoDir = join(testDir, "dotfiles-async");
1736+
mkdirSync(repoDir, { recursive: true });
1737+
const realFile = join(repoDir, "config.toml");
1738+
writeFileSync(realFile, "original", "utf-8");
1739+
const link = join(testDir, "config-async.toml");
1740+
symlinkSync(realFile, link);
1741+
1742+
await atomicWriteFileAsync(link, "rewritten");
1743+
1744+
expect(lstatSync(link).isSymbolicLink()).toBe(true);
1745+
expect(readlinkSync(link)).toBe(realFile);
1746+
expect(readFileSync(realFile, "utf8")).toBe("rewritten");
1747+
expect(readFileSync(link, "utf8")).toBe("rewritten");
1748+
});
1749+
1750+
test("no temp file is left beside the link or its target", async () => {
1751+
const repoDir = join(testDir, "dotfiles-async-clean");
1752+
mkdirSync(repoDir, { recursive: true });
1753+
const realFile = join(repoDir, "config.toml");
1754+
writeFileSync(realFile, "original", "utf-8");
1755+
const link = join(testDir, "config-async-clean.toml");
1756+
symlinkSync(realFile, link);
1757+
1758+
await atomicWriteFileAsync(link, "rewritten");
1759+
1760+
expect(readdirSync(repoDir).filter(name => name.includes(".ocx."))).toEqual([]);
1761+
expect(readdirSync(testDir).filter(name => name.includes(".ocx."))).toEqual([]);
1762+
});
1763+
1764+
test("a plain destination is unaffected", async () => {
1765+
const destination = join(testDir, "plain-async.toml");
1766+
await atomicWriteFileAsync(destination, "first");
1767+
await atomicWriteFileAsync(destination, "second");
1768+
1769+
expect(lstatSync(destination).isSymbolicLink()).toBe(false);
1770+
expect(readFileSync(destination, "utf8")).toBe("second");
1771+
});
1772+
1773+
test("a dangling symlink is replaced rather than followed into nothing", async () => {
1774+
const link = join(testDir, "dangling-async.toml");
1775+
symlinkSync(join(testDir, "gone-async", "config.toml"), link);
1776+
1777+
await atomicWriteFileAsync(link, "recovered");
1778+
1779+
expect(readFileSync(link, "utf8")).toBe("recovered");
1780+
});
1781+
});

0 commit comments

Comments
 (0)