import { describe, expect, test } from 'bun:test'; import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { writeAtomic } from './write-atomic'; /* The property: the final name never holds a partial file. A test cannot observe the instant between two syscalls, so what is pinned here is the shape that makes the property true — the data goes to `.part` first, the final name only ever appears complete, and a failure leaves no fragment under either name. */ const dir = () => mkdtempSync(join(tmpdir(), 'zz-atomic-')); describe('writeAtomic', () => { test('the file ends up at the final name, complete', async () => { const d = dir(); try { const path = join(d, 'memo.md'); await writeAtomic(path, 'hello'); expect(readFileSync(path, 'utf8')).toBe('hello'); expect(existsSync(`${path}.part`)).toBe(false); } finally { rmSync(d, { recursive: true, force: true }); } }); test('it overwrites an existing file rather than refusing', async () => { const d = dir(); try { const path = join(d, 'memo.md'); writeFileSync(path, 'old'); await writeAtomic(path, 'new'); expect(readFileSync(path, 'utf8')).toBe('new'); } finally { rmSync(d, { recursive: true, force: true }); } }); // A failed write must not leave a `.part` behind — only an interrupted PROCESS should, which is the // case worth being able to see in the folder. test('a failed write leaves nothing under either name', async () => { const d = dir(); try { // The parent is a FILE, so neither the .part write nor the rename can succeed. writeFileSync(join(d, 'blocker'), 'x'); const path = join(d, 'blocker', 'memo.md'); await expect(writeAtomic(path, 'x')).rejects.toThrow(); expect(existsSync(`${path}.part`)).toBe(false); expect(existsSync(path)).toBe(false); } finally { rmSync(d, { recursive: true, force: true }); } }); // Bun.write creates the parent chain itself, which is why this takes no mkdir option and why the // callers it replaced could drop theirs. test('a missing directory is created, not an error', async () => { const d = dir(); try { const path = join(d, 'nested', 'deep', 'memo.md'); await writeAtomic(path, 'x'); expect(readFileSync(path, 'utf8')).toBe('x'); } finally { rmSync(d, { recursive: true, force: true }); } }); test('a Blob writes the same way as a string', async () => { const d = dir(); try { const path = join(d, 'blob.bin'); await writeAtomic(path, new Blob([new Uint8Array([1, 2, 3])])); expect([...readFileSync(path)]).toEqual([1, 2, 3]); } finally { rmSync(d, { recursive: true, force: true }); } }); });