Files
einvoice/test/test.facturx-validator.ts

453 lines
13 KiB
TypeScript
Raw Normal View History

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<EInvoice> = {
metadata: {
profileId: 'urn:facturx:minimum:2017'
}
};
expect(validator.detectProfile(minInvoice as EInvoice)).toEqual(FacturXProfile.MINIMUM);
// BASIC profile
const basicInvoice: Partial<EInvoice> = {
metadata: {
profileId: 'urn:facturx:basic:2017'
}
};
expect(validator.detectProfile(basicInvoice as EInvoice)).toEqual(FacturXProfile.BASIC);
// EN16931 profile (Comfort)
const en16931Invoice: Partial<EInvoice> = {
metadata: {
profileId: 'urn:facturx:comfort:2017'
}
};
expect(validator.detectProfile(en16931Invoice as EInvoice)).toEqual(FacturXProfile.EN16931);
// EXTENDED profile
const extendedInvoice: Partial<EInvoice> = {
metadata: {
profileId: 'urn:facturx:extended:2017'
}
};
expect(validator.detectProfile(extendedInvoice as EInvoice)).toEqual(FacturXProfile.EXTENDED);
// Non-Factur-X invoice
const otherInvoice: Partial<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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<EInvoice> = {
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();