import { tap, expect } from '@git.zone/tstest/tapbundle'; import { FacturXValidator, FacturXProfile } from '../ts/formats/validation/facturx.validator.js'; import type { EInvoice } from '../ts/einvoice.js'; tap.test('Factur-X Validator - basic instantiation', async () => { const validator = FacturXValidator.create(); expect(validator).toBeInstanceOf(FacturXValidator); // Singleton pattern const validator2 = FacturXValidator.create(); expect(validator2).toEqual(validator); }); tap.test('Factur-X Validator - profile detection', async () => { const validator = FacturXValidator.create(); // MINIMUM profile const minInvoice: Partial = { metadata: { profileId: 'urn:facturx:minimum:2017' } }; expect(validator.detectProfile(minInvoice as EInvoice)).toEqual(FacturXProfile.MINIMUM); // BASIC profile const basicInvoice: Partial = { metadata: { profileId: 'urn:facturx:basic:2017' } }; expect(validator.detectProfile(basicInvoice as EInvoice)).toEqual(FacturXProfile.BASIC); // EN16931 profile (Comfort) const en16931Invoice: Partial = { metadata: { profileId: 'urn:facturx:comfort:2017' } }; expect(validator.detectProfile(en16931Invoice as EInvoice)).toEqual(FacturXProfile.EN16931); // EXTENDED profile const extendedInvoice: Partial = { metadata: { profileId: 'urn:facturx:extended:2017' } }; expect(validator.detectProfile(extendedInvoice as EInvoice)).toEqual(FacturXProfile.EXTENDED); // Non-Factur-X invoice const otherInvoice: Partial = { metadata: { profileId: 'urn:cen.eu:en16931:2017' } }; expect(validator.detectProfile(otherInvoice as EInvoice)).toEqual(null); }); tap.test('Factur-X Validator - MINIMUM profile validation', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:minimum:2017' }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), accountingDocType: 'invoice', currency: 'EUR', from: { type: 'company', name: 'Test Seller', vatNumber: 'DE123456789' }, to: { type: 'company', name: 'Test Buyer' }, totalInvoiceAmount: 119.00, totalNetAmount: 100.00, totalVatAmount: 19.00 }; const results = validator.validateFacturX(invoice as EInvoice, FacturXProfile.MINIMUM); const errors = results.filter(r => r.severity === 'error'); console.log('MINIMUM profile validation errors:', errors); expect(errors.length).toEqual(0); }); tap.test('Factur-X Validator - MINIMUM profile missing fields', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:minimum:2017' }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), // Missing required fields for MINIMUM }; const results = validator.validateFacturX(invoice as EInvoice, FacturXProfile.MINIMUM); const errors = results.filter(r => r.severity === 'error'); expect(errors.length).toBeGreaterThan(0); expect(errors.some(e => e.field === 'currency')).toBeTrue(); expect(errors.some(e => e.field === 'from.name')).toBeTrue(); }); tap.test('Factur-X Validator - BASIC profile validation', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:basic:2017' }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), accountingDocType: 'invoice', currency: 'EUR', dueDate: new Date('2025-02-11'), from: { type: 'company', name: 'Test Seller', vatNumber: 'DE123456789', address: 'Test Street 1', country: 'DE' }, to: { type: 'company', name: 'Test Buyer', address: 'Buyer Street 1', country: 'FR' }, items: [ { position: 1, name: 'Test Product', unitQuantity: 1, unitNetPrice: 100.00, unitType: 'C62', vatPercentage: 19, articleNumber: 'ART-001' } ], totalInvoiceAmount: 119.00, totalNetAmount: 100.00, totalVatAmount: 19.00 }; const results = validator.validateFacturX(invoice as EInvoice, FacturXProfile.BASIC); const errors = results.filter(r => r.severity === 'error'); console.log('BASIC profile validation errors:', errors); expect(errors.length).toEqual(0); }); tap.test('Factur-X Validator - BASIC profile missing line items', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:basic:2017' }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), accountingDocType: 'invoice', currency: 'EUR', dueDate: new Date('2025-02-11'), from: { type: 'company', name: 'Test Seller', vatNumber: 'DE123456789', address: 'Test Street 1', country: 'DE' }, to: { type: 'company', name: 'Test Buyer', address: 'Buyer Street 1', country: 'FR' }, // Missing items totalInvoiceAmount: 119.00, totalNetAmount: 100.00, totalVatAmount: 19.00 }; const results = validator.validateFacturX(invoice as EInvoice); const errors = results.filter(r => r.severity === 'error'); expect(errors.length).toBeGreaterThan(0); expect(errors.some(e => e.ruleId === 'FX-BAS-02')).toBeTrue(); }); tap.test('Factur-X Validator - BASIC_WL profile (without lines)', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:basicwl:2017' }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), accountingDocType: 'invoice', currency: 'EUR', dueDate: new Date('2025-02-11'), from: { type: 'company', name: 'Test Seller', vatNumber: 'DE123456789', address: 'Test Street 1', country: 'DE' }, to: { type: 'company', name: 'Test Buyer', address: 'Buyer Street 1', country: 'FR' }, // No items required for BASIC_WL totalInvoiceAmount: 119.00, totalNetAmount: 100.00, totalVatAmount: 19.00 }; const results = validator.validateFacturX(invoice as EInvoice, FacturXProfile.BASIC_WL); const errors = results.filter(r => r.severity === 'error'); console.log('BASIC_WL profile validation errors:', errors); expect(errors.length).toEqual(0); }); tap.test('Factur-X Validator - EN16931 profile validation', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:en16931:2017', buyerReference: 'REF-12345' }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), accountingDocType: 'invoice', currency: 'EUR', dueDate: new Date('2025-02-11'), from: { type: 'company', name: 'Test Seller', vatNumber: 'DE123456789', address: 'Test Street 1', city: 'Berlin', postalCode: '10115', country: 'DE' }, to: { type: 'company', name: 'Test Buyer', address: 'Buyer Street 1', city: 'Paris', postalCode: '75001', country: 'FR' }, items: [ { position: 1, name: 'Test Product', unitQuantity: 1, unitNetPrice: 100.00, unitType: 'C62', vatPercentage: 19, articleNumber: 'ART-001' } ], totalInvoiceAmount: 119.00, totalNetAmount: 100.00, totalVatAmount: 19.00 }; const results = validator.validateFacturX(invoice as EInvoice, FacturXProfile.EN16931); const errors = results.filter(r => r.severity === 'error'); console.log('EN16931 profile validation errors:', errors); expect(errors.length).toEqual(0); }); tap.test('Factur-X Validator - EN16931 missing buyer reference', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:en16931:2017', // Missing buyerReference or purchaseOrderReference }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), accountingDocType: 'invoice', currency: 'EUR', from: { type: 'company', name: 'Test Seller', vatNumber: 'DE123456789', address: 'Test Street 1', city: 'Berlin', postalCode: '10115', country: 'DE' }, to: { type: 'company', name: 'Test Buyer', address: 'Buyer Street 1', city: 'Paris', postalCode: '75001', country: 'FR' }, items: [], totalInvoiceAmount: 0, totalNetAmount: 0, totalVatAmount: 0, dueDate: new Date('2025-02-11') }; const results = validator.validateFacturX(invoice as EInvoice); const errors = results.filter(r => r.severity === 'error'); expect(errors.some(e => e.ruleId === 'FX-EN-01')).toBeTrue(); }); tap.test('Factur-X Validator - EXTENDED profile validation', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:extended:2017', extensions: { attachments: [ { filename: 'invoice.pdf', mimeType: 'application/pdf', data: 'base64encodeddata' } ] } }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), accountingDocType: 'invoice', currency: 'EUR', from: { type: 'company', name: 'Test Seller', vatNumber: 'DE123456789' }, to: { type: 'company', name: 'Test Buyer' }, totalInvoiceAmount: 119.00 }; const results = validator.validateFacturX(invoice as EInvoice, FacturXProfile.EXTENDED); const errors = results.filter(r => r.severity === 'error'); console.log('EXTENDED profile validation errors:', errors); expect(errors.length).toEqual(0); }); tap.test('Factur-X Validator - EXTENDED profile attachment validation', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:facturx:extended:2017', extensions: { attachments: [ { // Missing filename and mimeType data: 'base64encodeddata' } ] } }, accountingDocId: 'INV-2025-001', issueDate: new Date('2025-01-11'), accountingDocType: 'invoice', currency: 'EUR', from: { type: 'company', name: 'Test Seller', vatNumber: 'DE123456789' }, to: { type: 'company', name: 'Test Buyer' }, totalInvoiceAmount: 119.00 }; const results = validator.validateFacturX(invoice as EInvoice); const warnings = results.filter(r => r.severity === 'warning'); expect(warnings.some(w => w.ruleId === 'FX-EXT-01')).toBeTrue(); }); tap.test('Factur-X Validator - ZUGFeRD compatibility', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:zugferd:basic:2017' // ZUGFeRD format } }; // Should detect as Factur-X (ZUGFeRD is the German name) const profile = validator.detectProfile(invoice as EInvoice); expect(profile).toEqual(FacturXProfile.BASIC); }); tap.test('Factur-X Validator - profile display names', async () => { const validator = FacturXValidator.create(); expect(validator.getProfileDisplayName(FacturXProfile.MINIMUM)).toEqual('Factur-X MINIMUM'); expect(validator.getProfileDisplayName(FacturXProfile.BASIC)).toEqual('Factur-X BASIC'); expect(validator.getProfileDisplayName(FacturXProfile.BASIC_WL)).toEqual('Factur-X BASIC WL'); expect(validator.getProfileDisplayName(FacturXProfile.EN16931)).toEqual('Factur-X EN16931'); expect(validator.getProfileDisplayName(FacturXProfile.EXTENDED)).toEqual('Factur-X EXTENDED'); }); tap.test('Factur-X Validator - profile compliance levels', async () => { const validator = FacturXValidator.create(); expect(validator.getProfileComplianceLevel(FacturXProfile.MINIMUM)).toEqual(1); expect(validator.getProfileComplianceLevel(FacturXProfile.BASIC_WL)).toEqual(2); expect(validator.getProfileComplianceLevel(FacturXProfile.BASIC)).toEqual(3); expect(validator.getProfileComplianceLevel(FacturXProfile.EN16931)).toEqual(4); expect(validator.getProfileComplianceLevel(FacturXProfile.EXTENDED)).toEqual(5); }); tap.test('Factur-X Validator - non-Factur-X invoice skips validation', async () => { const validator = FacturXValidator.create(); const invoice: Partial = { metadata: { profileId: 'urn:cen.eu:en16931:2017' // Not Factur-X } }; const results = validator.validateFacturX(invoice as EInvoice); expect(results.length).toEqual(0); }); export default tap.start();