import { tap } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; import { PerformanceTracker } from '../performance.tracker.js'; tap.test('EDGE-05: Zero-Byte PDFs - should handle zero-byte and minimal PDF files', async () => { // Test 1: Truly zero-byte PDF await PerformanceTracker.track('truly-zero-byte-pdf', async () => { const zeroPDF = Buffer.alloc(0); try { const result = await EInvoice.fromPdf(zeroPDF); console.log('Zero-byte PDF: unexpectedly succeeded', result); } catch (error) { console.log('Zero-byte PDF: properly failed with error:', error.message); } }); // Test 2: Minimal PDF structure await PerformanceTracker.track('minimal-pdf-structure', 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' ) } ]; for (const pdf of minimalPDFs) { try { await EInvoice.fromPdf(pdf.content); console.log(`Minimal PDF ${pdf.name}: size=${pdf.content.length}, extracted invoice`); } catch (error) { console.log(`Minimal PDF ${pdf.name}: failed - ${error.message}`); } } }); // Test 3: Truncated PDF files await PerformanceTracker.track('truncated-pdf-files', async () => { // Start with a valid PDF structure and truncate at different points const fullPDF = Buffer.from( '%PDF-1.4\n' + '1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n' + '2 0 obj\n<< /Type /Pages /Count 1 /Kids [3 0 R] >>\nendobj\n' + '3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n' + 'xref\n0 4\n' + '0000000000 65535 f\n' + '0000000009 00000 n\n' + '0000000052 00000 n\n' + '0000000110 00000 n\n' + 'trailer\n<< /Size 4 /Root 1 0 R >>\n' + 'startxref\n196\n%%EOF' ); const truncationPoints = [ { name: 'after-header', bytes: 10 }, { name: 'mid-object', bytes: 50 }, { name: 'before-xref', bytes: 150 }, { name: 'before-eof', bytes: fullPDF.length - 5 } ]; for (const point of truncationPoints) { const truncated = fullPDF.subarray(0, point.bytes); try { await EInvoice.fromPdf(truncated); console.log(`Truncated PDF at ${point.name}: unexpectedly succeeded`); } catch (error) { console.log(`Truncated PDF at ${point.name}: properly failed - ${error.message}`); } } }); // Test 4: PDF extraction and embedding await PerformanceTracker.track('pdf-extraction-embedding', async () => { // Create an invoice first const einvoice = new EInvoice(); einvoice.issueDate = new Date(2024, 0, 1); einvoice.invoiceId = 'ZERO-001'; einvoice.from = { type: 'company', name: 'Test Company', description: 'Testing zero-byte scenarios', address: { streetName: 'Test Street', houseNumber: '1', postalCode: '12345', city: 'Test City', country: 'DE' }, status: 'active', foundedDate: { year: 2020, month: 1, day: 1 }, registrationDetails: { vatId: 'DE123456789', registrationId: 'HRB 12345', registrationName: 'Commercial Register' } }; einvoice.to = { type: 'person', name: 'Test', surname: 'Customer', salutation: 'Mr' as const, sex: 'male' as const, title: 'Doctor' as const, description: 'Test customer', address: { streetName: 'Customer Street', houseNumber: '2', postalCode: '54321', city: 'Customer City', country: 'DE' } }; einvoice.items = [{ position: 1, name: 'Test Service', articleNumber: 'SRV-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; try { // Generate UBL const ublString = await einvoice.toXmlString('ubl'); console.log(`Generated UBL invoice: ${ublString.length} bytes`); // Try to embed in a minimal PDF (this will likely fail) const minimalPDF = Buffer.from('%PDF-1.4\n%%EOF'); await einvoice.embedInPdf(minimalPDF, 'ubl'); console.log(`Embedded XML in minimal PDF: success`); } catch (error) { console.log(`PDF embedding test failed: ${error.message}`); } }); // Test 5: Empty invoice edge cases await PerformanceTracker.track('empty-invoice-edge-cases', async () => { const testCases = [ { name: 'no-items', setup: (invoice: EInvoice) => { invoice.items = []; } }, { name: 'empty-strings', setup: (invoice: EInvoice) => { invoice.invoiceId = ''; invoice.items = [{ position: 1, name: '', articleNumber: '', unitType: 'EA', unitQuantity: 0, unitNetPrice: 0, vatPercentage: 0 }]; } }, { name: 'zero-amounts', setup: (invoice: EInvoice) => { invoice.items = [{ position: 1, name: 'Zero Value Item', articleNumber: 'ZERO-001', unitType: 'EA', unitQuantity: 0, unitNetPrice: 0, vatPercentage: 0 }]; } } ]; for (const testCase of testCases) { const einvoice = new EInvoice(); einvoice.issueDate = new Date(2024, 0, 1); einvoice.invoiceId = 'EMPTY-001'; einvoice.from = { type: 'company', name: 'Empty Test Company', description: 'Testing empty scenarios', address: { streetName: 'Test Street', houseNumber: '1', postalCode: '12345', city: 'Test City', country: 'DE' }, status: 'active', foundedDate: { year: 2020, month: 1, day: 1 }, registrationDetails: { vatId: 'DE123456789', registrationId: 'HRB 12345', registrationName: 'Commercial Register' } }; einvoice.to = { type: 'company', name: 'Customer Company', description: 'Customer', address: { streetName: 'Customer Street', houseNumber: '2', postalCode: '54321', city: 'Customer City', country: 'DE' }, status: 'active', foundedDate: { year: 2019, month: 1, day: 1 }, registrationDetails: { vatId: 'DE987654321', registrationId: 'HRB 54321', registrationName: 'Commercial Register' } }; // Apply test-specific setup testCase.setup(einvoice); try { const ciiString = await einvoice.toXmlString('cii'); console.log(`Empty test ${testCase.name}: generated ${ciiString.length} bytes`); // Try validation const validationResult = await einvoice.validate(); console.log(`Empty test ${testCase.name} validation: ${validationResult.valid ? 'valid' : 'invalid'}`); if (!validationResult.valid) { console.log(`Validation errors: ${validationResult.errors.length}`); } } catch (error) { console.log(`Empty test ${testCase.name} failed: ${error.message}`); } } }); // Test 6: Batch processing with zero-byte PDFs await PerformanceTracker.track('batch-processing-zero-byte', async () => { const batch = [ { name: 'zero-byte', content: Buffer.alloc(0) }, { name: 'header-only', content: Buffer.from('%PDF-1.4') }, { name: 'invalid', content: Buffer.from('Not a PDF') }, { name: 'valid-minimal', content: createMinimalValidPDF() } ]; let successful = 0; let failed = 0; for (const item of batch) { try { await EInvoice.fromPdf(item.content); successful++; console.log(`Batch item ${item.name}: extracted successfully`); } catch (error) { failed++; console.log(`Batch item ${item.name}: failed - ${error.message}`); } } console.log(`Batch processing complete: ${successful} successful, ${failed} failed`); }); // Test 7: Memory efficiency with zero content await PerformanceTracker.track('memory-efficiency-zero-content', async () => { const iterations = 100; const beforeMem = process.memoryUsage(); // Create many empty invoices const invoices: EInvoice[] = []; for (let i = 0; i < iterations; i++) { const einvoice = new EInvoice(); einvoice.issueDate = new Date(2024, 0, 1); einvoice.invoiceId = `MEM-${i}`; einvoice.from = { type: 'company', name: 'Memory Test', description: 'Testing memory', address: { streetName: 'Test Street', houseNumber: '1', postalCode: '12345', city: 'Test City', country: 'DE' }, status: 'active', foundedDate: { year: 2020, month: 1, day: 1 }, registrationDetails: { vatId: 'DE123456789', registrationId: 'HRB 12345', registrationName: 'Commercial Register' } }; einvoice.to = { type: 'person', name: 'Test', surname: 'Customer', salutation: 'Mr' as const, sex: 'male' as const, title: 'Doctor' as const, description: 'Test customer', address: { streetName: 'Customer Street', houseNumber: '2', postalCode: '54321', city: 'Customer City', country: 'DE' } }; einvoice.items = []; // Empty items invoices.push(einvoice); } const afterMem = process.memoryUsage(); const memDiff = { heapUsed: Math.round((afterMem.heapUsed - beforeMem.heapUsed) / 1024 / 1024 * 100) / 100, rss: Math.round((afterMem.rss - beforeMem.rss) / 1024 / 1024 * 100) / 100 }; console.log(`Created ${iterations} empty invoices`); console.log(`Memory usage increase: Heap: ${memDiff.heapUsed}MB, RSS: ${memDiff.rss}MB`); // Try to process them all let processedCount = 0; for (const invoice of invoices) { try { const xml = await invoice.toXmlString('ubl'); if (xml && xml.length > 0) { processedCount++; } } catch (error) { // Expected for empty invoices } } console.log(`Successfully processed ${processedCount} out of ${iterations} empty invoices`); }); }); // Helper function to create a minimal valid PDF function createMinimalValidPDF(): Buffer { return Buffer.from( '%PDF-1.4\n' + '1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n' + '2 0 obj\n<< /Type /Pages /Count 0 /Kids [] >>\nendobj\n' + 'xref\n0 3\n' + '0000000000 65535 f\n' + '0000000009 00000 n\n' + '0000000058 00000 n\n' + 'trailer\n<< /Size 3 /Root 1 0 R >>\n' + 'startxref\n115\n%%EOF' ); } // Run the test tap.start();