document-conversion.e2e.ts 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. /** The shipped shared provider converts Office bytes without presentation plugins. */
  2. import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { fileURLToPath, pathToFileURL } from 'node:url'
  6. import { Context } from '@deepseek-ai/cordis'
  7. import Loader from '@deepseek-ai/cordis-plugin-loader'
  8. import Include, { applyEntryPatches } from '@deepseek-ai/cordis-plugin-include'
  9. import { loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
  10. import { OfficeSourceKey, type OfficeToPdfRequest } from '@deepseek-ai/dsh-office-to-pdf'
  11. import * as LibreOfficeProvider from '@deepseek-ai/dsh-office-to-pdf'
  12. import { expect, it, onTestFinished } from 'vitest'
  13. it('loads one shared conversion row and retains caller-owned PDFs after disposal', async () => {
  14. const directory = await mkdtemp(join(tmpdir(), 'dsh-conversion-loader-'))
  15. const ctx = new Context()
  16. onTestFinished(async () => {
  17. try { await ctx.fiber.dispose() }
  18. finally { await rm(directory, { recursive: true, force: true }) }
  19. })
  20. const rows = loadOverlayPatches('conversion-test', fileURLToPath(new URL('../cordis.patch.yml', import.meta.url)))
  21. .flatMap(patch => patch.insert ?? [])
  22. .filter(row => row.name === '@deepseek-ai/dsh-office-to-pdf')
  23. expect(rows.map(row => row.id)).toEqual(['office-to-pdf'])
  24. const configured = applyEntryPatches(rows, [{ id: 'office-to-pdf', config: { maxConcurrentConversions: 1 } }],
  25. (message) => { throw new Error(message) })
  26. const configPath = join(directory, 'cordis.yml')
  27. await writeFile(configPath, JSON.stringify(configured))
  28. ctx.baseUrl = pathToFileURL(directory).href + '/'
  29. await ctx.plugin(Loader)
  30. ctx.loader.builtins.include = Include
  31. // The Loader resolves the same source-plane module namespace as the test.
  32. ctx.loader.internal = {
  33. version: 'v2',
  34. async import(specifier: string) {
  35. if (specifier !== '@deepseek-ai/dsh-office-to-pdf') throw new Error(`Unexpected plugin: ${specifier}`)
  36. return LibreOfficeProvider
  37. },
  38. } as unknown as NonNullable<typeof ctx.loader.internal>
  39. await ctx.loader.create({ name: 'cordis:include', config: { path: pathToFileURL(configPath).href } })
  40. await ctx.loader.await()
  41. const entry = [...ctx.loader.entries()].find(candidate => candidate.options.id === 'office-to-pdf')!
  42. await entry.fiber!.await()
  43. expect(ctx.get('workspaceFiles')).toBeUndefined()
  44. expect(ctx.get('skills')).toBeUndefined()
  45. const bytes = await readFile(new URL('./fixtures/document-conversion.docx', import.meta.url))
  46. let reads = 0
  47. const request = (key: string): OfficeToPdfRequest => ({
  48. extension: 'docx', priority: 'foreground',
  49. source: { key: OfficeSourceKey(key), version: 'fixture', bytes: bytes.length,
  50. async read(signal, maxBytes) {
  51. signal.throwIfAborted()
  52. expect(bytes.length).toBeLessThanOrEqual(maxBytes)
  53. reads++
  54. return { bytes: Uint8Array.from(bytes), version: 'fixture' }
  55. },
  56. },
  57. })
  58. const provider = ctx.officeToPdf
  59. const [first, second] = await Promise.all([provider.convert(request('first')), provider.convert(request('second'))])
  60. expect(reads).toBe(2)
  61. expect(Buffer.from(first.pdf).subarray(0, 5).toString()).toBe('%PDF-')
  62. expect(first.cacheKey).toBe(second.cacheKey)
  63. expect(first.pdf).toEqual(second.pdf)
  64. expect(first.pdf).not.toBe(second.pdf)
  65. expect(first.missingFonts).not.toBe(second.missingFonts)
  66. first.pdf.fill(0)
  67. first.missingFonts.push('caller-owned')
  68. const cached = await provider.convert(request('first'))
  69. expect(reads).toBe(2)
  70. expect(cached.pdf).toEqual(second.pdf)
  71. expect(cached.missingFonts).toEqual(second.missingFonts)
  72. await ctx.fiber.dispose()
  73. expect(Buffer.from(cached.pdf).subarray(0, 5).toString()).toBe('%PDF-')
  74. await expect(provider.convert(request('first'))).rejects.toMatchObject({ code: 'unavailable' })
  75. })