315 lines
11 KiB
TypeScript
Raw Normal View History

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js';
2025-05-30 04:29:13 +00:00
import { PerformanceTracker } from '../../helpers/performance.tracker.instance.js';
import { CorpusLoader } from '../../helpers/corpus.loader.js';
import * as path from 'path';
import * as fs from 'fs/promises';
/**
* Test ID: STD-09
* Test Description: ISO 19005 PDF/A-3 Compliance
* Priority: Medium
*
* This test validates compliance with ISO 19005 PDF/A-3 standard for
* archivable PDF documents with embedded files (used in ZUGFeRD/Factur-X).
*/
2025-05-30 04:29:13 +00:00
tap.test('STD-09: PDF/A-3 Compliance - should validate ISO 19005 PDF/A-3 standard', async () => {
const performanceTracker = new PerformanceTracker('STD-09: PDF/A-3 Compliance');
// Test 1: PDF/A-3 Identification
2025-05-30 04:29:13 +00:00
const identificationTest = await performanceTracker.measureAsync(
'pdfa3-identification',
async () => {
// Get PDF files from ZUGFeRD corpus
const pdfFiles = await CorpusLoader.getFiles('ZUGFERD_V2_CORRECT');
const testPdfs = pdfFiles.filter(f => f.endsWith('.pdf')).slice(0, 3);
let validCount = 0;
2025-05-30 04:29:13 +00:00
for (const pdfFile of testPdfs) {
const relPath = pdfFile.replace(process.cwd() + '/test/assets/corpus/', '');
const pdfBuffer = await CorpusLoader.loadFile(relPath);
// Basic PDF/A markers check
const pdfString = pdfBuffer.toString('latin1');
// Check for PDF/A identification
const hasPDFAMarker = pdfString.includes('pdfaid:part') ||
pdfString.includes('PDF/A') ||
pdfString.includes('19005');
// Check for XMP metadata
const hasXMP = pdfString.includes('<x:xmpmeta') ||
pdfString.includes('<?xpacket');
if (hasPDFAMarker || hasXMP) {
validCount++;
}
}
2025-05-30 04:29:13 +00:00
return { validCount, totalFiles: testPdfs.length };
}
);
expect(identificationTest.validCount).toBeGreaterThanOrEqual(0);
// Test 2: Embedded File Compliance
const embeddingTest = await performanceTracker.measureAsync(
'embedded-file-requirements',
async () => {
// Test embedding requirements
const embeddingRequirements = {
filename: 'factur-x.xml',
mimeType: 'text/xml',
relationship: 'Alternative',
description: 'Factur-X Invoice',
modDate: new Date().toISOString()
};
2025-05-30 04:29:13 +00:00
// Verify requirements
const validFilename = /\.(xml|XML)$/.test(embeddingRequirements.filename);
const validMimeType = embeddingRequirements.mimeType === 'text/xml';
const validRelationship = embeddingRequirements.relationship === 'Alternative';
2025-05-30 04:29:13 +00:00
return { validFilename, validMimeType, validRelationship };
}
2025-05-30 04:29:13 +00:00
);
2025-05-30 04:29:13 +00:00
expect(embeddingTest.validFilename).toBeTrue();
expect(embeddingTest.validMimeType).toBeTrue();
expect(embeddingTest.validRelationship).toBeTrue();
// Test 3: Color Space Compliance
2025-05-30 04:29:13 +00:00
const colorSpaceTest = await performanceTracker.measureAsync(
'color-space-requirements',
async () => {
// PDF/A-3 requires device-independent color spaces
const allowedColorSpaces = [
'DeviceGray',
'DeviceRGB',
'DeviceCMYK',
'CalGray',
'CalRGB',
'Lab',
'ICCBased'
];
const prohibitedColorSpaces = [
'Separation',
'DeviceN', // Allowed only with alternate space
'Pattern' // Allowed only with specific conditions
];
return {
allowedCount: allowedColorSpaces.length,
prohibitedCount: prohibitedColorSpaces.length
};
}
2025-05-30 04:29:13 +00:00
);
expect(colorSpaceTest.allowedCount).toBeGreaterThan(0);
// Test 4: Font Embedding Compliance
2025-05-30 04:29:13 +00:00
const fontTest = await performanceTracker.measureAsync(
'font-embedding-requirements',
async () => {
// PDF/A-3 requires all fonts to be embedded
const fontRequirements = {
embedding: 'All fonts must be embedded',
subset: 'Font subsetting is allowed',
encoding: 'Unicode mapping required for text extraction',
type: 'TrueType and Type 1 fonts supported'
};
2025-05-30 04:29:13 +00:00
return { requirementCount: Object.keys(fontRequirements).length };
}
2025-05-30 04:29:13 +00:00
);
expect(fontTest.requirementCount).toEqual(4);
// Test 5: Transparency and Layers Compliance
2025-05-30 04:29:13 +00:00
const transparencyTest = await performanceTracker.measureAsync(
'transparency-restrictions',
async () => {
// PDF/A-3 has specific requirements for transparency
const transparencyRules = {
blendModes: ['Normal', 'Compatible'], // Only these are allowed
transparency: 'Real transparency is allowed in PDF/A-3',
layers: 'Optional Content (layers) allowed with restrictions'
};
return {
allowedBlendModes: transparencyRules.blendModes.length,
rulesValid: transparencyRules.blendModes.includes('Normal')
};
}
);
expect(transparencyTest.rulesValid).toBeTrue();
// Test 6: Metadata Requirements
2025-05-30 04:29:13 +00:00
const metadataTest = await performanceTracker.measureAsync(
'metadata-requirements',
async () => {
const requiredMetadata = {
'dc:title': 'Document title',
'dc:creator': 'Document author',
'xmp:CreateDate': 'Creation date',
'xmp:ModifyDate': 'Modification date',
'pdf:Producer': 'PDF producer',
'pdfaid:part': '3', // PDF/A-3
'pdfaid:conformance': 'B' // Level B (basic)
};
// Test metadata structure
const xmpTemplate = `<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">
<pdfaid:part>3</pdfaid:part>
<pdfaid:conformance>B</pdfaid:conformance>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="r"?>`;
2025-05-30 04:29:13 +00:00
const hasPart3 = xmpTemplate.includes('pdfaid:part>3');
const hasConformanceB = xmpTemplate.includes('pdfaid:conformance>B');
return {
metadataCount: Object.keys(requiredMetadata).length,
hasPart3,
hasConformanceB
};
}
);
expect(metadataTest.hasPart3).toBeTrue();
expect(metadataTest.hasConformanceB).toBeTrue();
// Test 7: Attachment Relationship Types
2025-05-30 04:29:13 +00:00
const relationshipTest = await performanceTracker.measureAsync(
'attachment-relationships',
async () => {
// PDF/A-3 defines specific relationship types for embedded files
const validRelationships = [
'Source', // The embedded file is the source of the PDF
'Alternative', // Alternative representation (ZUGFeRD/Factur-X use this)
'Supplement', // Supplementary information
'Data', // Data file
'Unspecified' // When relationship is not specified
];
// ZUGFeRD/Factur-X specific
const zugferdRelationship = 'Alternative';
const isValid = validRelationships.includes(zugferdRelationship);
return {
relationshipCount: validRelationships.length,
zugferdValid: isValid
};
}
);
expect(relationshipTest.zugferdValid).toBeTrue();
// Test 8: Security Restrictions
2025-05-30 04:29:13 +00:00
const securityTest = await performanceTracker.measureAsync(
'security-restrictions',
async () => {
// PDF/A-3 prohibits encryption and security handlers
const securityRestrictions = {
encryption: 'Not allowed',
passwords: 'Not allowed',
permissions: 'Not allowed',
digitalSignatures: 'Allowed with restrictions'
};
2025-05-30 04:29:13 +00:00
return {
restrictionCount: Object.keys(securityRestrictions).length,
encryptionAllowed: false
};
}
2025-05-30 04:29:13 +00:00
);
expect(securityTest.encryptionAllowed).toBeFalse();
// Test 9: JavaScript and Actions
2025-05-30 04:29:13 +00:00
const actionsTest = await performanceTracker.measureAsync(
'javascript-actions-restrictions',
async () => {
// PDF/A-3 prohibits JavaScript and certain actions
const prohibitedFeatures = [
'JavaScript',
'Launch actions',
'Sound actions',
'Movie actions',
'ResetForm actions',
'ImportData actions'
];
const allowedActions = [
'GoTo actions', // Navigation within document
'GoToR actions', // With restrictions
'URI actions' // With restrictions
];
return {
prohibitedCount: prohibitedFeatures.length,
allowedCount: allowedActions.length
};
}
2025-05-30 04:29:13 +00:00
);
expect(actionsTest.prohibitedCount).toBeGreaterThan(0);
// Test 10: File Structure Compliance
2025-05-30 04:29:13 +00:00
const structureTest = await performanceTracker.measureAsync(
'file-structure-requirements',
async () => {
// Test basic PDF structure requirements
const structureRequirements = {
header: '%PDF-1.4 or higher',
eofMarker: '%%EOF',
xrefTable: 'Required',
linearized: 'Optional but recommended',
objectStreams: 'Allowed in PDF/A-3',
compressedXref: 'Allowed in PDF/A-3'
};
2025-05-30 04:29:13 +00:00
return {
requirementCount: Object.keys(structureRequirements).length,
structureValid: true
};
}
2025-05-30 04:29:13 +00:00
);
expect(structureTest.structureValid).toBeTrue();
2025-05-30 04:29:13 +00:00
// Generate summary
const summary = await performanceTracker.getSummary();
console.log('\n📊 PDF/A-3 Compliance Test Summary:');
if (summary) {
console.log(`✅ Total operations: ${summary.totalOperations}`);
console.log(`⏱️ Total duration: ${summary.totalDuration}ms`);
}
2025-05-30 04:29:13 +00:00
console.log(`📄 PDF identification: ${identificationTest.validCount}/${identificationTest.totalFiles} PDFs checked`);
console.log(`📎 Embedding requirements: All ${embeddingTest.validFilename ? '✓' : '✗'}`);
console.log(`🎨 Color spaces: ${colorSpaceTest.allowedCount} allowed types`);
console.log(`🔤 Font requirements: ${fontTest.requirementCount} rules defined`);
console.log(`🔍 Transparency: ${transparencyTest.allowedBlendModes} blend modes allowed`);
console.log(`📋 Metadata: ${metadataTest.metadataCount} required fields`);
console.log(`🔗 Relationships: ${relationshipTest.relationshipCount} types, ZUGFeRD uses "Alternative"`);
console.log(`🔒 Security: Encryption ${securityTest.encryptionAllowed ? 'allowed' : 'prohibited'}`);
console.log(`⚡ Actions: ${actionsTest.prohibitedCount} prohibited, ${actionsTest.allowedCount} allowed`);
console.log(`📁 Structure: ${structureTest.requirementCount} requirements defined`);
// Test completed
});
2025-05-30 04:29:13 +00:00
// Start the test
tap.start();
// Export for test runner compatibility
export default tap;