gongxiangp2/lib/__tests__/detail-generator-storage.test.ts

41 lines
2.3 KiB
TypeScript

/** @jest-environment node */
import { mkdtemp, rm } from 'fs/promises'
import os from 'os'
import path from 'path'
import { deleteDetailFile, getDetailStorageRoot, readDetailFile, saveDetailFile } from '@/lib/detail-generator/storage'
describe('detail generator private storage', () => {
let root: string
const oldStorage = process.env.DETAIL_GENERATOR_STORAGE_DIR
const oldUploads = process.env.UPLOAD_DIR
beforeEach(async () => {
root = await mkdtemp(path.join(os.tmpdir(), 'detail-storage-test-'))
process.env.DETAIL_GENERATOR_STORAGE_DIR = path.join(root, 'private')
process.env.UPLOAD_DIR = path.join(root, 'uploads')
})
afterEach(async () => {
// Recursive removal is restricted to this test's freshly created temporary root.
if (path.dirname(root) !== path.resolve(os.tmpdir()) || !path.basename(root).startsWith('detail-storage-test-')) throw new Error('Unsafe test cleanup')
await rm(root, { recursive: true, force: true })
if (oldStorage === undefined) delete process.env.DETAIL_GENERATOR_STORAGE_DIR; else process.env.DETAIL_GENERATOR_STORAGE_DIR = oldStorage
if (oldUploads === undefined) delete process.env.UPLOAD_DIR; else process.env.UPLOAD_DIR = oldUploads
})
it('stores files outside generic uploads and reads the exact bytes', async () => {
const key = await saveDetailFile('project-1', Buffer.from('private bytes'), 'png')
expect(key).toMatch(/^project-1\/[a-f0-9-]{36}\.png$/)
expect((await readDetailFile(key)).toString()).toBe('private bytes')
await deleteDetailFile(key)
await expect(readDetailFile(key)).rejects.toThrow()
})
it('rejects encoded, absolute and parent traversal keys', async () => {
for (const key of ['../private.png', '/etc/passwd', 'p/%2e%2e/secret.png', 'p\\secret.png', 'http://host/image.png']) await expect(readDetailFile(key)).rejects.toThrow('无效')
await expect(saveDetailFile('../project', Buffer.from('x'), 'png')).rejects.toThrow('无效')
})
it('rejects private roots inside either public serving directory', () => {
process.env.DETAIL_GENERATOR_STORAGE_DIR = path.join(root, 'uploads', 'secret')
expect(getDetailStorageRoot).toThrow('之外')
process.env.DETAIL_GENERATOR_STORAGE_DIR = path.join(process.cwd(), 'public', 'secret')
expect(getDetailStorageRoot).toThrow('之外')
})
})