update
This commit is contained in:
@ -0,0 +1,566 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { EInvoice } from '../../../ts/index.js';
|
||||
import { CorpusLoader } from '../corpus.loader.js';
|
||||
import { PerformanceTracker } from '../performance.tracker.js';
|
||||
|
||||
tap.test('PDF-12: PDF Version Compatibility - should handle different PDF versions correctly', async (t) => {
|
||||
// PDF-12: Verify compatibility across different PDF versions (1.3 - 1.7)
|
||||
// This test ensures the system works with various PDF specifications
|
||||
|
||||
const performanceTracker = new PerformanceTracker('PDF-12: PDF Version Compatibility');
|
||||
const corpusLoader = new CorpusLoader();
|
||||
|
||||
t.test('Create PDFs with different version headers', async () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
const { PDFDocument } = plugins;
|
||||
|
||||
// Test different PDF versions
|
||||
const versions = [
|
||||
{ version: '1.3', features: 'Basic PDF features, Acrobat 4.x compatible' },
|
||||
{ version: '1.4', features: 'Transparency, Acrobat 5.x compatible' },
|
||||
{ version: '1.5', features: 'Object streams, Acrobat 6.x compatible' },
|
||||
{ version: '1.6', features: 'OpenType fonts, Acrobat 7.x compatible' },
|
||||
{ version: '1.7', features: 'XFA forms, ISO 32000-1:2008 standard' }
|
||||
];
|
||||
|
||||
for (const ver of versions) {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
// Note: pdf-lib doesn't allow direct version setting
|
||||
// PDFs are typically created as 1.7 by default
|
||||
|
||||
pdfDoc.setTitle(`PDF Version ${ver.version} Test`);
|
||||
pdfDoc.setSubject(ver.features);
|
||||
|
||||
const page = pdfDoc.addPage([595, 842]);
|
||||
|
||||
page.drawText(`PDF Version ${ver.version}`, {
|
||||
x: 50,
|
||||
y: 750,
|
||||
size: 24
|
||||
});
|
||||
|
||||
page.drawText(`Features: ${ver.features}`, {
|
||||
x: 50,
|
||||
y: 700,
|
||||
size: 12
|
||||
});
|
||||
|
||||
// Add version-specific content
|
||||
if (parseFloat(ver.version) >= 1.4) {
|
||||
// Transparency (PDF 1.4+)
|
||||
page.drawRectangle({
|
||||
x: 50,
|
||||
y: 600,
|
||||
width: 200,
|
||||
height: 50,
|
||||
color: { red: 0, green: 0, blue: 1 },
|
||||
opacity: 0.5 // Transparency
|
||||
});
|
||||
}
|
||||
|
||||
// Add invoice XML
|
||||
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||||
<ID>PDF-VER-${ver.version}</ID>
|
||||
<Note>Test invoice for PDF ${ver.version}</Note>
|
||||
<PDFVersion>${ver.version}</PDFVersion>
|
||||
</Invoice>`;
|
||||
|
||||
await pdfDoc.attach(
|
||||
Buffer.from(xmlContent, 'utf8'),
|
||||
'invoice.xml',
|
||||
{
|
||||
mimeType: 'application/xml',
|
||||
description: `Invoice for PDF ${ver.version}`
|
||||
}
|
||||
);
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
|
||||
// Check version in output
|
||||
const pdfString = pdfBytes.toString('binary').substring(0, 100);
|
||||
console.log(`Created PDF (declared as ${ver.version}), header: ${pdfString.substring(0, 8)}`);
|
||||
|
||||
// Test processing
|
||||
const einvoice = new EInvoice();
|
||||
try {
|
||||
await einvoice.loadFromPdfBuffer(pdfBytes);
|
||||
const xml = einvoice.getXmlString();
|
||||
expect(xml).toContain(`PDF-VER-${ver.version}`);
|
||||
} catch (error) {
|
||||
console.log(`Version ${ver.version} processing error:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
const elapsed = performance.now() - startTime;
|
||||
performanceTracker.addMeasurement('version-creation', elapsed);
|
||||
});
|
||||
|
||||
t.test('Feature compatibility across versions', async () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
const { PDFDocument } = plugins;
|
||||
|
||||
// Test version-specific features
|
||||
const featureTests = [
|
||||
{
|
||||
name: 'Basic Features (1.3+)',
|
||||
test: async (pdfDoc: any) => {
|
||||
const page = pdfDoc.addPage();
|
||||
// Basic text and graphics
|
||||
page.drawText('Basic Text', { x: 50, y: 700, size: 14 });
|
||||
page.drawLine({
|
||||
start: { x: 50, y: 680 },
|
||||
end: { x: 200, y: 680 },
|
||||
thickness: 1
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Transparency (1.4+)',
|
||||
test: async (pdfDoc: any) => {
|
||||
const page = pdfDoc.addPage();
|
||||
// Overlapping transparent rectangles
|
||||
page.drawRectangle({
|
||||
x: 50,
|
||||
y: 600,
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: { red: 1, green: 0, blue: 0 },
|
||||
opacity: 0.5
|
||||
});
|
||||
page.drawRectangle({
|
||||
x: 100,
|
||||
y: 650,
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: { red: 0, green: 0, blue: 1 },
|
||||
opacity: 0.5
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Embedded Files (1.4+)',
|
||||
test: async (pdfDoc: any) => {
|
||||
// Multiple embedded files
|
||||
await pdfDoc.attach(
|
||||
Buffer.from('<data>Primary</data>', 'utf8'),
|
||||
'primary.xml',
|
||||
{ mimeType: 'application/xml' }
|
||||
);
|
||||
await pdfDoc.attach(
|
||||
Buffer.from('<data>Secondary</data>', 'utf8'),
|
||||
'secondary.xml',
|
||||
{ mimeType: 'application/xml' }
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Unicode Support (1.5+)',
|
||||
test: async (pdfDoc: any) => {
|
||||
const page = pdfDoc.addPage();
|
||||
page.drawText('Unicode: 中文 العربية ελληνικά', {
|
||||
x: 50,
|
||||
y: 600,
|
||||
size: 14
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
for (const feature of featureTests) {
|
||||
console.log(`Testing: ${feature.name}`);
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
pdfDoc.setTitle(feature.name);
|
||||
await feature.test(pdfDoc);
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
expect(pdfBytes.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
const elapsed = performance.now() - startTime;
|
||||
performanceTracker.addMeasurement('feature-compatibility', elapsed);
|
||||
});
|
||||
|
||||
t.test('Cross-version attachment compatibility', async () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
const { PDFDocument, AFRelationship } = plugins;
|
||||
|
||||
// Test attachment features across versions
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
pdfDoc.setTitle('Cross-Version Attachment Test');
|
||||
const page = pdfDoc.addPage();
|
||||
page.drawText('PDF with Various Attachment Features', { x: 50, y: 750, size: 16 });
|
||||
|
||||
// Test different attachment configurations
|
||||
const attachmentTests = [
|
||||
{
|
||||
name: 'Simple attachment (1.3+)',
|
||||
file: 'simple.xml',
|
||||
content: '<invoice><id>SIMPLE</id></invoice>',
|
||||
options: { mimeType: 'application/xml' }
|
||||
},
|
||||
{
|
||||
name: 'With description (1.4+)',
|
||||
file: 'described.xml',
|
||||
content: '<invoice><id>DESCRIBED</id></invoice>',
|
||||
options: {
|
||||
mimeType: 'application/xml',
|
||||
description: 'Invoice with description'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'With relationship (1.7+)',
|
||||
file: 'related.xml',
|
||||
content: '<invoice><id>RELATED</id></invoice>',
|
||||
options: {
|
||||
mimeType: 'application/xml',
|
||||
description: 'Invoice with AFRelationship',
|
||||
afRelationship: AFRelationship.Data
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'With dates (1.4+)',
|
||||
file: 'dated.xml',
|
||||
content: '<invoice><id>DATED</id></invoice>',
|
||||
options: {
|
||||
mimeType: 'application/xml',
|
||||
description: 'Invoice with timestamps',
|
||||
creationDate: new Date('2025-01-01'),
|
||||
modificationDate: new Date('2025-01-25')
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
let yPos = 700;
|
||||
for (const test of attachmentTests) {
|
||||
await pdfDoc.attach(
|
||||
Buffer.from(test.content, 'utf8'),
|
||||
test.file,
|
||||
test.options
|
||||
);
|
||||
|
||||
page.drawText(`✓ ${test.name}`, { x: 70, y: yPos, size: 10 });
|
||||
yPos -= 20;
|
||||
}
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
|
||||
// Test extraction
|
||||
const einvoice = new EInvoice();
|
||||
await einvoice.loadFromPdfBuffer(pdfBytes);
|
||||
console.log('Cross-version attachment test completed');
|
||||
|
||||
const elapsed = performance.now() - startTime;
|
||||
performanceTracker.addMeasurement('attachment-compatibility', elapsed);
|
||||
});
|
||||
|
||||
t.test('Backward compatibility', async () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
const { PDFDocument } = plugins;
|
||||
|
||||
// Create PDF with only features from older versions
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
pdfDoc.setTitle('Backward Compatible PDF');
|
||||
pdfDoc.setAuthor('Legacy System');
|
||||
pdfDoc.setSubject('PDF 1.3 Compatible Invoice');
|
||||
|
||||
const page = pdfDoc.addPage([612, 792]); // US Letter
|
||||
|
||||
// Use only basic features available in PDF 1.3
|
||||
const helvetica = await pdfDoc.embedFont('Helvetica');
|
||||
|
||||
// Simple text
|
||||
page.drawText('Legacy Compatible Invoice', {
|
||||
x: 72,
|
||||
y: 720,
|
||||
size: 18,
|
||||
font: helvetica,
|
||||
color: { red: 0, green: 0, blue: 0 }
|
||||
});
|
||||
|
||||
// Basic shapes without transparency
|
||||
page.drawRectangle({
|
||||
x: 72,
|
||||
y: 600,
|
||||
width: 468,
|
||||
height: 100,
|
||||
borderColor: { red: 0, green: 0, blue: 0 },
|
||||
borderWidth: 1
|
||||
});
|
||||
|
||||
// Simple lines
|
||||
page.drawLine({
|
||||
start: { x: 72, y: 650 },
|
||||
end: { x: 540, y: 650 },
|
||||
thickness: 1,
|
||||
color: { red: 0, green: 0, blue: 0 }
|
||||
});
|
||||
|
||||
// Basic invoice data (no advanced features)
|
||||
const invoiceLines = [
|
||||
'Invoice Number: 2025-001',
|
||||
'Date: January 25, 2025',
|
||||
'Amount: $1,234.56',
|
||||
'Status: PAID'
|
||||
];
|
||||
|
||||
let yPos = 620;
|
||||
invoiceLines.forEach(line => {
|
||||
page.drawText(line, {
|
||||
x: 80,
|
||||
y: yPos,
|
||||
size: 12,
|
||||
font: helvetica,
|
||||
color: { red: 0, green: 0, blue: 0 }
|
||||
});
|
||||
yPos -= 20;
|
||||
});
|
||||
|
||||
// Simple XML attachment
|
||||
const xmlContent = `<?xml version="1.0"?>
|
||||
<invoice>
|
||||
<number>2025-001</number>
|
||||
<date>2025-01-25</date>
|
||||
<amount>1234.56</amount>
|
||||
</invoice>`;
|
||||
|
||||
await pdfDoc.attach(
|
||||
Buffer.from(xmlContent, 'utf8'),
|
||||
'invoice.xml',
|
||||
{ mimeType: 'text/xml' } // Basic MIME type
|
||||
);
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
|
||||
// Verify it can be processed
|
||||
const einvoice = new EInvoice();
|
||||
await einvoice.loadFromPdfBuffer(pdfBytes);
|
||||
|
||||
console.log('Created backward compatible PDF (1.3 features only)');
|
||||
|
||||
const elapsed = performance.now() - startTime;
|
||||
performanceTracker.addMeasurement('backward-compatibility', elapsed);
|
||||
});
|
||||
|
||||
t.test('Version detection in corpus', async () => {
|
||||
const startTime = performance.now();
|
||||
let processedCount = 0;
|
||||
const versionStats: Record<string, number> = {};
|
||||
const featureStats = {
|
||||
transparency: 0,
|
||||
embeddedFiles: 0,
|
||||
javascript: 0,
|
||||
forms: 0,
|
||||
compression: 0
|
||||
};
|
||||
|
||||
const files = await corpusLoader.getAllFiles();
|
||||
const pdfFiles = files.filter(f => f.endsWith('.pdf'));
|
||||
|
||||
// Analyze PDF versions in corpus
|
||||
const sampleSize = Math.min(50, pdfFiles.length);
|
||||
const sample = pdfFiles.slice(0, sampleSize);
|
||||
|
||||
for (const file of sample) {
|
||||
try {
|
||||
const content = await corpusLoader.readFile(file);
|
||||
const pdfString = content.toString('binary');
|
||||
|
||||
// Extract PDF version from header
|
||||
const versionMatch = pdfString.match(/%PDF-(\d\.\d)/);
|
||||
if (versionMatch) {
|
||||
const version = versionMatch[1];
|
||||
versionStats[version] = (versionStats[version] || 0) + 1;
|
||||
}
|
||||
|
||||
// Check for version-specific features
|
||||
if (pdfString.includes('/Group') && pdfString.includes('/S /Transparency')) {
|
||||
featureStats.transparency++;
|
||||
}
|
||||
|
||||
if (pdfString.includes('/EmbeddedFiles')) {
|
||||
featureStats.embeddedFiles++;
|
||||
}
|
||||
|
||||
if (pdfString.includes('/JS') || pdfString.includes('/JavaScript')) {
|
||||
featureStats.javascript++;
|
||||
}
|
||||
|
||||
if (pdfString.includes('/AcroForm')) {
|
||||
featureStats.forms++;
|
||||
}
|
||||
|
||||
if (pdfString.includes('/Filter') && pdfString.includes('/FlateDecode')) {
|
||||
featureStats.compression++;
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
} catch (error) {
|
||||
console.log(`Error analyzing ${file}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Corpus version analysis (${processedCount} PDFs):`);
|
||||
console.log('PDF versions found:', versionStats);
|
||||
console.log('Feature usage:', featureStats);
|
||||
|
||||
// Most common version
|
||||
const sortedVersions = Object.entries(versionStats).sort((a, b) => b[1] - a[1]);
|
||||
if (sortedVersions.length > 0) {
|
||||
console.log(`Most common version: PDF ${sortedVersions[0][0]} (${sortedVersions[0][1]} files)`);
|
||||
}
|
||||
|
||||
const elapsed = performance.now() - startTime;
|
||||
performanceTracker.addMeasurement('corpus-versions', elapsed);
|
||||
});
|
||||
|
||||
t.test('Version upgrade scenarios', async () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
const { PDFDocument } = plugins;
|
||||
|
||||
// Simulate upgrading PDF from older to newer version
|
||||
console.log('Testing version upgrade scenarios:');
|
||||
|
||||
// Create "old" PDF (simulated)
|
||||
const oldPdf = await PDFDocument.create();
|
||||
oldPdf.setTitle('Old PDF (1.3 style)');
|
||||
|
||||
const page1 = oldPdf.addPage();
|
||||
page1.drawText('Original Document', { x: 50, y: 700, size: 16 });
|
||||
page1.drawText('Created with PDF 1.3 features only', { x: 50, y: 650, size: 12 });
|
||||
|
||||
const oldPdfBytes = await oldPdf.save();
|
||||
|
||||
// "Upgrade" by loading and adding new features
|
||||
const upgradedPdf = await PDFDocument.load(oldPdfBytes);
|
||||
upgradedPdf.setTitle('Upgraded PDF (1.7 features)');
|
||||
|
||||
// Add new page with modern features
|
||||
const page2 = upgradedPdf.addPage();
|
||||
page2.drawText('Upgraded Content', { x: 50, y: 700, size: 16 });
|
||||
|
||||
// Add transparency (1.4+ feature)
|
||||
page2.drawRectangle({
|
||||
x: 50,
|
||||
y: 600,
|
||||
width: 200,
|
||||
height: 50,
|
||||
color: { red: 0, green: 0.5, blue: 1 },
|
||||
opacity: 0.7
|
||||
});
|
||||
|
||||
// Add multiple attachments (enhanced in later versions)
|
||||
await upgradedPdf.attach(
|
||||
Buffer.from('<data>New attachment</data>', 'utf8'),
|
||||
'new_data.xml',
|
||||
{
|
||||
mimeType: 'application/xml',
|
||||
description: 'Added during upgrade',
|
||||
afRelationship: plugins.AFRelationship.Supplement
|
||||
}
|
||||
);
|
||||
|
||||
const upgradedBytes = await upgradedPdf.save();
|
||||
console.log(`Original size: ${oldPdfBytes.length} bytes`);
|
||||
console.log(`Upgraded size: ${upgradedBytes.length} bytes`);
|
||||
|
||||
// Test both versions work
|
||||
const einvoice = new EInvoice();
|
||||
await einvoice.loadFromPdfBuffer(upgradedBytes);
|
||||
console.log('Version upgrade test completed');
|
||||
|
||||
const elapsed = performance.now() - startTime;
|
||||
performanceTracker.addMeasurement('version-upgrade', elapsed);
|
||||
});
|
||||
|
||||
t.test('Compatibility edge cases', async () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
const { PDFDocument } = plugins;
|
||||
|
||||
// Test edge cases that might cause compatibility issues
|
||||
const edgeCases = [
|
||||
{
|
||||
name: 'Empty pages',
|
||||
test: async () => {
|
||||
const pdf = await PDFDocument.create();
|
||||
pdf.addPage(); // Empty page
|
||||
pdf.addPage(); // Another empty page
|
||||
return pdf.save();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Very long text',
|
||||
test: async () => {
|
||||
const pdf = await PDFDocument.create();
|
||||
const page = pdf.addPage();
|
||||
const longText = 'Lorem ipsum '.repeat(1000);
|
||||
page.drawText(longText.substring(0, 1000), { x: 50, y: 700, size: 8 });
|
||||
return pdf.save();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Special characters in metadata',
|
||||
test: async () => {
|
||||
const pdf = await PDFDocument.create();
|
||||
pdf.setTitle('Test™ © ® € £ ¥');
|
||||
pdf.setAuthor('Müller & Associés');
|
||||
pdf.setSubject('Invoice (2025) <test>');
|
||||
pdf.addPage();
|
||||
return pdf.save();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Maximum attachments',
|
||||
test: async () => {
|
||||
const pdf = await PDFDocument.create();
|
||||
pdf.addPage();
|
||||
// Add multiple small attachments
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await pdf.attach(
|
||||
Buffer.from(`<item>${i}</item>`, 'utf8'),
|
||||
`file${i}.xml`,
|
||||
{ mimeType: 'application/xml' }
|
||||
);
|
||||
}
|
||||
return pdf.save();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
for (const edgeCase of edgeCases) {
|
||||
try {
|
||||
console.log(`Testing edge case: ${edgeCase.name}`);
|
||||
const pdfBytes = await edgeCase.test();
|
||||
|
||||
const einvoice = new EInvoice();
|
||||
await einvoice.loadFromPdfBuffer(pdfBytes);
|
||||
console.log(`✓ ${edgeCase.name} - Success`);
|
||||
} catch (error) {
|
||||
console.log(`✗ ${edgeCase.name} - Failed:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
const elapsed = performance.now() - startTime;
|
||||
performanceTracker.addMeasurement('edge-cases', elapsed);
|
||||
});
|
||||
|
||||
// Print performance summary
|
||||
performanceTracker.printSummary();
|
||||
|
||||
// Performance assertions
|
||||
const avgTime = performanceTracker.getAverageTime();
|
||||
expect(avgTime).toBeLessThan(500); // Version compatibility tests may vary
|
||||
});
|
||||
|
||||
tap.start();
|
Reference in New Issue
Block a user