fix(compliance): improve compliance

This commit is contained in:
2025-05-28 14:46:32 +00:00
parent 784a50bc7f
commit 16e2bd6b1a
16 changed files with 4718 additions and 3138 deletions

View File

@ -1,130 +1,409 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js';
import { PerformanceTracker } from '../performance.tracker.js';
tap.test('ENC-06: Namespace Declarations - should handle XML namespace declarations correctly', async () => {
// ENC-06: Verify handling of Namespace Declarations encoded documents
// Test 1: Direct Namespace Declarations encoding (expected to fail)
console.log('\nTest 1: Direct Namespace Declarations encoding');
const { result: directResult, metric: directMetric } = await PerformanceTracker.track(
'namespace-direct',
async () => {
// XML parsers typically don't support Namespace Declarations directly
const xmlContent = `<?xml version="1.0" encoding="Namespace Declarations"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<UBLVersionID>2.1</UBLVersionID>
<ID>NAMESPACE-TEST</ID>
<IssueDate>2025-01-25</IssueDate>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
</Invoice>`;
let success = false;
let error = null;
try {
const newInvoice = new EInvoice();
await newInvoice.fromXmlString(xmlContent);
success = newInvoice.id === 'NAMESPACE-TEST' ||
newInvoice.invoiceId === 'NAMESPACE-TEST' ||
newInvoice.accountingDocId === 'NAMESPACE-TEST';
} catch (e) {
error = e;
console.log(` Namespace Declarations not directly supported: ${e.message}`);
console.log('Testing XML namespace declaration handling...\n');
// Test 1: Default namespaces
const testDefaultNamespaces = async () => {
const einvoice = new EInvoice();
einvoice.id = 'NAMESPACE-DEFAULT-TEST';
einvoice.date = Date.now();
einvoice.currency = 'EUR';
einvoice.subject = 'Default namespace test';
einvoice.from = {
type: 'company',
name: 'Default Namespace Company',
description: 'Testing default namespaces',
address: {
streetName: 'Test Street',
houseNumber: '1',
postalCode: '12345',
city: 'Test City',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB 12345',
registrationName: 'Registry'
}
return { success, error };
}
);
console.log(` Namespace Declarations direct test completed in ${directMetric.duration}ms`);
// Test 2: UTF-8 fallback (should always work)
console.log('\nTest 2: UTF-8 fallback');
const { result: fallbackResult, metric: fallbackMetric } = await PerformanceTracker.track(
'namespace-fallback',
async () => {
const einvoice = new EInvoice();
einvoice.id = 'NAMESPACE-FALLBACK-TEST';
einvoice.issueDate = new Date(2025, 0, 25);
einvoice.invoiceId = 'NAMESPACE-FALLBACK-TEST';
einvoice.accountingDocId = 'NAMESPACE-FALLBACK-TEST';
einvoice.subject = 'Namespace Declarations fallback test';
einvoice.from = {
type: 'company',
name: 'Test Company',
description: 'Testing Namespace Declarations encoding',
address: {
streetName: 'Test Street',
houseNumber: '1',
postalCode: '12345',
city: 'Test City',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB 12345',
registrationName: 'Commercial Register'
}
};
einvoice.to = {
type: 'company',
name: 'Customer',
description: 'Test customer',
address: {
streetName: 'Customer Street',
houseNumber: '2',
postalCode: '54321',
city: 'Customer City',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2019, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE987654321',
registrationId: 'HRB 54321',
registrationName: 'Registry'
}
};
einvoice.items = [{
position: 1,
name: 'Namespace Test Product',
unitType: 'C62',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
const xmlString = await einvoice.toXmlString('ubl');
// Check if proper UBL namespaces are declared
const hasUblNamespace = xmlString.includes('xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"');
const hasCacNamespace = xmlString.includes('xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"');
const hasCbcNamespace = xmlString.includes('xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"');
// Check if elements use proper prefixes
const hasProperPrefixes = xmlString.includes('<cbc:ID>') &&
xmlString.includes('<cac:AccountingSupplierParty>') &&
xmlString.includes('<cac:AccountingCustomerParty>');
return {
hasUblNamespace,
hasCacNamespace,
hasCbcNamespace,
hasProperPrefixes,
xmlString
};
};
const defaultResult = await testDefaultNamespaces();
console.log('Test 1 - Default namespaces:');
console.log(` UBL namespace declared: ${defaultResult.hasUblNamespace ? 'Yes' : 'No'}`);
console.log(` CAC namespace declared: ${defaultResult.hasCacNamespace ? 'Yes' : 'No'}`);
console.log(` CBC namespace declared: ${defaultResult.hasCbcNamespace ? 'Yes' : 'No'}`);
console.log(` Proper prefixes used: ${defaultResult.hasProperPrefixes ? 'Yes' : 'No'}`);
// Test 2: Custom namespace handling
const testCustomNamespaces = async () => {
const customXml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:ext="urn:example:custom:extension">
<cbc:ID>CUSTOM-NS-TEST</cbc:ID>
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Custom Namespace Company</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Test Street</cbc:StreetName>
<cbc:CityName>Test City</cbc:CityName>
<cbc:PostalZone>12345</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Customer</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Customer Street</cbc:StreetName>
<cbc:CityName>Customer City</cbc:CityName>
<cbc:PostalZone>54321</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Test Item</cbc:Name>
</cac:Item>
</cac:InvoiceLine>
<ext:CustomExtension>
<ext:CustomField>Custom Value</ext:CustomField>
</ext:CustomExtension>
</Invoice>`;
try {
const invoice = await EInvoice.fromXml(customXml);
return {
success: invoice.id === 'CUSTOM-NS-TEST',
supplierName: invoice.from?.name || '',
customerName: invoice.to?.name || ''
};
einvoice.to = {
type: 'person',
name: 'Test',
surname: 'Customer',
salutation: 'Mr' as const,
sex: 'male' as const,
title: 'Doctor' as const,
description: 'Test customer',
address: {
streetName: 'Customer Street',
houseNumber: '2',
postalCode: '54321',
city: 'Customer City',
country: 'DE'
}
} catch (error) {
return {
success: false,
error: error.message
};
einvoice.items = [{
position: 1,
name: 'Test Product',
articleNumber: 'NAMESPACE-001',
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
// Export as UTF-8 (our default)
const utf8Xml = await einvoice.toXmlString('ubl');
// Verify UTF-8 works correctly
const newInvoice = new EInvoice();
await newInvoice.fromXmlString(utf8Xml);
const success = newInvoice.id === 'NAMESPACE-FALLBACK-TEST' ||
newInvoice.invoiceId === 'NAMESPACE-FALLBACK-TEST' ||
newInvoice.accountingDocId === 'NAMESPACE-FALLBACK-TEST';
console.log(` UTF-8 fallback works: ${success}`);
return { success };
}
);
console.log(` Namespace Declarations fallback test completed in ${fallbackMetric.duration}ms`);
};
const customResult = await testCustomNamespaces();
console.log('\nTest 2 - Custom namespace handling:');
console.log(` Custom namespace XML parsed: ${customResult.success ? 'Yes' : 'No'}`);
if (customResult.error) {
console.log(` Error: ${customResult.error}`);
}
// Test 3: No namespace prefix handling
const testNoNamespacePrefix = async () => {
const noNsXml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>NO-NS-PREFIX-TEST</ID>
<IssueDate>2025-01-25</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<AccountingSupplierParty xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
<Party>
<PartyName>
<Name xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">No Prefix Company</Name>
</PartyName>
<PostalAddress>
<StreetName xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">Test Street</StreetName>
<CityName xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">Test City</CityName>
<PostalZone xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">12345</PostalZone>
<Country>
<IdentificationCode xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">DE</IdentificationCode>
</Country>
</PostalAddress>
</Party>
</AccountingSupplierParty>
<AccountingCustomerParty xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
<Party>
<PartyName>
<Name xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">Customer</Name>
</PartyName>
<PostalAddress>
<StreetName xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">Customer Street</StreetName>
<CityName xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">Customer City</CityName>
<PostalZone xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">54321</PostalZone>
<Country>
<IdentificationCode xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">DE</IdentificationCode>
</Country>
</PostalAddress>
</Party>
</AccountingCustomerParty>
<InvoiceLine xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
<ID xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">1</ID>
<InvoicedQuantity xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" unitCode="C62">1</InvoicedQuantity>
<LineExtensionAmount xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" currencyID="EUR">100.00</LineExtensionAmount>
<Item>
<Name xmlns="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">Test Item</Name>
</Item>
</InvoiceLine>
</Invoice>`;
try {
const invoice = await EInvoice.fromXml(noNsXml);
return {
success: invoice.id === 'NO-NS-PREFIX-TEST',
supplierName: invoice.from?.name || '',
customerName: invoice.to?.name || ''
};
} catch (error) {
return {
success: false,
error: error.message
};
}
};
const noNsResult = await testNoNamespacePrefix();
console.log('\nTest 3 - No namespace prefix handling:');
console.log(` No prefix XML parsed: ${noNsResult.success ? 'Yes' : 'No'}`);
if (noNsResult.error) {
console.log(` Error: ${noNsResult.error}`);
}
// Test 4: Namespace inheritance
const testNamespaceInheritance = async () => {
const inheritanceXml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:ID>INHERITANCE-TEST</cbc:ID>
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Inheritance Company</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Test Street</cbc:StreetName>
<cbc:CityName>Test City</cbc:CityName>
<cbc:PostalZone>12345</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Customer</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Customer Street</cbc:StreetName>
<cbc:CityName>Customer City</cbc:CityName>
<cbc:PostalZone>54321</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Test Item</cbc:Name>
</cac:Item>
</cac:InvoiceLine>
</Invoice>`;
try {
const invoice = await EInvoice.fromXml(inheritanceXml);
// Test round-trip to see if namespaces are preserved
const regeneratedXml = await invoice.toXmlString('ubl');
const reparsedInvoice = await EInvoice.fromXml(regeneratedXml);
return {
success: invoice.id === 'INHERITANCE-TEST',
roundTripSuccess: reparsedInvoice.id === 'INHERITANCE-TEST',
supplierName: invoice.from?.name || '',
regeneratedXml
};
} catch (error) {
return {
success: false,
error: error.message
};
}
};
const inheritanceResult = await testNamespaceInheritance();
console.log('\nTest 4 - Namespace inheritance and round-trip:');
console.log(` Inheritance XML parsed: ${inheritanceResult.success ? 'Yes' : 'No'}`);
console.log(` Round-trip successful: ${inheritanceResult.roundTripSuccess ? 'Yes' : 'No'}`);
if (inheritanceResult.error) {
console.log(` Error: ${inheritanceResult.error}`);
}
// Test 5: Mixed namespace scenarios
const testMixedNamespaces = async () => {
const mixedXml = `<?xml version="1.0" encoding="UTF-8"?>
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<cbc:ID>MIXED-NS-TEST</cbc:ID>
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Mixed Namespace Company</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Test Street</cbc:StreetName>
<cbc:CityName>Test City</cbc:CityName>
<cbc:PostalZone>12345</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Customer</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Customer Street</cbc:StreetName>
<cbc:CityName>Customer City</cbc:CityName>
<cbc:PostalZone>54321</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Test Item</cbc:Name>
</cac:Item>
</cac:InvoiceLine>
</ubl:Invoice>`;
try {
const invoice = await EInvoice.fromXml(mixedXml);
return {
success: invoice.id === 'MIXED-NS-TEST',
supplierName: invoice.from?.name || ''
};
} catch (error) {
return {
success: false,
error: error.message
};
}
};
const mixedResult = await testMixedNamespaces();
console.log('\nTest 5 - Mixed namespace scenarios:');
console.log(` Mixed namespace XML parsed: ${mixedResult.success ? 'Yes' : 'No'}`);
if (mixedResult.error) {
console.log(` Error: ${mixedResult.error}`);
}
// Summary
console.log('\n=== Namespace Declarations Encoding Test Summary ===');
console.log(`Namespace Declarations Direct: ${directResult.success ? 'Supported' : 'Not supported (acceptable)'}`);
console.log(`UTF-8 Fallback: ${fallbackResult.success ? 'Working' : 'Failed'}`);
console.log('\n=== XML Namespace Declarations Test Summary ===');
console.log(`Default namespaces: ${defaultResult.hasUblNamespace && defaultResult.hasCacNamespace && defaultResult.hasCbcNamespace ? 'Working' : 'Issues'}`);
console.log(`Custom namespaces: ${customResult.success ? 'Working' : 'Issues'}`);
console.log(`No prefix handling: ${noNsResult.success ? 'Working' : 'Issues'}`);
console.log(`Namespace inheritance: ${inheritanceResult.success && inheritanceResult.roundTripSuccess ? 'Working' : 'Issues'}`);
console.log(`Mixed namespaces: ${mixedResult.success ? 'Working' : 'Issues'}`);
// The test passes if UTF-8 fallback works, since Namespace Declarations support is optional
expect(fallbackResult.success).toBeTrue();
// Tests pass if basic namespace functionality works
expect(defaultResult.hasUblNamespace).toEqual(true);
expect(defaultResult.hasCacNamespace).toEqual(true);
expect(defaultResult.hasCbcNamespace).toEqual(true);
expect(defaultResult.hasProperPrefixes).toEqual(true);
expect(customResult.success).toEqual(true);
expect(inheritanceResult.success).toEqual(true);
expect(inheritanceResult.roundTripSuccess).toEqual(true);
console.log('\n✓ XML namespace declarations test completed');
});
// Run the test
tap.start();
tap.start();