import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, writeFile, readFile, rm, access } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createConverter, runProcess } from './native-office.mjs'; import { patchOffice, target } from './install-office-preview.mjs'; test('patch is exact, version gated and refuses repeat application', () => { const patched = patchOffice(target, '0.2.0-rc.2', '0.2.0-rc.2'); assert.match(patched, /dsh-native-office/); for (const source of ['', target + target, patched]) assert.throws(() => patchOffice(source, '0.2.0-rc.2', '0.2.0-rc.2')); assert.throws(() => patchOffice(target, 'next', '0.2.0-rc.2')); assert.throws(() => patchOffice(target, '0.2.0-rc.2', 'next')); }); test('process cancellation, missing binary and failure are classified', async () => { const signal = new AbortController().signal; await assert.rejects(runProcess('/not-an-office-binary', [], signal), { code: 'unavailable' }); await assert.rejects(runProcess(process.execPath, ['-e', 'process.exit(2)'], signal), { code: 'invalid-document' }); const controller = new AbortController(); const reason = new Error('cancel test'); const pending = runProcess(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], controller.signal); controller.abort(reason); await assert.rejects(pending, error => error === reason); }); test('input bounds, corruption, exclusive output, abort and disposal', async () => { const directory = await mkdtemp(join(tmpdir(), 'office-test-')); const inputPath = join(directory, 'source.docx'); const outputPath = join(directory, 'output.pdf'); const converter = await createConverter(); try { await writeFile(inputPath, 'invalid'); await assert.rejects(converter.render({ inputPath, outputPath }), { code: 'invalid-document' }); await assert.rejects(access(outputPath)); const small = await createConverter({ maxInputBytes: 1 }); await assert.rejects(small.render({ inputPath, outputPath }), { code: 'input-too-large' }); await small.dispose(); await writeFile(outputPath, 'preserve'); await assert.rejects(converter.render({ inputPath, outputPath }), { code: 'EEXIST' }); assert.equal(await readFile(outputPath, 'utf8'), 'preserve'); const controller = new AbortController(); controller.abort(new Error('cancelled')); await assert.rejects(converter.render({ inputPath, outputPath }, controller.signal), /cancelled/); await converter.dispose(); await assert.rejects(converter.render({ inputPath, outputPath }), { code: 'unavailable' }); await assert.rejects(createConverter({ fontDirectories: [] }), { code: 'unavailable' }); } finally { await converter.dispose(); await rm(directory, { recursive: true, force: true }); } });