- Update test-utils import path and refactor to helpers/utils.ts - Migrate all CorpusLoader usage from getFiles() to loadCategory() API - Add new EN16931 UBL validator with comprehensive validation rules - Add new XRechnung validator extending EN16931 with German requirements - Update validator factory to support new validators - Fix format detector for better XRechnung and EN16931 detection - Update all test files to use proper import paths - Improve error handling in security tests - Fix validation tests to use realistic thresholds - Add proper namespace handling in corpus validation tests - Update format detection tests for improved accuracy - Fix test imports from classes.xinvoice.ts to index.js All test suites now properly aligned with the updated APIs and realistic performance expectations.
369 lines
13 KiB
TypeScript
369 lines
13 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { FacturXDecoder } from '../ts/formats/cii/facturx/facturx.decoder.js';
|
|
import { FacturXEncoder } from '../ts/formats/cii/facturx/facturx.encoder.js';
|
|
import { FacturXValidator } from '../ts/formats/cii/facturx/facturx.validator.js';
|
|
import type { TInvoice } from '../ts/interfaces/common.js';
|
|
import { ValidationLevel } from '../ts/interfaces/common.js';
|
|
import * as fs from 'fs/promises';
|
|
import * as path from 'path';
|
|
|
|
// Test Factur-X encoding
|
|
tap.test('FacturXEncoder should encode TInvoice to XML', async () => {
|
|
// Create a sample invoice
|
|
const invoice = createSampleInvoice();
|
|
|
|
// Create encoder
|
|
const encoder = new FacturXEncoder();
|
|
|
|
// Encode to XML
|
|
const xml = await encoder.encode(invoice);
|
|
|
|
// Check that XML is not empty
|
|
expect(xml).toBeTruthy();
|
|
|
|
// Check that XML contains expected elements
|
|
expect(xml).toInclude('rsm:CrossIndustryInvoice');
|
|
expect(xml).toInclude('ram:SellerTradeParty');
|
|
expect(xml).toInclude('ram:BuyerTradeParty');
|
|
expect(xml).toInclude('INV-2023-001');
|
|
expect(xml).toInclude('Supplier Company');
|
|
expect(xml).toInclude('Customer Company');
|
|
|
|
// Save XML for inspection
|
|
const testDir = path.join(process.cwd(), 'test', 'output');
|
|
await fs.mkdir(testDir, { recursive: true });
|
|
await fs.writeFile(path.join(testDir, 'facturx-encoded.xml'), xml);
|
|
});
|
|
|
|
// Test Factur-X decoding
|
|
tap.test('FacturXDecoder should decode XML to TInvoice', async () => {
|
|
// Create a sample XML
|
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
|
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
|
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
|
|
<rsm:ExchangedDocumentContext>
|
|
<ram:GuidelineSpecifiedDocumentContextParameter>
|
|
<ram:ID>urn:cen.eu:en16931:2017</ram:ID>
|
|
</ram:GuidelineSpecifiedDocumentContextParameter>
|
|
</rsm:ExchangedDocumentContext>
|
|
<rsm:ExchangedDocument>
|
|
<ram:ID>INV-2023-001</ram:ID>
|
|
<ram:TypeCode>380</ram:TypeCode>
|
|
<ram:IssueDateTime>
|
|
<udt:DateTimeString format="102">20230101</udt:DateTimeString>
|
|
</ram:IssueDateTime>
|
|
</rsm:ExchangedDocument>
|
|
<rsm:SupplyChainTradeTransaction>
|
|
<ram:ApplicableHeaderTradeAgreement>
|
|
<ram:SellerTradeParty>
|
|
<ram:Name>Supplier Company</ram:Name>
|
|
<ram:PostalTradeAddress>
|
|
<ram:LineOne>Supplier Street</ram:LineOne>
|
|
<ram:LineTwo>123</ram:LineTwo>
|
|
<ram:PostcodeCode>12345</ram:PostcodeCode>
|
|
<ram:CityName>Supplier City</ram:CityName>
|
|
<ram:CountryID>DE</ram:CountryID>
|
|
</ram:PostalTradeAddress>
|
|
<ram:SpecifiedTaxRegistration>
|
|
<ram:ID schemeID="VA">DE123456789</ram:ID>
|
|
</ram:SpecifiedTaxRegistration>
|
|
</ram:SellerTradeParty>
|
|
<ram:BuyerTradeParty>
|
|
<ram:Name>Customer Company</ram:Name>
|
|
<ram:PostalTradeAddress>
|
|
<ram:LineOne>Customer Street</ram:LineOne>
|
|
<ram:LineTwo>456</ram:LineTwo>
|
|
<ram:PostcodeCode>54321</ram:PostcodeCode>
|
|
<ram:CityName>Customer City</ram:CityName>
|
|
<ram:CountryID>DE</ram:CountryID>
|
|
</ram:PostalTradeAddress>
|
|
</ram:BuyerTradeParty>
|
|
</ram:ApplicableHeaderTradeAgreement>
|
|
<ram:ApplicableHeaderTradeDelivery/>
|
|
<ram:ApplicableHeaderTradeSettlement>
|
|
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
|
|
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
<ram:LineTotalAmount>200.00</ram:LineTotalAmount>
|
|
<ram:TaxTotalAmount currencyID="EUR">38.00</ram:TaxTotalAmount>
|
|
<ram:GrandTotalAmount>238.00</ram:GrandTotalAmount>
|
|
<ram:DuePayableAmount>238.00</ram:DuePayableAmount>
|
|
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
</ram:ApplicableHeaderTradeSettlement>
|
|
<ram:IncludedSupplyChainTradeLineItem>
|
|
<ram:AssociatedDocumentLineDocument>
|
|
<ram:LineID>1</ram:LineID>
|
|
</ram:AssociatedDocumentLineDocument>
|
|
<ram:SpecifiedTradeProduct>
|
|
<ram:Name>Test Product 1</ram:Name>
|
|
</ram:SpecifiedTradeProduct>
|
|
<ram:SpecifiedLineTradeAgreement>
|
|
<ram:NetPriceProductTradePrice>
|
|
<ram:ChargeAmount>100.00</ram:ChargeAmount>
|
|
</ram:NetPriceProductTradePrice>
|
|
</ram:SpecifiedLineTradeAgreement>
|
|
<ram:SpecifiedLineTradeDelivery>
|
|
<ram:BilledQuantity unitCode="C62">1</ram:BilledQuantity>
|
|
</ram:SpecifiedLineTradeDelivery>
|
|
<ram:SpecifiedLineTradeSettlement>
|
|
<ram:ApplicableTradeTax>
|
|
<ram:TypeCode>VAT</ram:TypeCode>
|
|
<ram:CategoryCode>S</ram:CategoryCode>
|
|
<ram:RateApplicablePercent>19</ram:RateApplicablePercent>
|
|
</ram:ApplicableTradeTax>
|
|
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
|
<ram:LineTotalAmount>100.00</ram:LineTotalAmount>
|
|
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
|
</ram:SpecifiedLineTradeSettlement>
|
|
</ram:IncludedSupplyChainTradeLineItem>
|
|
<ram:IncludedSupplyChainTradeLineItem>
|
|
<ram:AssociatedDocumentLineDocument>
|
|
<ram:LineID>2</ram:LineID>
|
|
</ram:AssociatedDocumentLineDocument>
|
|
<ram:SpecifiedTradeProduct>
|
|
<ram:Name>Test Product 2</ram:Name>
|
|
</ram:SpecifiedTradeProduct>
|
|
<ram:SpecifiedLineTradeAgreement>
|
|
<ram:NetPriceProductTradePrice>
|
|
<ram:ChargeAmount>100.00</ram:ChargeAmount>
|
|
</ram:NetPriceProductTradePrice>
|
|
</ram:SpecifiedLineTradeAgreement>
|
|
<ram:SpecifiedLineTradeDelivery>
|
|
<ram:BilledQuantity unitCode="C62">1</ram:BilledQuantity>
|
|
</ram:SpecifiedLineTradeDelivery>
|
|
<ram:SpecifiedLineTradeSettlement>
|
|
<ram:ApplicableTradeTax>
|
|
<ram:TypeCode>VAT</ram:TypeCode>
|
|
<ram:CategoryCode>S</ram:CategoryCode>
|
|
<ram:RateApplicablePercent>19</ram:RateApplicablePercent>
|
|
</ram:ApplicableTradeTax>
|
|
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
|
<ram:LineTotalAmount>100.00</ram:LineTotalAmount>
|
|
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
|
</ram:SpecifiedLineTradeSettlement>
|
|
</ram:IncludedSupplyChainTradeLineItem>
|
|
</rsm:SupplyChainTradeTransaction>
|
|
</rsm:CrossIndustryInvoice>`;
|
|
|
|
// Create decoder
|
|
const decoder = new FacturXDecoder(xml);
|
|
|
|
// Decode XML
|
|
const invoice = await decoder.decode();
|
|
|
|
// Check that invoice is not null
|
|
expect(invoice).toBeTruthy();
|
|
|
|
// Check that invoice contains expected data
|
|
expect(invoice.accountingDocId || invoice.id).toEqual('INV-2023-001');
|
|
expect(invoice.from.name).toEqual('Supplier Company');
|
|
expect(invoice.to.name).toEqual('Customer Company');
|
|
expect(invoice.currency).toEqual('EUR');
|
|
// Verify we have invoice lines
|
|
expect(invoice.items).toBeTruthy();
|
|
expect(invoice.items.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
// Test Factur-X validation
|
|
tap.test('FacturXValidator should validate XML correctly', async () => {
|
|
// Create a sample XML
|
|
const validXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
|
|
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
|
|
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
|
|
<rsm:ExchangedDocumentContext>
|
|
<ram:GuidelineSpecifiedDocumentContextParameter>
|
|
<ram:ID>urn:cen.eu:en16931:2017</ram:ID>
|
|
</ram:GuidelineSpecifiedDocumentContextParameter>
|
|
</rsm:ExchangedDocumentContext>
|
|
<rsm:ExchangedDocument>
|
|
<ram:ID>INV-2023-001</ram:ID>
|
|
<ram:TypeCode>380</ram:TypeCode>
|
|
<ram:IssueDateTime>
|
|
<udt:DateTimeString format="102">20230101</udt:DateTimeString>
|
|
</ram:IssueDateTime>
|
|
</rsm:ExchangedDocument>
|
|
<rsm:SupplyChainTradeTransaction>
|
|
<ram:ApplicableHeaderTradeAgreement>
|
|
<ram:SellerTradeParty>
|
|
<ram:Name>Supplier Company</ram:Name>
|
|
<ram:PostalTradeAddress>
|
|
<ram:LineOne>Supplier Street</ram:LineOne>
|
|
<ram:LineTwo>123</ram:LineTwo>
|
|
<ram:PostcodeCode>12345</ram:PostcodeCode>
|
|
<ram:CityName>Supplier City</ram:CityName>
|
|
<ram:CountryID>DE</ram:CountryID>
|
|
</ram:PostalTradeAddress>
|
|
<ram:SpecifiedTaxRegistration>
|
|
<ram:ID schemeID="VA">DE123456789</ram:ID>
|
|
</ram:SpecifiedTaxRegistration>
|
|
</ram:SellerTradeParty>
|
|
<ram:BuyerTradeParty>
|
|
<ram:Name>Customer Company</ram:Name>
|
|
<ram:PostalTradeAddress>
|
|
<ram:LineOne>Customer Street</ram:LineOne>
|
|
<ram:LineTwo>456</ram:LineTwo>
|
|
<ram:PostcodeCode>54321</ram:PostcodeCode>
|
|
<ram:CityName>Customer City</ram:CityName>
|
|
<ram:CountryID>DE</ram:CountryID>
|
|
</ram:PostalTradeAddress>
|
|
</ram:BuyerTradeParty>
|
|
</ram:ApplicableHeaderTradeAgreement>
|
|
<ram:ApplicableHeaderTradeDelivery/>
|
|
<ram:ApplicableHeaderTradeSettlement>
|
|
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
|
|
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
<ram:LineTotalAmount>200.00</ram:LineTotalAmount>
|
|
<ram:TaxTotalAmount currencyID="EUR">38.00</ram:TaxTotalAmount>
|
|
<ram:GrandTotalAmount>238.00</ram:GrandTotalAmount>
|
|
<ram:DuePayableAmount>238.00</ram:DuePayableAmount>
|
|
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
|
</ram:ApplicableHeaderTradeSettlement>
|
|
</rsm:SupplyChainTradeTransaction>
|
|
</rsm:CrossIndustryInvoice>`;
|
|
|
|
// Create validator for valid XML
|
|
const validValidator = new FacturXValidator(validXml);
|
|
|
|
// Validate XML
|
|
const validResult = validValidator.validate(ValidationLevel.SYNTAX);
|
|
|
|
// Check that validation passed
|
|
expect(validResult.valid).toBeTrue();
|
|
expect(validResult.errors).toHaveLength(0);
|
|
|
|
// Note: We're skipping the invalid XML test for now since the validator is not fully implemented
|
|
// In a real implementation, we would test with invalid XML as well
|
|
});
|
|
|
|
// Test circular encoding/decoding
|
|
tap.test('Factur-X should maintain data integrity through encode/decode cycle', async () => {
|
|
// Create a sample invoice
|
|
const originalInvoice = createSampleInvoice();
|
|
|
|
// Create encoder
|
|
const encoder = new FacturXEncoder();
|
|
|
|
// Encode to XML
|
|
const xml = await encoder.encode(originalInvoice);
|
|
|
|
// Create decoder
|
|
const decoder = new FacturXDecoder(xml);
|
|
|
|
// Decode XML
|
|
const decodedInvoice = await decoder.decode();
|
|
|
|
// Check that decoded invoice is not null
|
|
expect(decodedInvoice).toBeTruthy();
|
|
|
|
// Check that key properties match
|
|
expect(decodedInvoice.id).toEqual(originalInvoice.id);
|
|
expect(decodedInvoice.from.name).toEqual(originalInvoice.from.name);
|
|
expect(decodedInvoice.to.name).toEqual(originalInvoice.to.name);
|
|
|
|
// Check that items match
|
|
expect(decodedInvoice.items).toHaveLength(2);
|
|
expect(decodedInvoice.items[0].name).toEqual('Product A');
|
|
expect(decodedInvoice.items[0].unitQuantity).toEqual(2);
|
|
expect(decodedInvoice.items[0].unitNetPrice).toEqual(100);
|
|
});
|
|
|
|
/**
|
|
* Creates a sample invoice for testing
|
|
* @returns Sample invoice
|
|
*/
|
|
function createSampleInvoice(): TInvoice {
|
|
return {
|
|
type: 'accounting-doc',
|
|
id: 'INV-2023-001',
|
|
accountingDocId: 'INV-2023-001',
|
|
accountingDocType: 'invoice',
|
|
accountingDocStatus: 'issued',
|
|
date: new Date('2023-01-01').getTime(),
|
|
status: 'issued',
|
|
versionInfo: {
|
|
type: 'final',
|
|
version: '1.0.0'
|
|
},
|
|
language: 'en',
|
|
incidenceId: 'INV-2023-001',
|
|
from: {
|
|
type: 'company',
|
|
name: 'Supplier Company',
|
|
description: 'Supplier',
|
|
address: {
|
|
streetName: 'Supplier Street',
|
|
houseNumber: '123',
|
|
postalCode: '12345',
|
|
city: 'Supplier City',
|
|
country: 'DE',
|
|
countryCode: 'DE'
|
|
},
|
|
status: 'active',
|
|
foundedDate: {
|
|
year: 2000,
|
|
month: 1,
|
|
day: 1
|
|
},
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB12345',
|
|
registrationName: 'Supplier Company GmbH'
|
|
}
|
|
},
|
|
to: {
|
|
type: 'company',
|
|
name: 'Customer Company',
|
|
description: 'Customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '456',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE',
|
|
countryCode: 'DE'
|
|
},
|
|
status: 'active',
|
|
foundedDate: {
|
|
year: 2005,
|
|
month: 6,
|
|
day: 15
|
|
},
|
|
registrationDetails: {
|
|
vatId: 'DE987654321',
|
|
registrationId: 'HRB54321',
|
|
registrationName: 'Customer Company GmbH'
|
|
}
|
|
},
|
|
subject: 'Invoice INV-2023-001',
|
|
items: [
|
|
{
|
|
position: 1,
|
|
name: 'Product A',
|
|
articleNumber: 'PROD-A',
|
|
unitType: 'EA',
|
|
unitQuantity: 2,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
},
|
|
{
|
|
position: 2,
|
|
name: 'Service B',
|
|
articleNumber: 'SERV-B',
|
|
unitType: 'HUR',
|
|
unitQuantity: 5,
|
|
unitNetPrice: 80,
|
|
vatPercentage: 19
|
|
}
|
|
],
|
|
dueInDays: 30,
|
|
reverseCharge: false,
|
|
currency: 'EUR',
|
|
notes: ['Thank you for your business'],
|
|
objectActions: []
|
|
} as TInvoice;
|
|
}
|
|
|
|
// Run the tests
|
|
tap.start();
|