import { lstat, mkdir, mkdtemp, readFile, readdir, stat, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { describe, expect, it } from 'vitest' import { writeFileAtomic } from '../src/index.ts' async function scratch(): Promise { return mkdtemp(join(tmpdir(), 'dsh-atomic-write-')) } describe('writeFileAtomic', () => { it('creates the file and its parents with exactly the stated mode', async () => { const dir = await scratch() const target = join(dir, 'nested', 'deep', 'doc.yaml') await writeFileAtomic(target, 'a: 1\n', { mode: 0o600 }) expect(await readFile(target, 'utf8')).toBe('a: 1\n') expect((await stat(target)).mode & 0o777).toBe(0o600) }) it('replaces existing content and narrows a wider-permission file to the stated mode', async () => { const dir = await scratch() const target = join(dir, 'doc.yaml') await writeFile(target, 'old', { mode: 0o644 }) await writeFileAtomic(target, 'new', { mode: 0o600 }) expect(await readFile(target, 'utf8')).toBe('new') expect((await stat(target)).mode & 0o777).toBe(0o600) }) it('replaces a symlinked target itself without writing through to the referent', async () => { const dir = await scratch() const victim = join(dir, 'victim') await writeFile(victim, 'victim-content') const target = join(dir, 'doc.yaml') await symlink(victim, target) await writeFileAtomic(target, 'replaced', { mode: 0o600 }) expect((await lstat(target)).isSymbolicLink()).toBe(false) expect(await readFile(target, 'utf8')).toBe('replaced') expect(await readFile(victim, 'utf8')).toBe('victim-content') }) it('leaves no temp sibling and rethrows when the rename fails', async () => { const dir = await scratch() const target = join(dir, 'occupied') await mkdir(target) await expect(writeFileAtomic(target, 'content', { mode: 0o600 })).rejects.toThrow() expect((await readdir(dir)).filter(entry => entry.includes('.tmp'))).toEqual([]) }) })