import { tap, expect } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; 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). */ 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 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; 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(' { // Test embedding requirements const embeddingRequirements = { filename: 'factur-x.xml', mimeType: 'text/xml', relationship: 'Alternative', description: 'Factur-X Invoice', modDate: new Date().toISOString() }; // Verify requirements const validFilename = /\.(xml|XML)$/.test(embeddingRequirements.filename); const validMimeType = embeddingRequirements.mimeType === 'text/xml'; const validRelationship = embeddingRequirements.relationship === 'Alternative'; return { validFilename, validMimeType, validRelationship }; } ); expect(embeddingTest.validFilename).toBeTrue(); expect(embeddingTest.validMimeType).toBeTrue(); expect(embeddingTest.validRelationship).toBeTrue(); // Test 3: Color Space Compliance 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 }; } ); expect(colorSpaceTest.allowedCount).toBeGreaterThan(0); // Test 4: Font Embedding Compliance 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' }; return { requirementCount: Object.keys(fontRequirements).length }; } ); expect(fontTest.requirementCount).toEqual(4); // Test 5: Transparency and Layers Compliance 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 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 = ` 3 B `; 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 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 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' }; return { restrictionCount: Object.keys(securityRestrictions).length, encryptionAllowed: false }; } ); expect(securityTest.encryptionAllowed).toBeFalse(); // Test 9: JavaScript and Actions 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 }; } ); expect(actionsTest.prohibitedCount).toBeGreaterThan(0); // Test 10: File Structure Compliance 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' }; return { requirementCount: Object.keys(structureRequirements).length, structureValid: true }; } ); expect(structureTest.structureValid).toBeTrue(); // 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`); } 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 }); // Start the test tap.start(); // Export for test runner compatibility export default tap;