gongxiangp2/lib/__tests__/zip-entry-reader.test.ts

123 lines
4.4 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 AdmZip from 'adm-zip'
import { mkdtemp, readFile, readdir, rm } from 'fs/promises'
import { tmpdir } from 'os'
import path from 'path'
import {
createTaskDetailEditWorkspace,
extractZipEntriesToDirectory,
listZipEntries,
} from '@/lib/zip-entry-reader'
jest.mock('@/lib/upload-paths', () => ({
getUploadRootDir: jest.fn(() => tempRoot),
resolveStoredUploadPath: jest.fn((stored: string) => stored),
}))
let tempRoot: string
beforeEach(async () => {
tempRoot = await mkdtemp(path.join(tmpdir(), 'zip-reader-test-'))
})
afterEach(async () => {
await rm(tempRoot, { recursive: true, force: true })
})
async function writeFixtureZip(name: string, entries: Array<{ name: string; data: Buffer | string; store?: boolean }>) {
const zipPath = path.join(tempRoot, name)
if (entries.some((entry) => entry.store)) {
// store 条目走 archiver,覆盖 method 0 分支
const archiver = (await import('archiver')).default
const { createWriteStream } = await import('fs')
const output = createWriteStream(zipPath)
const archive = archiver('zip', { zlib: { level: 0 } })
const done = new Promise<void>((resolve, reject) => {
output.on('close', () => resolve())
output.on('error', reject)
archive.on('error', reject)
})
archive.pipe(output)
for (const entry of entries) {
const filePath = path.join(tempRoot, `src-${entry.name}`)
await (await import('fs/promises')).writeFile(filePath, entry.data)
archive.file(filePath, { name: entry.name, ...(entry.store ? { store: true } : {}) })
}
await archive.finalize()
await done
return zipPath
}
const zip = new AdmZip()
for (const entry of entries) {
zip.addFile(entry.name, entry.data as Buffer)
}
zip.writeZip(zipPath)
return zipPath
}
describe('zip-entry-reader', () => {
test('按中央目录顺序列出条目并解压内容一致(deflate)', async () => {
const zipPath = await writeFixtureZip('deflate.zip', [
{ name: 'b-second.jpg', data: Buffer.from('second-image-bytes') },
{ name: 'a-first.jpg', data: Buffer.alloc(64 * 1024, 7) },
{ name: '中文图.png', data: Buffer.from([1, 2, 3, 4, 5]) },
])
const entries = await listZipEntries(zipPath)
// adm-zip 写包时的目录顺序与插入顺序不保证一致,这里只校验完整性;
// 顺序一致性由 list 与 extract 共用同一中央目录保证。
expect([...entries.map((entry) => entry.name)].sort()).toEqual(
['a-first.jpg', 'b-second.jpg', '中文图.png'].sort(),
)
const destination = path.join(tempRoot, 'out-deflate')
const extracted = await extractZipEntriesToDirectory(zipPath, destination)
expect(extracted.length).toBe(3)
// 列表与解压保持同一顺序
expect(extracted.map((item) => item.entryName)).toEqual(entries.map((entry) => entry.name))
const byName = new Map(extracted.map((item) => [item.entryName, item]))
const second = await readFile(byName.get('b-second.jpg')!.filePath)
expect(second.toString()).toBe('second-image-bytes')
const big = await readFile(byName.get('a-first.jpg')!.filePath)
expect(big.length).toBe(64 * 1024)
expect(big[0]).toBe(7)
const chinese = await readFile(byName.get('中文图.png')!.filePath)
expect(Array.from(chinese)).toEqual([1, 2, 3, 4, 5])
})
test('store 条目按原始字节拷贝', async () => {
const payload = Buffer.alloc(32 * 1024, 9)
const zipPath = await writeFixtureZip('store.zip', [
{ name: 'raw.bin', data: payload, store: true },
])
const destination = path.join(tempRoot, 'out-store')
const [extracted] = await extractZipEntriesToDirectory(zipPath, destination)
expect(extracted.entryName).toBe('raw.bin')
expect((await readFile(extracted.filePath)).equals(payload)).toBe(true)
})
test('空压缩包返回空列表', async () => {
const zipPath = await writeFixtureZip('empty.zip', [])
expect(await listZipEntries(zipPath)).toEqual([])
})
test('非 ZIP 文件报错', async () => {
const notZip = path.join(tempRoot, 'not.zip')
await (await import('fs/promises')).writeFile(notZip, Buffer.from('plain text, not a zip'))
await expect(listZipEntries(notZip)).rejects.toThrow('ZIP')
})
test('编辑工作目录创建后可清理', async () => {
const workspace = await createTaskDetailEditWorkspace()
await workspace.cleanup()
await expect(readdir(workspace.dir)).rejects.toBeDefined()
})
})