import assert from 'node:assert/strict'; import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import test from 'node:test'; import { loadConfig } from '../src/config.js'; import { convertDocumentToPdf, convertDocumentToXlsx } from '../src/document-converter.js'; function testConfig(converterPath: string, fontConfigFile?: string) { return loadConfig({ NODE_ENV: 'test', FIELD_ENCRYPTION_KEY: Buffer.alloc(32, 21).toString('base64'), DOCUMENT_CONVERTER_PATH: converterPath, ...(fontConfigFile ? { DOCUMENT_FONTCONFIG_FILE: fontConfigFile } : {}) }); } test('PDF sources pass through with PDF metadata', async () => { const content = Buffer.from('%PDF-1.7\nsource'); const result = await convertDocumentToPdf(testConfig('/path/that/is/not/used'), { fileName: 'team.doc', contentType: 'application/msword', content, maxBytes: 10_000 }); assert.equal(result.fileName, 'team.pdf'); assert.equal(result.contentType, 'application/pdf'); assert.equal(result.converted, true); assert.equal(result.conversion_status, 'already_pdf'); assert.deepEqual(result.content, content); }); test('conversion failure falls back to the source file', async () => { const content = Buffer.from('not a valid office document'); const result = await convertDocumentToPdf(testConfig('/path/that/does/not/exist'), { fileName: 'team.doc', contentType: 'application/msword', content, maxBytes: 10_000 }); assert.equal(result.fileName, 'team.doc'); assert.equal(result.contentType, 'application/msword'); assert.equal(result.converted, false); assert.equal(result.conversion_status, 'source_fallback'); assert.equal(result.conversion_error_code, 'converter_unavailable'); assert.deepEqual(result.content, content); }); test('a valid converter output replaces the source with a PDF', { skip: process.platform === 'win32' ? 'Unix fake converter fixture is not executable through Windows execFile.' : false }, async () => { const temporaryDirectory = await mkdtemp(join(tmpdir(), 'ltjt-converter-test-')); const converterPath = join(temporaryDirectory, 'fake-soffice.sh'); const fontConfigFile = join(temporaryDirectory, 'fonts.conf'); await writeFile(fontConfigFile, ''); await writeFile(converterPath, [ '#!/bin/sh', 'outdir=""', 'convert_to=""', 'next_is_outdir=0', 'next_is_convert_to=0', 'for arg in "$@"; do', ' if [ "$next_is_convert_to" = "1" ]; then convert_to="$arg"; next_is_convert_to=0; continue; fi', ' if [ "$next_is_outdir" = "1" ]; then outdir="$arg"; next_is_outdir=0; continue; fi', ' if [ "$arg" = "--convert-to" ]; then next_is_convert_to=1; fi', ' if [ "$arg" = "--outdir" ]; then next_is_outdir=1; fi', 'done', '[ "$convert_to" = "pdf:writer_pdf_Export" ] || exit 41', '[ "$FONTCONFIG_FILE" = "' + fontConfigFile + '" ] || exit 42', 'printf "%s" "%PDF-1.7\\nconverted" > "$outdir/team.pdf"' ].join('\n'), { mode: 0o700 }); await chmod(converterPath, 0o700); try { const result = await convertDocumentToPdf(testConfig(converterPath, fontConfigFile), { fileName: 'team.docx', contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', content: Buffer.from('source'), maxBytes: 10_000 }); assert.equal(result.fileName, 'team.pdf'); assert.equal(result.contentType, 'application/pdf'); assert.equal(result.converted, true); assert.equal(result.conversion_status, 'converted'); assert.match(result.content.toString(), /^%PDF-1\.7/); } finally { await rm(temporaryDirectory, { recursive: true, force: true }); } }); test('HTML-in-XLS visitor sources become real XLSX workbooks', { skip: process.platform === 'win32' ? 'Unix fake converter fixture is not executable through Windows execFile.' : false }, async () => { const temporaryDirectory = await mkdtemp(join(tmpdir(), 'ltjt-converter-xlsx-test-')); const converterPath = join(temporaryDirectory, 'fake-soffice.sh'); await writeFile(converterPath, [ '#!/bin/sh', 'outdir=""', 'convert_to=""', 'input_path=""', 'next_is_outdir=0', 'next_is_convert_to=0', 'for arg in "$@"; do', ' if [ "$next_is_convert_to" = "1" ]; then convert_to="$arg"; next_is_convert_to=0; continue; fi', ' if [ "$next_is_outdir" = "1" ]; then outdir="$arg"; next_is_outdir=0; continue; fi', ' if [ "$arg" = "--convert-to" ]; then next_is_convert_to=1; continue; fi', ' if [ "$arg" = "--outdir" ]; then next_is_outdir=1; continue; fi', ' case "$arg" in *.xls) input_path="$arg";; esac', 'done', '[ "$convert_to" = "xlsx:Calc MS Excel 2007 XML" ] || exit 41', 'case "$input_path" in *.xls) ;; *) exit 42;; esac', 'printf "PK\\003\\004fake-xlsx" > "$outdir/Visitor.xlsx"' ].join('\n'), { mode: 0o700 }); await chmod(converterPath, 0o700); try { const content = Buffer.from('
游客
', 'utf8'); const result = await convertDocumentToXlsx(testConfig(converterPath), { fileName: 'Visitor.xls', contentType: 'application/vnd.ms-excel; Charset=UTF-8', content, maxBytes: 10_000 }); assert.equal(result.fileName, 'Visitor.xlsx'); assert.equal(result.contentType, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); assert.equal(result.converted, true); assert.equal(result.conversion_status, 'converted_xlsx'); assert.deepEqual(result.content.subarray(0, 4), Buffer.from([0x50, 0x4b, 0x03, 0x04])); } finally { await rm(temporaryDirectory, { recursive: true, force: true }); } }); test('visitor XLSX conversion failure remains a source fallback', async () => { const content = Buffer.from('游客', 'utf8'); const result = await convertDocumentToXlsx(testConfig('/path/that/does/not/exist'), { fileName: 'Visitor.xls', contentType: 'application/vnd.ms-excel', content, maxBytes: 10_000 }); assert.equal(result.fileName, 'Visitor.xls'); assert.equal(result.converted, false); assert.equal(result.conversion_status, 'source_fallback'); assert.equal(result.conversion_error_code, 'converter_unavailable'); assert.deepEqual(result.content, content); }); test('wide Word HTML tables are scaled to the printable width before conversion', { skip: process.platform === 'win32' ? 'Unix fake converter fixture is not executable through Windows execFile.' : false }, async () => { const temporaryDirectory = await mkdtemp(join(tmpdir(), 'ltjt-converter-html-test-')); const converterPath = join(temporaryDirectory, 'fake-soffice.sh'); await writeFile(converterPath, [ '#!/bin/sh', 'outdir=""', 'input_path=""', 'next_is_outdir=0', 'for arg in "$@"; do', ' if [ "$next_is_outdir" = "1" ]; then outdir="$arg"; next_is_outdir=0; continue; fi', ' if [ "$arg" = "--outdir" ]; then next_is_outdir=1; continue; fi', ' case "$arg" in *.doc|*.docx|*.html) input_path="$arg";; esac', 'done', 'grep -q "width=733" "$input_path" || exit 43', 'grep -q "width=\\\"154\\\"" "$input_path" || exit 44', 'grep -q "width: 550pt" "$input_path" || exit 45', 'printf "%s" "%PDF-1.7\\nconverted" > "$outdir/team.pdf"' ].join('\n'), { mode: 0o700 }); await chmod(converterPath, 0o700); try { const result = await convertDocumentToPdf(testConfig(converterPath), { fileName: 'team.doc', contentType: 'application/msword', content: Buffer.from( '' + '
内容
', 'utf8' ), maxBytes: 10_000 }); assert.equal(result.converted, true); assert.equal(result.conversion_status, 'converted'); } finally { await rm(temporaryDirectory, { recursive: true, force: true }); } });