Files
deepseek-harness/packages/bundle/web-app/tests/document-conversion.e2e.ts
T
yudshj 611e42b9a5 fix(office): 输出清理限定在自有目录,缓存命中前确认读权限
- office-kit:启动 sweep 只处理 session-<hash>/<uuid> 格式的真实目录,不跟随 symlink;删除成功后才释放记账,失败保留并重试,dispose 等待全部删除;容量腾不出时 save 明确失败
- office-kit / web-app 测试:显式使用临时 outputDirectory,不再清理真实 $DSH_HOME
- tool-office:交给 kit 前读 1 字节并重核 version,缓存命中也受当次读权限约束
- 文档:升级指南与 README 写明 render 不再保存图片文件、原生引擎在 Host 内运行;skill-office 链接 #7677;清理 Office-to-PDF 旧描述并重新生成 catalog
- 客户端:renderImages 改用 useCallback;e2e 补 assertNever,依赖按字母序
2026-11-04 18:17:58 +08:00

82 lines
4.0 KiB
TypeScript

/** The shipped shared provider converts Office bytes without presentation plugins. */
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { Context } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import Include, { applyEntryPatches } from '@deepseek-ai/cordis-plugin-include'
import { loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { OfficeSourceKey, type OfficeKitRequest } from '@deepseek-ai/dsh-office-kit'
import * as LibreOfficeProvider from '@deepseek-ai/dsh-office-kit'
import { expect, it, onTestFinished } from 'vitest'
it('loads one shared conversion row and retains caller-owned PDFs after disposal', async () => {
const directory = await mkdtemp(join(tmpdir(), 'dsh-conversion-loader-'))
const outputDirectory = await mkdtemp(join(tmpdir(), 'dsh-conversion-outputs-'))
const ctx = new Context()
onTestFinished(async () => {
try { await ctx.fiber.dispose() }
finally {
await rm(directory, { recursive: true, force: true })
await rm(outputDirectory, { recursive: true, force: true })
}
})
const rows = loadOverlayPatches('conversion-test', fileURLToPath(new URL('../cordis.patch.yml', import.meta.url)))
.flatMap(patch => patch.insert ?? [])
.filter(row => row.name === '@deepseek-ai/dsh-office-kit')
expect(rows.map(row => row.id)).toEqual(['office-kit'])
const configured = applyEntryPatches(rows, [{ id: 'office-kit', config: { maxConcurrentConversions: 1, outputDirectory } }],
(message) => { throw new Error(message) })
const configPath = join(directory, 'cordis.yml')
await writeFile(configPath, JSON.stringify(configured))
ctx.baseUrl = pathToFileURL(directory).href + '/'
await ctx.plugin(Loader)
ctx.loader.builtins.include = Include
// The Loader resolves the same source-plane module namespace as the test.
ctx.loader.internal = {
version: 'v2',
async import(specifier: string) {
if (specifier !== '@deepseek-ai/dsh-office-kit') throw new Error(`Unexpected plugin: ${specifier}`)
return LibreOfficeProvider
},
} as unknown as NonNullable<typeof ctx.loader.internal>
await ctx.loader.create({ name: 'cordis:include', config: { path: pathToFileURL(configPath).href } })
await ctx.loader.await()
const entry = [...ctx.loader.entries()].find(candidate => candidate.options.id === 'office-kit')!
await entry.fiber!.await()
expect(ctx.get('workspaceFiles')).toBeUndefined()
expect(ctx.get('skills')).toBeUndefined()
const bytes = await readFile(new URL('./fixtures/document-conversion.docx', import.meta.url))
let reads = 0
const request = (key: string): OfficeKitRequest => ({
extension: 'docx', priority: 'foreground',
source: { key: OfficeSourceKey(key), version: 'fixture', bytes: bytes.length,
async read(signal, maxBytes) {
signal.throwIfAborted()
expect(bytes.length).toBeLessThanOrEqual(maxBytes)
reads++
return { bytes: Uint8Array.from(bytes), version: 'fixture' }
},
},
})
const provider = ctx.officeKit
expect(provider.outputDirectory).toBe(outputDirectory)
const [first, second] = await Promise.all([provider.convert(request('first')), provider.convert(request('second'))])
expect(reads).toBe(2)
expect(Buffer.from(first.pdf).subarray(0, 5).toString()).toBe('%PDF-')
expect(first.cacheKey).toBe(second.cacheKey)
expect(first.pdf).toEqual(second.pdf)
expect(first.pdf).not.toBe(second.pdf)
expect(first.missingFonts).not.toBe(second.missingFonts)
first.pdf.fill(0)
first.missingFonts.push('caller-owned')
const cached = await provider.convert(request('first'))
expect(reads).toBe(2)
expect(cached.pdf).toEqual(second.pdf)
expect(cached.missingFonts).toEqual(second.missingFonts)
await ctx.fiber.dispose()
expect(Buffer.from(cached.pdf).subarray(0, 5).toString()).toBe('%PDF-')
await expect(provider.convert(request('first'))).rejects.toMatchObject({ code: 'unavailable' })
})