790 lines
30 KiB
TypeScript
790 lines
30 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import * as plugins from '../../../ts/plugins.ts';
|
|
import { EInvoice } from '../../../ts/classes.xinvoice.ts';
|
|
import { CorpusLoader } from '../../helpers/corpus.loader.ts';
|
|
import { PerformanceTracker } from '../../helpers/performance.tracker.ts';
|
|
|
|
const testTimeout = 300000; // 5 minutes timeout for PDF processing
|
|
|
|
// PDF-05: PDF/A-3 Creation
|
|
// Tests creation of PDF/A-3 compliant documents with embedded XML attachments
|
|
// according to ISO 19005-3 standard and ZUGFeRD/Factur-X requirements
|
|
|
|
tap.test('PDF-05: PDF/A-3 Creation - Basic PDF/A-3 Generation', async (tools) => {
|
|
const startTime = Date.now();
|
|
|
|
// Test basic PDF/A-3 creation functionality
|
|
try {
|
|
const sampleXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<ID>PDFA3-TEST-001</ID>
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
<AccountingSupplierParty>
|
|
<Party>
|
|
<PartyName>
|
|
<Name>PDF/A-3 Test Supplier</Name>
|
|
</PartyName>
|
|
<PostalAddress>
|
|
<StreetName>Test Street 123</StreetName>
|
|
<CityName>Test City</CityName>
|
|
<PostalZone>12345</PostalZone>
|
|
<Country>
|
|
<IdentificationCode>DE</IdentificationCode>
|
|
</Country>
|
|
</PostalAddress>
|
|
</Party>
|
|
</AccountingSupplierParty>
|
|
<AccountingCustomerParty>
|
|
<Party>
|
|
<PartyName>
|
|
<Name>PDF/A-3 Test Customer</Name>
|
|
</PartyName>
|
|
</Party>
|
|
</AccountingCustomerParty>
|
|
<InvoiceLine>
|
|
<ID>1</ID>
|
|
<InvoicedQuantity unitCode="C62">1</InvoicedQuantity>
|
|
<LineExtensionAmount currencyID="EUR">100.00</LineExtensionAmount>
|
|
<Item>
|
|
<Name>PDF/A-3 Test Item</Name>
|
|
</Item>
|
|
<Price>
|
|
<PriceAmount currencyID="EUR">100.00</PriceAmount>
|
|
</Price>
|
|
</InvoiceLine>
|
|
<TaxTotal>
|
|
<TaxAmount currencyID="EUR">19.00</TaxAmount>
|
|
</TaxTotal>
|
|
<LegalMonetaryTotal>
|
|
<LineExtensionAmount currencyID="EUR">100.00</LineExtensionAmount>
|
|
<TaxExclusiveAmount currencyID="EUR">100.00</TaxExclusiveAmount>
|
|
<TaxInclusiveAmount currencyID="EUR">119.00</TaxInclusiveAmount>
|
|
<PayableAmount currencyID="EUR">119.00</PayableAmount>
|
|
</LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
const invoice = new EInvoice();
|
|
const parseResult = await invoice.fromXmlString(sampleXml);
|
|
expect(parseResult).toBeTruthy();
|
|
|
|
// Test PDF/A-3 creation if supported
|
|
if (typeof invoice.createPdfA3 === 'function') {
|
|
tools.log('Testing PDF/A-3 creation...');
|
|
|
|
const outputPath = plugins.path.join(process.cwd(), '.nogit', 'test-pdfa3-basic.pdf');
|
|
await plugins.fs.ensureDir(plugins.path.dirname(outputPath));
|
|
|
|
try {
|
|
const pdfA3Options = {
|
|
outputPath: outputPath,
|
|
xmlContent: sampleXml,
|
|
attachmentName: 'ZUGFeRD-invoice.xml',
|
|
pdfA3Compliance: true,
|
|
title: 'Electronic Invoice PDFA3-TEST-001',
|
|
author: 'EInvoice Test Suite',
|
|
subject: 'PDF/A-3 compliant invoice',
|
|
keywords: 'invoice, electronic, PDF/A-3, ZUGFeRD'
|
|
};
|
|
|
|
const creationResult = await invoice.createPdfA3(pdfA3Options);
|
|
|
|
if (creationResult) {
|
|
tools.log('✓ PDF/A-3 creation completed');
|
|
|
|
// Verify output file
|
|
const outputExists = await plugins.fs.pathExists(outputPath);
|
|
if (outputExists) {
|
|
const outputStats = await plugins.fs.stat(outputPath);
|
|
tools.log(`✓ PDF/A-3 file created: ${(outputStats.size / 1024).toFixed(1)}KB`);
|
|
|
|
// Basic PDF validation (check if it starts with PDF header)
|
|
const pdfHeader = await plugins.fs.readFile(outputPath, { encoding: 'binary' });
|
|
if (pdfHeader.startsWith('%PDF-')) {
|
|
tools.log('✓ Valid PDF header detected');
|
|
|
|
// Check for PDF/A-3 markers if possible
|
|
const pdfContent = pdfHeader.substring(0, 1024);
|
|
if (pdfContent.includes('PDF/A-3') || pdfContent.includes('PDFA-3')) {
|
|
tools.log('✓ PDF/A-3 markers detected');
|
|
}
|
|
} else {
|
|
tools.log('⚠ Invalid PDF header');
|
|
}
|
|
|
|
// Test XML extraction from created PDF/A-3
|
|
try {
|
|
const extractionInvoice = new EInvoice();
|
|
const extractionResult = await extractionInvoice.fromFile(outputPath);
|
|
|
|
if (extractionResult) {
|
|
const extractedXml = await extractionInvoice.toXmlString();
|
|
if (extractedXml.includes('PDFA3-TEST-001')) {
|
|
tools.log('✓ XML successfully extracted from PDF/A-3');
|
|
} else {
|
|
tools.log('⚠ Extracted XML does not contain expected content');
|
|
}
|
|
} else {
|
|
tools.log('⚠ Could not extract XML from created PDF/A-3');
|
|
}
|
|
} catch (extractionError) {
|
|
tools.log(`⚠ XML extraction test failed: ${extractionError.message}`);
|
|
}
|
|
|
|
// Clean up
|
|
await plugins.fs.remove(outputPath);
|
|
|
|
} else {
|
|
tools.log('⚠ PDF/A-3 file not created');
|
|
}
|
|
|
|
} else {
|
|
tools.log('⚠ PDF/A-3 creation returned no result');
|
|
}
|
|
|
|
} catch (creationError) {
|
|
tools.log(`⚠ PDF/A-3 creation failed: ${creationError.message}`);
|
|
}
|
|
|
|
} else if (typeof invoice.toPdf === 'function') {
|
|
tools.log('⚠ Specific PDF/A-3 creation not available, testing general PDF creation...');
|
|
|
|
try {
|
|
const pdfResult = await invoice.toPdf({
|
|
pdfACompliance: 'PDF/A-3'
|
|
});
|
|
|
|
if (pdfResult) {
|
|
tools.log('✓ General PDF creation with PDF/A-3 compliance completed');
|
|
}
|
|
} catch (pdfError) {
|
|
tools.log(`⚠ General PDF creation failed: ${pdfError.message}`);
|
|
}
|
|
|
|
} else {
|
|
tools.log('⚠ PDF/A-3 creation functionality not available');
|
|
}
|
|
|
|
} catch (error) {
|
|
tools.log(`Basic PDF/A-3 creation test failed: ${error.message}`);
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
PerformanceTracker.recordMetric('pdfa3-creation-basic', duration);
|
|
});
|
|
|
|
tap.test('PDF-05: PDF/A-3 Creation - Compliance Levels', async (tools) => {
|
|
const startTime = Date.now();
|
|
|
|
// Test different PDF/A-3 compliance levels (A, B, U)
|
|
const complianceLevels = [
|
|
{
|
|
level: 'PDF/A-3B',
|
|
description: 'PDF/A-3 Level B (visual appearance)',
|
|
strictness: 'medium'
|
|
},
|
|
{
|
|
level: 'PDF/A-3A',
|
|
description: 'PDF/A-3 Level A (accessibility)',
|
|
strictness: 'high'
|
|
},
|
|
{
|
|
level: 'PDF/A-3U',
|
|
description: 'PDF/A-3 Level U (Unicode)',
|
|
strictness: 'medium'
|
|
}
|
|
];
|
|
|
|
const testXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<ID>COMPLIANCE-TEST-001</ID>
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
<LegalMonetaryTotal>
|
|
<PayableAmount currencyID="EUR">100.00</PayableAmount>
|
|
</LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
for (const compliance of complianceLevels) {
|
|
tools.log(`Testing ${compliance.description}...`);
|
|
|
|
try {
|
|
const invoice = new EInvoice();
|
|
await invoice.fromXmlString(testXml);
|
|
|
|
if (typeof invoice.createPdfA3 === 'function') {
|
|
const outputPath = plugins.path.join(process.cwd(), '.nogit', `test-${compliance.level.toLowerCase().replace(/\//g, '-')}.pdf`);
|
|
await plugins.fs.ensureDir(plugins.path.dirname(outputPath));
|
|
|
|
const complianceOptions = {
|
|
outputPath: outputPath,
|
|
xmlContent: testXml,
|
|
attachmentName: 'invoice.xml',
|
|
complianceLevel: compliance.level,
|
|
title: `${compliance.level} Test Invoice`,
|
|
validateCompliance: true
|
|
};
|
|
|
|
try {
|
|
const creationResult = await invoice.createPdfA3(complianceOptions);
|
|
|
|
if (creationResult) {
|
|
tools.log(`✓ ${compliance.level} creation completed`);
|
|
|
|
const outputExists = await plugins.fs.pathExists(outputPath);
|
|
if (outputExists) {
|
|
const outputStats = await plugins.fs.stat(outputPath);
|
|
tools.log(` File size: ${(outputStats.size / 1024).toFixed(1)}KB`);
|
|
|
|
// Basic compliance validation
|
|
const pdfContent = await plugins.fs.readFile(outputPath, { encoding: 'binary' });
|
|
const headerSection = pdfContent.substring(0, 2048);
|
|
|
|
// Look for PDF/A compliance indicators
|
|
if (headerSection.includes('PDF/A-3') ||
|
|
headerSection.includes('PDFA-3') ||
|
|
headerSection.includes(compliance.level)) {
|
|
tools.log(` ✓ ${compliance.level} compliance indicators found`);
|
|
} else {
|
|
tools.log(` ⚠ ${compliance.level} compliance indicators not clearly detected`);
|
|
}
|
|
|
|
// Clean up
|
|
await plugins.fs.remove(outputPath);
|
|
|
|
} else {
|
|
tools.log(` ⚠ ${compliance.level} file not created`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(`⚠ ${compliance.level} creation returned no result`);
|
|
}
|
|
|
|
} catch (complianceError) {
|
|
tools.log(`⚠ ${compliance.level} creation failed: ${complianceError.message}`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(`⚠ ${compliance.level} creation not supported`);
|
|
}
|
|
|
|
} catch (error) {
|
|
tools.log(`✗ ${compliance.level} test failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
PerformanceTracker.recordMetric('pdfa3-creation-compliance-levels', duration);
|
|
});
|
|
|
|
tap.test('PDF-05: PDF/A-3 Creation - ZUGFeRD Profile Creation', async (tools) => {
|
|
const startTime = Date.now();
|
|
|
|
// Test PDF/A-3 creation with specific ZUGFeRD/Factur-X profiles
|
|
const zugferdProfiles = [
|
|
{
|
|
profile: 'MINIMUM',
|
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
|
<CrossIndustryInvoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
|
<ExchangedDocumentContext>
|
|
<GuidelineSpecifiedDocumentContextParameter>
|
|
<ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:minimum</ID>
|
|
</GuidelineSpecifiedDocumentContextParameter>
|
|
</ExchangedDocumentContext>
|
|
<ExchangedDocument>
|
|
<ID>ZUGFERD-MIN-001</ID>
|
|
<TypeCode>380</TypeCode>
|
|
<IssueDateTime>
|
|
<DateTimeString format="102">20240101</DateTimeString>
|
|
</IssueDateTime>
|
|
</ExchangedDocument>
|
|
<SupplyChainTradeTransaction>
|
|
<ApplicableHeaderTradeSettlement>
|
|
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
|
|
<SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
<DuePayableAmount>100.00</DuePayableAmount>
|
|
</SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
</ApplicableHeaderTradeSettlement>
|
|
</SupplyChainTradeTransaction>
|
|
</CrossIndustryInvoice>`
|
|
},
|
|
{
|
|
profile: 'BASIC',
|
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
|
<CrossIndustryInvoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
|
<ExchangedDocumentContext>
|
|
<GuidelineSpecifiedDocumentContextParameter>
|
|
<ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:basic</ID>
|
|
</GuidelineSpecifiedDocumentContextParameter>
|
|
</ExchangedDocumentContext>
|
|
<ExchangedDocument>
|
|
<ID>ZUGFERD-BASIC-001</ID>
|
|
<TypeCode>380</TypeCode>
|
|
<IssueDateTime>
|
|
<DateTimeString format="102">20240101</DateTimeString>
|
|
</IssueDateTime>
|
|
</ExchangedDocument>
|
|
<SupplyChainTradeTransaction>
|
|
<ApplicableHeaderTradeAgreement>
|
|
<SellerTradeParty>
|
|
<Name>ZUGFeRD Test Supplier</Name>
|
|
</SellerTradeParty>
|
|
<BuyerTradeParty>
|
|
<Name>ZUGFeRD Test Customer</Name>
|
|
</BuyerTradeParty>
|
|
</ApplicableHeaderTradeAgreement>
|
|
<ApplicableHeaderTradeSettlement>
|
|
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
|
|
<SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
<TaxBasisTotalAmount>100.00</TaxBasisTotalAmount>
|
|
<TaxTotalAmount currencyID="EUR">19.00</TaxTotalAmount>
|
|
<GrandTotalAmount>119.00</GrandTotalAmount>
|
|
<DuePayableAmount>119.00</DuePayableAmount>
|
|
</SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
</ApplicableHeaderTradeSettlement>
|
|
</SupplyChainTradeTransaction>
|
|
</CrossIndustryInvoice>`
|
|
},
|
|
{
|
|
profile: 'COMFORT',
|
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
|
<CrossIndustryInvoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
|
<ExchangedDocumentContext>
|
|
<GuidelineSpecifiedDocumentContextParameter>
|
|
<ID>urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p1:comfort</ID>
|
|
</GuidelineSpecifiedDocumentContextParameter>
|
|
</ExchangedDocumentContext>
|
|
<ExchangedDocument>
|
|
<ID>ZUGFERD-COMFORT-001</ID>
|
|
<TypeCode>380</TypeCode>
|
|
<IssueDateTime>
|
|
<DateTimeString format="102">20240101</DateTimeString>
|
|
</IssueDateTime>
|
|
</ExchangedDocument>
|
|
<SupplyChainTradeTransaction>
|
|
<IncludedSupplyChainTradeLineItem>
|
|
<AssociatedDocumentLineDocument>
|
|
<LineID>1</LineID>
|
|
</AssociatedDocumentLineDocument>
|
|
<SpecifiedTradeProduct>
|
|
<Name>ZUGFeRD Test Product</Name>
|
|
</SpecifiedTradeProduct>
|
|
<SpecifiedLineTradeAgreement>
|
|
<NetPriceProductTradePrice>
|
|
<ChargeAmount>100.00</ChargeAmount>
|
|
</NetPriceProductTradePrice>
|
|
</SpecifiedLineTradeAgreement>
|
|
<SpecifiedLineTradeSettlement>
|
|
<SpecifiedTradeSettlementLineMonetarySummation>
|
|
<LineTotalAmount>100.00</LineTotalAmount>
|
|
</SpecifiedTradeSettlementLineMonetarySummation>
|
|
</SpecifiedLineTradeSettlement>
|
|
</IncludedSupplyChainTradeLineItem>
|
|
<ApplicableHeaderTradeSettlement>
|
|
<InvoiceCurrencyCode>EUR</InvoiceCurrencyCode>
|
|
<SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
<LineTotalAmount>100.00</LineTotalAmount>
|
|
<TaxBasisTotalAmount>100.00</TaxBasisTotalAmount>
|
|
<TaxTotalAmount currencyID="EUR">19.00</TaxTotalAmount>
|
|
<GrandTotalAmount>119.00</GrandTotalAmount>
|
|
<DuePayableAmount>119.00</DuePayableAmount>
|
|
</SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
</ApplicableHeaderTradeSettlement>
|
|
</SupplyChainTradeTransaction>
|
|
</CrossIndustryInvoice>`
|
|
}
|
|
];
|
|
|
|
for (const zugferdTest of zugferdProfiles) {
|
|
tools.log(`Testing ZUGFeRD ${zugferdTest.profile} profile PDF/A-3 creation...`);
|
|
|
|
try {
|
|
const invoice = new EInvoice();
|
|
await invoice.fromXmlString(zugferdTest.xml);
|
|
|
|
if (typeof invoice.createPdfA3 === 'function') {
|
|
const outputPath = plugins.path.join(process.cwd(), '.nogit', `test-zugferd-${zugferdTest.profile.toLowerCase()}.pdf`);
|
|
await plugins.fs.ensureDir(plugins.path.dirname(outputPath));
|
|
|
|
const zugferdOptions = {
|
|
outputPath: outputPath,
|
|
xmlContent: zugferdTest.xml,
|
|
attachmentName: 'ZUGFeRD-invoice.xml',
|
|
zugferdProfile: zugferdTest.profile,
|
|
zugferdVersion: '2.1',
|
|
complianceLevel: 'PDF/A-3B',
|
|
title: `ZUGFeRD ${zugferdTest.profile} Invoice`,
|
|
conformanceLevel: 'PDFA_3B'
|
|
};
|
|
|
|
try {
|
|
const creationResult = await invoice.createPdfA3(zugferdOptions);
|
|
|
|
if (creationResult) {
|
|
tools.log(`✓ ZUGFeRD ${zugferdTest.profile} PDF/A-3 creation completed`);
|
|
|
|
const outputExists = await plugins.fs.pathExists(outputPath);
|
|
if (outputExists) {
|
|
const outputStats = await plugins.fs.stat(outputPath);
|
|
tools.log(` File size: ${(outputStats.size / 1024).toFixed(1)}KB`);
|
|
|
|
// Test round-trip (extraction from created PDF)
|
|
try {
|
|
const extractionInvoice = new EInvoice();
|
|
const extractionResult = await extractionInvoice.fromFile(outputPath);
|
|
|
|
if (extractionResult) {
|
|
const extractedXml = await extractionInvoice.toXmlString();
|
|
const expectedId = `ZUGFERD-${zugferdTest.profile}-001`;
|
|
|
|
if (extractedXml.includes(expectedId)) {
|
|
tools.log(` ✓ Round-trip successful - extracted XML contains ${expectedId}`);
|
|
} else {
|
|
tools.log(` ⚠ Round-trip issue - expected ID ${expectedId} not found`);
|
|
}
|
|
|
|
// Check for profile-specific elements
|
|
if (zugferdTest.profile === 'COMFORT' && extractedXml.includes('IncludedSupplyChainTradeLineItem')) {
|
|
tools.log(` ✓ COMFORT profile line items preserved`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(` ⚠ Round-trip failed - could not extract XML`);
|
|
}
|
|
} catch (extractionError) {
|
|
tools.log(` ⚠ Round-trip test failed: ${extractionError.message}`);
|
|
}
|
|
|
|
// Clean up
|
|
await plugins.fs.remove(outputPath);
|
|
|
|
} else {
|
|
tools.log(` ⚠ ZUGFeRD ${zugferdTest.profile} file not created`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(`⚠ ZUGFeRD ${zugferdTest.profile} creation returned no result`);
|
|
}
|
|
|
|
} catch (creationError) {
|
|
tools.log(`⚠ ZUGFeRD ${zugferdTest.profile} creation failed: ${creationError.message}`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(`⚠ ZUGFeRD ${zugferdTest.profile} PDF/A-3 creation not supported`);
|
|
}
|
|
|
|
} catch (error) {
|
|
tools.log(`✗ ZUGFeRD ${zugferdTest.profile} test failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
PerformanceTracker.recordMetric('pdfa3-creation-zugferd-profiles', duration);
|
|
});
|
|
|
|
tap.test('PDF-05: PDF/A-3 Creation - Metadata and Accessibility', async (tools) => {
|
|
const startTime = Date.now();
|
|
|
|
// Test PDF/A-3 creation with comprehensive metadata and accessibility features
|
|
const testXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<ID>METADATA-ACCESSIBILITY-001</ID>
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
<LegalMonetaryTotal>
|
|
<PayableAmount currencyID="EUR">100.00</PayableAmount>
|
|
</LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
const metadataTests = [
|
|
{
|
|
name: 'Comprehensive Metadata',
|
|
options: {
|
|
title: 'Electronic Invoice METADATA-ACCESSIBILITY-001',
|
|
author: 'EInvoice Test Suite',
|
|
subject: 'PDF/A-3 compliant invoice with comprehensive metadata',
|
|
keywords: 'invoice, electronic, PDF/A-3, ZUGFeRD, accessible',
|
|
creator: 'EInvoice PDF Generator',
|
|
producer: 'EInvoice Test Framework',
|
|
creationDate: new Date('2024-01-01'),
|
|
modificationDate: new Date(),
|
|
language: 'en-US'
|
|
}
|
|
},
|
|
{
|
|
name: 'Accessibility Features',
|
|
options: {
|
|
title: 'Accessible Electronic Invoice',
|
|
tagged: true, // Structured PDF for screen readers
|
|
displayDocTitle: true,
|
|
linearized: true, // Fast web view
|
|
complianceLevel: 'PDF/A-3A', // Accessibility compliance
|
|
structuredPdf: true
|
|
}
|
|
},
|
|
{
|
|
name: 'Internationalization',
|
|
options: {
|
|
title: 'Elektronische Rechnung / Facture Électronique',
|
|
language: 'de-DE',
|
|
keywords: 'Rechnung, elektronisch, PDF/A-3, ZUGFeRD, Factur-X',
|
|
unicodeSupport: true,
|
|
characterEncoding: 'UTF-8'
|
|
}
|
|
}
|
|
];
|
|
|
|
for (const metadataTest of metadataTests) {
|
|
tools.log(`Testing ${metadataTest.name}...`);
|
|
|
|
try {
|
|
const invoice = new EInvoice();
|
|
await invoice.fromXmlString(testXml);
|
|
|
|
if (typeof invoice.createPdfA3 === 'function') {
|
|
const outputPath = plugins.path.join(process.cwd(), '.nogit', `test-${metadataTest.name.toLowerCase().replace(/\s+/g, '-')}.pdf`);
|
|
await plugins.fs.ensureDir(plugins.path.dirname(outputPath));
|
|
|
|
const creationOptions = {
|
|
outputPath: outputPath,
|
|
xmlContent: testXml,
|
|
attachmentName: 'invoice.xml',
|
|
complianceLevel: 'PDF/A-3B',
|
|
...metadataTest.options
|
|
};
|
|
|
|
try {
|
|
const creationResult = await invoice.createPdfA3(creationOptions);
|
|
|
|
if (creationResult) {
|
|
tools.log(`✓ ${metadataTest.name} PDF/A-3 creation completed`);
|
|
|
|
const outputExists = await plugins.fs.pathExists(outputPath);
|
|
if (outputExists) {
|
|
const outputStats = await plugins.fs.stat(outputPath);
|
|
tools.log(` File size: ${(outputStats.size / 1024).toFixed(1)}KB`);
|
|
|
|
// Basic metadata validation by reading PDF content
|
|
const pdfContent = await plugins.fs.readFile(outputPath, { encoding: 'binary' });
|
|
|
|
// Check for metadata presence (simplified check)
|
|
if (metadataTest.options.title && pdfContent.includes(metadataTest.options.title)) {
|
|
tools.log(` ✓ Title metadata preserved`);
|
|
}
|
|
|
|
if (metadataTest.options.author && pdfContent.includes(metadataTest.options.author)) {
|
|
tools.log(` ✓ Author metadata preserved`);
|
|
}
|
|
|
|
if (metadataTest.options.keywords && metadataTest.options.keywords.split(',').some(keyword =>
|
|
pdfContent.includes(keyword.trim()))) {
|
|
tools.log(` ✓ Keywords metadata preserved`);
|
|
}
|
|
|
|
// Check for accessibility features
|
|
if (metadataTest.options.tagged && (pdfContent.includes('/StructTreeRoot') || pdfContent.includes('/Marked'))) {
|
|
tools.log(` ✓ PDF structure/tagging detected`);
|
|
}
|
|
|
|
// Check for compliance level
|
|
if (metadataTest.options.complianceLevel && pdfContent.includes(metadataTest.options.complianceLevel)) {
|
|
tools.log(` ✓ Compliance level preserved`);
|
|
}
|
|
|
|
// Clean up
|
|
await plugins.fs.remove(outputPath);
|
|
|
|
} else {
|
|
tools.log(` ⚠ ${metadataTest.name} file not created`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(`⚠ ${metadataTest.name} creation returned no result`);
|
|
}
|
|
|
|
} catch (creationError) {
|
|
tools.log(`⚠ ${metadataTest.name} creation failed: ${creationError.message}`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(`⚠ ${metadataTest.name} PDF/A-3 creation not supported`);
|
|
}
|
|
|
|
} catch (error) {
|
|
tools.log(`✗ ${metadataTest.name} test failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
PerformanceTracker.recordMetric('pdfa3-creation-metadata-accessibility', duration);
|
|
});
|
|
|
|
tap.test('PDF-05: PDF/A-3 Creation - Performance and Size Optimization', async (tools) => {
|
|
const startTime = Date.now();
|
|
|
|
// Test PDF/A-3 creation performance with different optimization settings
|
|
const optimizationTests = [
|
|
{
|
|
name: 'Standard Quality',
|
|
options: {
|
|
imageQuality: 'standard',
|
|
compression: 'standard',
|
|
optimizeFor: 'balanced'
|
|
}
|
|
},
|
|
{
|
|
name: 'High Quality',
|
|
options: {
|
|
imageQuality: 'high',
|
|
compression: 'minimal',
|
|
optimizeFor: 'quality'
|
|
}
|
|
},
|
|
{
|
|
name: 'Small Size',
|
|
options: {
|
|
imageQuality: 'medium',
|
|
compression: 'maximum',
|
|
optimizeFor: 'size'
|
|
}
|
|
},
|
|
{
|
|
name: 'Fast Generation',
|
|
options: {
|
|
imageQuality: 'medium',
|
|
compression: 'fast',
|
|
optimizeFor: 'speed'
|
|
}
|
|
}
|
|
];
|
|
|
|
const testXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<ID>PERFORMANCE-TEST-001</ID>
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
<LegalMonetaryTotal>
|
|
<PayableAmount currencyID="EUR">100.00</PayableAmount>
|
|
</LegalMonetaryTotal>
|
|
</Invoice>`;
|
|
|
|
const performanceResults = [];
|
|
|
|
for (const optimizationTest of optimizationTests) {
|
|
tools.log(`Testing ${optimizationTest.name} optimization...`);
|
|
|
|
try {
|
|
const invoice = new EInvoice();
|
|
await invoice.fromXmlString(testXml);
|
|
|
|
if (typeof invoice.createPdfA3 === 'function') {
|
|
const outputPath = plugins.path.join(process.cwd(), '.nogit', `test-${optimizationTest.name.toLowerCase().replace(/\s+/g, '-')}.pdf`);
|
|
await plugins.fs.ensureDir(plugins.path.dirname(outputPath));
|
|
|
|
const creationStartTime = Date.now();
|
|
|
|
const creationOptions = {
|
|
outputPath: outputPath,
|
|
xmlContent: testXml,
|
|
attachmentName: 'invoice.xml',
|
|
complianceLevel: 'PDF/A-3B',
|
|
title: `Performance Test - ${optimizationTest.name}`,
|
|
...optimizationTest.options
|
|
};
|
|
|
|
try {
|
|
const creationResult = await invoice.createPdfA3(creationOptions);
|
|
const creationTime = Date.now() - creationStartTime;
|
|
|
|
if (creationResult) {
|
|
const outputExists = await plugins.fs.pathExists(outputPath);
|
|
if (outputExists) {
|
|
const outputStats = await plugins.fs.stat(outputPath);
|
|
const fileSizeKB = outputStats.size / 1024;
|
|
|
|
const result = {
|
|
name: optimizationTest.name,
|
|
creationTimeMs: creationTime,
|
|
fileSizeKB: fileSizeKB,
|
|
...optimizationTest.options
|
|
};
|
|
|
|
performanceResults.push(result);
|
|
|
|
tools.log(` Creation time: ${creationTime}ms`);
|
|
tools.log(` File size: ${fileSizeKB.toFixed(1)}KB`);
|
|
tools.log(` Performance ratio: ${(creationTime / fileSizeKB).toFixed(2)}ms/KB`);
|
|
|
|
// Clean up
|
|
await plugins.fs.remove(outputPath);
|
|
|
|
} else {
|
|
tools.log(` ⚠ ${optimizationTest.name} file not created`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(`⚠ ${optimizationTest.name} creation returned no result`);
|
|
}
|
|
|
|
} catch (creationError) {
|
|
tools.log(`⚠ ${optimizationTest.name} creation failed: ${creationError.message}`);
|
|
}
|
|
|
|
} else {
|
|
tools.log(`⚠ ${optimizationTest.name} PDF/A-3 creation not supported`);
|
|
}
|
|
|
|
} catch (error) {
|
|
tools.log(`✗ ${optimizationTest.name} test failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Analyze performance results
|
|
if (performanceResults.length > 0) {
|
|
tools.log(`\nPDF/A-3 Performance Analysis:`);
|
|
|
|
const fastestCreation = performanceResults.reduce((min, r) => r.creationTimeMs < min.creationTimeMs ? r : min);
|
|
const smallestFile = performanceResults.reduce((min, r) => r.fileSizeKB < min.fileSizeKB ? r : min);
|
|
const avgCreationTime = performanceResults.reduce((sum, r) => sum + r.creationTimeMs, 0) / performanceResults.length;
|
|
const avgFileSize = performanceResults.reduce((sum, r) => sum + r.fileSizeKB, 0) / performanceResults.length;
|
|
|
|
tools.log(`- Fastest creation: ${fastestCreation.name} (${fastestCreation.creationTimeMs}ms)`);
|
|
tools.log(`- Smallest file: ${smallestFile.name} (${smallestFile.fileSizeKB.toFixed(1)}KB)`);
|
|
tools.log(`- Average creation time: ${avgCreationTime.toFixed(1)}ms`);
|
|
tools.log(`- Average file size: ${avgFileSize.toFixed(1)}KB`);
|
|
|
|
// Performance expectations
|
|
expect(avgCreationTime).toBeLessThan(5000); // 5 seconds max average
|
|
expect(avgFileSize).toBeLessThan(500); // 500KB max average
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
PerformanceTracker.recordMetric('pdfa3-creation-performance-optimization', duration);
|
|
});
|
|
|
|
tap.test('PDF-05: Performance Summary', async (tools) => {
|
|
const operations = [
|
|
'pdfa3-creation-basic',
|
|
'pdfa3-creation-compliance-levels',
|
|
'pdfa3-creation-zugferd-profiles',
|
|
'pdfa3-creation-metadata-accessibility',
|
|
'pdfa3-creation-performance-optimization'
|
|
];
|
|
|
|
tools.log(`\n=== PDF/A-3 Creation Performance Summary ===`);
|
|
|
|
for (const operation of operations) {
|
|
const summary = await PerformanceTracker.getSummary(operation);
|
|
if (summary) {
|
|
tools.log(`${operation}:`);
|
|
tools.log(` avg=${summary.average}ms, min=${summary.min}ms, max=${summary.max}ms, p95=${summary.p95}ms`);
|
|
}
|
|
}
|
|
|
|
tools.log(`\nPDF/A-3 creation testing completed.`);
|
|
}); |