Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
/** @jest-environment node */
|
||
|
||
import { mkdir, mkdtemp, rm, symlink, writeFile } from 'fs/promises'
|
||
import os from 'os'
|
||
import path from 'path'
|
||
|
||
import {
|
||
readBoundedRegularFile,
|
||
readBoundedResponseBuffer,
|
||
} from '@/lib/bounded-file-reader'
|
||
|
||
describe('bounded-file-reader', () => {
|
||
let root: string
|
||
|
||
beforeEach(async () => {
|
||
root = await mkdtemp(path.join(os.tmpdir(), 'bounded-file-reader-'))
|
||
})
|
||
|
||
afterEach(async () => {
|
||
await rm(root, { recursive: true, force: true })
|
||
})
|
||
|
||
test('在分配 Buffer 前拒绝超限、申报不一致和符号链接文件', async () => {
|
||
const filePath = path.join(root, 'source.bin')
|
||
await writeFile(filePath, Buffer.from('12345'))
|
||
|
||
await expect(readBoundedRegularFile(filePath, {
|
||
maxBytes: 4,
|
||
label: '测试文件',
|
||
})).rejects.toThrow('超过 1MB 限制')
|
||
|
||
await expect(readBoundedRegularFile(filePath, {
|
||
maxBytes: 10,
|
||
expectedBytes: 4,
|
||
label: '测试文件',
|
||
})).rejects.toThrow('实际大小与上传记录不一致')
|
||
|
||
const symlinkTarget = path.join(root, 'source-link-target')
|
||
const symlinkPath = path.join(root, 'source-link')
|
||
await mkdir(symlinkTarget)
|
||
await symlink(symlinkTarget, symlinkPath, process.platform === 'win32' ? 'junction' : 'dir')
|
||
await expect(readBoundedRegularFile(symlinkPath, {
|
||
maxBytes: 10,
|
||
expectedBytes: 5,
|
||
label: '测试文件',
|
||
})).rejects.toThrow('不是可读取的普通文件')
|
||
})
|
||
|
||
test('普通文件仅在实际大小与申报一致时返回完整内容', async () => {
|
||
const filePath = path.join(root, 'source.bin')
|
||
await writeFile(filePath, Buffer.from('bounded'))
|
||
|
||
await expect(readBoundedRegularFile(filePath, {
|
||
maxBytes: 64,
|
||
expectedBytes: 7,
|
||
label: '测试文件',
|
||
})).resolves.toEqual(Buffer.from('bounded'))
|
||
})
|
||
|
||
test('远程响应无 Content-Length 时仍按可信大小硬停,且不使用 arrayBuffer', async () => {
|
||
const stream = new ReadableStream<Uint8Array>({
|
||
start(controller) {
|
||
controller.enqueue(new Uint8Array([1, 2]))
|
||
controller.enqueue(new Uint8Array([3, 4]))
|
||
controller.close()
|
||
},
|
||
})
|
||
const response = new Response(stream)
|
||
|
||
await expect(readBoundedResponseBuffer(response, {
|
||
maxBytes: 4,
|
||
expectedBytes: 3,
|
||
label: '远程文件',
|
||
})).rejects.toThrow('响应超过申报大小')
|
||
})
|
||
|
||
test('远程响应先校验 Content-Length,并按申报大小返回单个 Buffer', async () => {
|
||
const response = new Response(Buffer.from('remote'), {
|
||
headers: { 'Content-Length': '6' },
|
||
})
|
||
|
||
await expect(readBoundedResponseBuffer(response, {
|
||
maxBytes: 10,
|
||
expectedBytes: 6,
|
||
label: '远程文件',
|
||
})).resolves.toEqual(Buffer.from('remote'))
|
||
|
||
const mismatched = new Response(Buffer.from('remote'), {
|
||
headers: { 'Content-Length': '6' },
|
||
})
|
||
await expect(readBoundedResponseBuffer(mismatched, {
|
||
maxBytes: 10,
|
||
expectedBytes: 5,
|
||
label: '远程文件',
|
||
})).rejects.toThrow('响应大小与上传记录不一致')
|
||
})
|
||
})
|