| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- import { test } from 'node:test'
- import assert from 'node:assert/strict'
- import os from 'node:os'
- import path from 'node:path'
- import { promises as fs } from 'node:fs'
- import { writeAtomicBatch } from '../../src/storage/atomic.js'
- test('writeAtomicBatch:后续文件失败时,已替换文件恢复原样', async () => {
- const root = await fs.mkdtemp(path.join(os.tmpdir(), 'wnw-atomic-'))
- try {
- await fs.writeFile(path.join(root, 'a.txt'), 'old', 'utf8')
- await fs.mkdir(path.join(root, 'b.txt'))
- await assert.rejects(() =>
- writeAtomicBatch(root, [
- { path: 'a.txt', content: 'new' },
- { path: 'b.txt', content: 'bad' },
- ])
- )
- assert.equal(await fs.readFile(path.join(root, 'a.txt'), 'utf8'), 'old')
- const bStat = await fs.stat(path.join(root, 'b.txt'))
- assert.equal(bStat.isDirectory(), true, '既有目录不应被批量写删除')
- } finally {
- await fs.rm(root, { recursive: true, force: true })
- }
- })
- // R12 故障注入:atomic.js 与本测试共享 node:fs 的 promises 单例,patch 即生效。
- test('writeAtomicBatch:写 tmp 就失败的文件,其原文绝不能被回滚删除(R12)', async () => {
- const root = await fs.mkdtemp(path.join(os.tmpdir(), 'wnw-atomic-'))
- const origWriteFile = fs.writeFile
- try {
- await origWriteFile.call(fs, path.join(root, 'a.txt'), 'oldA', 'utf8')
- await origWriteFile.call(fs, path.join(root, 'b.txt'), 'oldB', 'utf8')
- fs.writeFile = async (p, ...rest) => {
- if (String(p).includes('b.txt') && String(p).includes('.wnwtmp')) {
- const err = new Error('注入:EPERM 模拟(文件被占用/路径超长)')
- err.code = 'EPERM'
- throw err
- }
- return origWriteFile.call(fs, p, ...rest)
- }
- await assert.rejects(() =>
- writeAtomicBatch(root, [
- { path: 'a.txt', content: 'newA' },
- { path: 'b.txt', content: 'newB' },
- ])
- )
- assert.equal(await fs.readFile(path.join(root, 'a.txt'), 'utf8'), 'oldA', 'A 应从备份恢复')
- assert.equal(await fs.readFile(path.join(root, 'b.txt'), 'utf8'), 'oldB', 'B 从未动过,原文必须还在')
- } finally {
- fs.writeFile = origWriteFile
- await fs.rm(root, { recursive: true, force: true })
- }
- })
- test('writeAtomicBatch:备份 rename 失败的文件,其原文绝不能被回滚删除(R12)', async () => {
- const root = await fs.mkdtemp(path.join(os.tmpdir(), 'wnw-atomic-'))
- const origRename = fs.rename
- try {
- await fs.writeFile(path.join(root, 'a.txt'), 'oldA', 'utf8')
- await fs.writeFile(path.join(root, 'b.txt'), 'oldB', 'utf8')
- fs.rename = async (from, to, ...rest) => {
- if (String(from).endsWith('b.txt') && String(to).includes('.wnwbackup')) {
- const err = new Error('注入:EPERM 模拟(原文件被编辑器占用)')
- err.code = 'EPERM'
- throw err
- }
- return origRename.call(fs, from, to, ...rest)
- }
- await assert.rejects(() =>
- writeAtomicBatch(root, [
- { path: 'a.txt', content: 'newA' },
- { path: 'b.txt', content: 'newB' },
- ])
- )
- assert.equal(await fs.readFile(path.join(root, 'a.txt'), 'utf8'), 'oldA', 'A 应从备份恢复')
- assert.equal(await fs.readFile(path.join(root, 'b.txt'), 'utf8'), 'oldB', 'B 原文必须还在')
- const leftovers = (await fs.readdir(root)).filter((f) => f.includes('.wnwtmp') || f.includes('.wnwbackup'))
- assert.deepEqual(leftovers, [], '不应残留 tmp/backup 文件')
- } finally {
- fs.rename = origRename
- await fs.rm(root, { recursive: true, force: true })
- }
- })
|