230 lines
7.8 KiB
TypeScript
230 lines
7.8 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
|
|
tap.test('EDGE-05: Zero-Byte PDFs - should handle zero-byte and minimal PDF files', async () => {
|
|
console.log('Testing zero-byte and minimal PDF handling...\n');
|
|
|
|
// Test 1: Truly zero-byte PDF
|
|
const testZeroBytePdf = async () => {
|
|
const zeroPDF = Buffer.alloc(0);
|
|
|
|
try {
|
|
const result = await EInvoice.fromPdf(zeroPDF);
|
|
console.log('Test 1 - Zero-byte PDF:');
|
|
console.log(' Unexpectedly succeeded, result:', result);
|
|
return { handled: false, error: null };
|
|
} catch (error) {
|
|
console.log('Test 1 - Zero-byte PDF:');
|
|
console.log(' Properly failed with error:', error.message);
|
|
return { handled: true, error: error.message };
|
|
}
|
|
};
|
|
|
|
// Test 2: Minimal PDF structures
|
|
const testMinimalPdfStructures = async () => {
|
|
const minimalPDFs = [
|
|
{
|
|
name: 'header-only',
|
|
content: Buffer.from('%PDF-1.4')
|
|
},
|
|
{
|
|
name: 'header-and-eof',
|
|
content: Buffer.from('%PDF-1.4\n%%EOF')
|
|
},
|
|
{
|
|
name: 'empty-catalog',
|
|
content: Buffer.from(
|
|
'%PDF-1.4\n' +
|
|
'1 0 obj\n<< /Type /Catalog >>\nendobj\n' +
|
|
'xref\n0 2\n' +
|
|
'0000000000 65535 f\n' +
|
|
'0000000009 00000 n\n' +
|
|
'trailer\n<< /Size 2 /Root 1 0 R >>\n' +
|
|
'startxref\n64\n%%EOF'
|
|
)
|
|
},
|
|
{
|
|
name: 'invalid-header',
|
|
content: Buffer.from('NOT-A-PDF-HEADER')
|
|
},
|
|
{
|
|
name: 'truncated-pdf',
|
|
content: Buffer.from('%PDF-1.4\n1 0 obj\n<< /Type /Cat')
|
|
}
|
|
];
|
|
|
|
const results = [];
|
|
|
|
for (const pdf of minimalPDFs) {
|
|
try {
|
|
const result = await EInvoice.fromPdf(pdf.content);
|
|
console.log(`\nTest 2.${pdf.name}:`);
|
|
console.log(` Size: ${pdf.content.length} bytes`);
|
|
console.log(` Extracted invoice: Yes`);
|
|
console.log(` Result type: ${typeof result}`);
|
|
results.push({ name: pdf.name, success: true, error: null });
|
|
} catch (error) {
|
|
console.log(`\nTest 2.${pdf.name}:`);
|
|
console.log(` Size: ${pdf.content.length} bytes`);
|
|
console.log(` Error: ${error.message}`);
|
|
results.push({ name: pdf.name, success: false, error: error.message });
|
|
}
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
// Test 3: PDF with invalid content but correct headers
|
|
const testInvalidContentPdf = async () => {
|
|
const invalidContentPDFs = [
|
|
{
|
|
name: 'binary-garbage',
|
|
content: Buffer.concat([
|
|
Buffer.from('%PDF-1.4\n'),
|
|
Buffer.from(Array(100).fill(0).map(() => Math.floor(Math.random() * 256))),
|
|
Buffer.from('\n%%EOF')
|
|
])
|
|
},
|
|
{
|
|
name: 'text-only',
|
|
content: Buffer.from('%PDF-1.4\nThis is just plain text content\n%%EOF')
|
|
},
|
|
{
|
|
name: 'xml-content',
|
|
content: Buffer.from('%PDF-1.4\n<xml><invoice>test</invoice></xml>\n%%EOF')
|
|
}
|
|
];
|
|
|
|
const results = [];
|
|
|
|
for (const pdf of invalidContentPDFs) {
|
|
try {
|
|
const result = await EInvoice.fromPdf(pdf.content);
|
|
console.log(`\nTest 3.${pdf.name}:`);
|
|
console.log(` PDF parsed successfully: Yes`);
|
|
console.log(` Invoice extracted: ${result ? 'Yes' : 'No'}`);
|
|
results.push({ name: pdf.name, parsed: true, extracted: !!result });
|
|
} catch (error) {
|
|
console.log(`\nTest 3.${pdf.name}:`);
|
|
console.log(` Error: ${error.message}`);
|
|
results.push({ name: pdf.name, parsed: false, extracted: false, error: error.message });
|
|
}
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
// Test 4: Edge case PDF sizes
|
|
const testEdgeCaseSizes = async () => {
|
|
const edgeCasePDFs = [
|
|
{
|
|
name: 'single-byte',
|
|
content: Buffer.from('P')
|
|
},
|
|
{
|
|
name: 'minimal-header',
|
|
content: Buffer.from('%PDF')
|
|
},
|
|
{
|
|
name: 'almost-valid-header',
|
|
content: Buffer.from('%PDF-1')
|
|
},
|
|
{
|
|
name: 'very-large-empty',
|
|
content: Buffer.concat([
|
|
Buffer.from('%PDF-1.4\n'),
|
|
Buffer.alloc(10000, 0x20), // 10KB of spaces
|
|
Buffer.from('\n%%EOF')
|
|
])
|
|
}
|
|
];
|
|
|
|
const results = [];
|
|
|
|
for (const pdf of edgeCasePDFs) {
|
|
try {
|
|
await EInvoice.fromPdf(pdf.content);
|
|
console.log(`\nTest 4.${pdf.name}:`);
|
|
console.log(` Size: ${pdf.content.length} bytes`);
|
|
console.log(` Processing successful: Yes`);
|
|
results.push({ name: pdf.name, size: pdf.content.length, processed: true });
|
|
} catch (error) {
|
|
console.log(`\nTest 4.${pdf.name}:`);
|
|
console.log(` Size: ${pdf.content.length} bytes`);
|
|
console.log(` Error: ${error.message}`);
|
|
results.push({ name: pdf.name, size: pdf.content.length, processed: false, error: error.message });
|
|
}
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
// Test 5: PDF with embedded XML but malformed structure
|
|
const testMalformedEmbeddedXml = async () => {
|
|
try {
|
|
// Create a PDF-like structure with embedded XML-like content
|
|
const malformedPdf = Buffer.from(
|
|
'%PDF-1.4\n' +
|
|
'1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n' +
|
|
'2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n' +
|
|
'3 0 obj\n<< /Type /Page /Parent 2 0 R >>\nendobj\n' +
|
|
'4 0 obj\n<< /Type /EmbeddedFile /Filter /ASCIIHexDecode /Length 100 >>\n' +
|
|
'stream\n' +
|
|
'3C696E766F6963653E3C2F696E766F6963653E\n' + // hex for <invoice></invoice>
|
|
'endstream\nendobj\n' +
|
|
'xref\n0 5\n' +
|
|
'0000000000 65535 f\n' +
|
|
'0000000009 00000 n\n' +
|
|
'0000000052 00000 n\n' +
|
|
'0000000101 00000 n\n' +
|
|
'0000000141 00000 n\n' +
|
|
'trailer\n<< /Size 5 /Root 1 0 R >>\n' +
|
|
'startxref\n241\n%%EOF'
|
|
);
|
|
|
|
const result = await EInvoice.fromPdf(malformedPdf);
|
|
|
|
console.log(`\nTest 5 - Malformed embedded XML:`);
|
|
console.log(` PDF size: ${malformedPdf.length} bytes`);
|
|
console.log(` Processing result: ${result ? 'Success' : 'No invoice found'}`);
|
|
|
|
return { processed: true, result: !!result };
|
|
} catch (error) {
|
|
console.log(`\nTest 5 - Malformed embedded XML:`);
|
|
console.log(` Error: ${error.message}`);
|
|
|
|
return { processed: false, error: error.message };
|
|
}
|
|
};
|
|
|
|
// Run all tests
|
|
const zeroByteResult = await testZeroBytePdf();
|
|
const minimalResults = await testMinimalPdfStructures();
|
|
const invalidContentResults = await testInvalidContentPdf();
|
|
const edgeCaseResults = await testEdgeCaseSizes();
|
|
const malformedResult = await testMalformedEmbeddedXml();
|
|
|
|
console.log(`\n=== Zero-Byte PDF Test Summary ===`);
|
|
|
|
// Count results
|
|
const minimalHandled = minimalResults.filter(r => r.error !== null).length;
|
|
const invalidHandled = invalidContentResults.filter(r => r.error !== null).length;
|
|
const edgeCaseHandled = edgeCaseResults.filter(r => r.error !== null).length;
|
|
|
|
console.log(`Zero-byte PDF: ${zeroByteResult.handled ? 'Properly handled' : 'Unexpected behavior'}`);
|
|
console.log(`Minimal PDFs: ${minimalHandled}/${minimalResults.length} properly handled`);
|
|
console.log(`Invalid content PDFs: ${invalidHandled}/${invalidContentResults.length} properly handled`);
|
|
console.log(`Edge case sizes: ${edgeCaseHandled}/${edgeCaseResults.length} properly handled`);
|
|
console.log(`Malformed embedded XML: ${malformedResult.processed ? 'Processed' : 'Error handled'}`);
|
|
|
|
// Test passes if the library properly handles edge cases without crashing
|
|
// Zero-byte PDF should fail gracefully
|
|
expect(zeroByteResult.handled).toBeTrue();
|
|
|
|
// At least some minimal PDFs should fail (they don't contain valid invoice data)
|
|
const someMinimalFailed = minimalResults.some(r => !r.success);
|
|
expect(someMinimalFailed).toBeTrue();
|
|
});
|
|
|
|
// Run the test
|
|
tap.start(); |