gongxiangp2/lib/__tests__/material-upload-consumption-journal.test.ts
chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

117 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @jest-environment node */
import { mkdtemp, mkdir, readFile, rename, rm, writeFile } from 'fs/promises'
import os from 'os'
import path from 'path'
import {
recoverMaterialUploadConsumptionJournal,
removeMaterialUploadConsumptionJournal,
writeMaterialUploadConsumptionJournal,
} from '@/lib/material-upload-consumption-journal'
describe('普通素材上传消费 journal', () => {
let uploadRoot = ''
const previousUploadDir = process.env.UPLOAD_DIR
beforeEach(async () => {
uploadRoot = await mkdtemp(path.join(os.tmpdir(), 'material-upload-journal-'))
process.env.UPLOAD_DIR = uploadRoot
})
afterEach(async () => {
if (previousUploadDir === undefined) {
delete process.env.UPLOAD_DIR
} else {
process.env.UPLOAD_DIR = previousUploadDir
}
await rm(uploadRoot, { recursive: true, force: true })
})
test('rename 后模拟进程退出,重启读取 journal 能找回同一正式源', async () => {
const uploadId = 'upload-crash-recovery'
const sessionDirectory = path.join(uploadRoot, 'temp', uploadId)
const mergedPath = path.join(sessionDirectory, 'merged-source.mp4')
const finalPath = path.join(uploadRoot, 'video-crash-recovery.mp4')
await mkdir(sessionDirectory, { recursive: true })
await writeFile(mergedPath, Buffer.from('video-source'))
const record = await writeMaterialUploadConsumptionJournal({
uploadId,
ownerId: 'user-1',
mergedPath,
finalPath,
storedFinalPath: finalPath,
fileName: 'video-crash-recovery.mp4',
size: Buffer.byteLength('video-source'),
})
// 故障注入点:rename 已成功,但还没有任何 Material 数据库记录。
await rename(mergedPath, finalPath)
const recovered = await recoverMaterialUploadConsumptionJournal({
uploadId,
ownerId: 'user-1',
})
expect(recovered).toEqual({
record,
sourcePath: finalPath,
sourceLocation: 'final',
})
expect(await readFile(recovered!.sourcePath, 'utf8')).toBe('video-source')
})
test('owner 不匹配时不能发现或消费其他用户的恢复记录', async () => {
const uploadId = 'upload-owner-scope'
const sessionDirectory = path.join(uploadRoot, 'temp', uploadId)
const mergedPath = path.join(sessionDirectory, 'merged-source.zip')
const finalPath = path.join(uploadRoot, 'material-owner-scope.zip')
await mkdir(sessionDirectory, { recursive: true })
await writeFile(mergedPath, Buffer.from('archive'))
await writeMaterialUploadConsumptionJournal({
uploadId,
ownerId: 'user-1',
mergedPath,
finalPath,
storedFinalPath: finalPath,
fileName: 'material-owner-scope.zip',
size: Buffer.byteLength('archive'),
})
await expect(recoverMaterialUploadConsumptionJournal({
uploadId,
ownerId: 'user-2',
})).resolves.toBeNull()
})
test('正式源大小变化时拒绝恢复并保留 journal 供人工核验', async () => {
const uploadId = 'upload-size-mismatch'
const sessionDirectory = path.join(uploadRoot, 'temp', uploadId)
const mergedPath = path.join(sessionDirectory, 'merged-source.zip')
const finalPath = path.join(uploadRoot, 'material-size-mismatch.zip')
await mkdir(sessionDirectory, { recursive: true })
await writeFile(mergedPath, Buffer.from('archive'))
await writeMaterialUploadConsumptionJournal({
uploadId,
ownerId: 'user-1',
mergedPath,
finalPath,
storedFinalPath: finalPath,
fileName: 'material-size-mismatch.zip',
size: Buffer.byteLength('archive'),
})
await rename(mergedPath, finalPath)
await writeFile(finalPath, Buffer.from('changed-size'))
await expect(recoverMaterialUploadConsumptionJournal({
uploadId,
ownerId: 'user-1',
})).rejects.toThrow('上传源文件恢复记录异常')
await expect(removeMaterialUploadConsumptionJournal({
uploadId,
ownerId: 'user-1',
expectedFinalPath: finalPath,
})).resolves.toBe(true)
})
})