fix(compliance): improve compliance
This commit is contained in:
@ -1,129 +1,258 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EInvoice } from '../../../ts/index.js';
|
||||
import { PerformanceTracker } from '../performance.tracker.js';
|
||||
|
||||
tap.test('ENC-08: Mixed Content - should handle mixed text and element content', async () => {
|
||||
// ENC-08: Verify handling of Mixed Content encoded documents
|
||||
console.log('Testing XML mixed content handling...\n');
|
||||
|
||||
// Test 1: Direct Mixed Content encoding (expected to fail)
|
||||
console.log('\nTest 1: Direct Mixed Content encoding');
|
||||
const { result: directResult, metric: directMetric } = await PerformanceTracker.track(
|
||||
'mixed-direct',
|
||||
async () => {
|
||||
// XML parsers typically don't support Mixed Content directly
|
||||
const xmlContent = `<?xml version="1.0" encoding="Mixed Content"?>
|
||||
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||||
<UBLVersionID>2.1</UBLVersionID>
|
||||
<ID>MIXED-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 === 'MIXED-TEST' ||
|
||||
newInvoice.invoiceId === 'MIXED-TEST' ||
|
||||
newInvoice.accountingDocId === 'MIXED-TEST';
|
||||
} catch (e) {
|
||||
error = e;
|
||||
console.log(` Mixed Content not directly supported: ${e.message}`);
|
||||
// Test 1: Pure element content (structured only)
|
||||
const testPureElementContent = async () => {
|
||||
const einvoice = new EInvoice();
|
||||
einvoice.id = 'MIXED-ELEMENT-TEST';
|
||||
einvoice.issueDate = new Date(2025, 0, 25);
|
||||
einvoice.subject = 'Pure element content test';
|
||||
|
||||
einvoice.from = {
|
||||
type: 'company',
|
||||
name: 'Test Company',
|
||||
description: 'Testing pure element content',
|
||||
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'
|
||||
}
|
||||
|
||||
return { success, error };
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
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'
|
||||
}
|
||||
};
|
||||
|
||||
einvoice.items = [{
|
||||
position: 1,
|
||||
name: 'Test Product',
|
||||
articleNumber: 'MIXED-001',
|
||||
unitType: 'EA',
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 100,
|
||||
vatPercentage: 19
|
||||
}];
|
||||
|
||||
// Generate XML and verify structure
|
||||
const xmlString = await einvoice.toXmlString('ubl');
|
||||
|
||||
// Check for proper element structure without mixed content
|
||||
const hasProperStructure = xmlString.includes('<cbc:ID>MIXED-ELEMENT-TEST</cbc:ID>') &&
|
||||
xmlString.includes('<cac:AccountingSupplierParty>') &&
|
||||
xmlString.includes('<cac:Party>');
|
||||
|
||||
// Verify round-trip works
|
||||
const newInvoice = new EInvoice();
|
||||
await newInvoice.fromXmlString(xmlString);
|
||||
|
||||
const roundTripSuccess = (newInvoice.id === 'MIXED-ELEMENT-TEST' ||
|
||||
newInvoice.invoiceId === 'MIXED-ELEMENT-TEST' ||
|
||||
newInvoice.accountingDocId === 'MIXED-ELEMENT-TEST');
|
||||
|
||||
console.log(`Test 1 - Pure element content:`);
|
||||
console.log(` Proper XML structure: ${hasProperStructure ? 'Yes' : 'No'}`);
|
||||
console.log(` Round-trip successful: ${roundTripSuccess ? 'Yes' : 'No'}`);
|
||||
|
||||
return { hasProperStructure, roundTripSuccess };
|
||||
};
|
||||
|
||||
console.log(` Mixed Content 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(
|
||||
'mixed-fallback',
|
||||
async () => {
|
||||
// Test 2: Mixed content with text and elements
|
||||
const testMixedContent = async () => {
|
||||
// XML with mixed content (text + elements combined)
|
||||
const mixedContentXml = `<?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:UBLVersionID>2.1</cbc:UBLVersionID>
|
||||
<cbc:ID>MIXED-CONTENT-TEST</cbc:ID>
|
||||
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
||||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||||
<cac:AccountingSupplierParty>
|
||||
<cac:Party>
|
||||
<cac:PartyName>
|
||||
<cbc:Name>Company Name with Text
|
||||
<Element>nested element</Element> and more text
|
||||
</cbc:Name>
|
||||
</cac:PartyName>
|
||||
</cac:Party>
|
||||
</cac:AccountingSupplierParty>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>1</cbc:ID>
|
||||
<cbc:Note>This is a note with <strong>emphasis</strong> and additional text</cbc:Note>
|
||||
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
||||
<cac:Item>
|
||||
<cbc:Name>Test Item</cbc:Name>
|
||||
<cbc:Description>Item description with
|
||||
<detail>detailed info</detail>
|
||||
and more descriptive text
|
||||
</cbc:Description>
|
||||
</cac:Item>
|
||||
</cac:InvoiceLine>
|
||||
</Invoice>`;
|
||||
|
||||
try {
|
||||
const einvoice = new EInvoice();
|
||||
einvoice.id = 'MIXED-FALLBACK-TEST';
|
||||
einvoice.issueDate = new Date(2025, 0, 25);
|
||||
einvoice.invoiceId = 'MIXED-FALLBACK-TEST';
|
||||
einvoice.accountingDocId = 'MIXED-FALLBACK-TEST';
|
||||
einvoice.subject = 'Mixed Content fallback test';
|
||||
await einvoice.fromXmlString(mixedContentXml);
|
||||
|
||||
einvoice.from = {
|
||||
type: 'company',
|
||||
name: 'Test Company',
|
||||
description: 'Testing Mixed Content 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'
|
||||
}
|
||||
};
|
||||
// Check if mixed content is handled appropriately
|
||||
const mixedContentHandled = einvoice.from?.name !== undefined &&
|
||||
einvoice.items?.[0]?.name !== undefined;
|
||||
|
||||
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'
|
||||
}
|
||||
};
|
||||
console.log(`\nTest 2 - Mixed content parsing:`);
|
||||
console.log(` Mixed content XML parsed: ${mixedContentHandled ? 'Yes' : 'No'}`);
|
||||
console.log(` Supplier name extracted: ${einvoice.from?.name ? 'Yes' : 'No'}`);
|
||||
console.log(` Item data extracted: ${einvoice.items?.[0]?.name ? 'Yes' : 'No'}`);
|
||||
|
||||
einvoice.items = [{
|
||||
position: 1,
|
||||
name: 'Test Product',
|
||||
articleNumber: 'MIXED-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 === 'MIXED-FALLBACK-TEST' ||
|
||||
newInvoice.invoiceId === 'MIXED-FALLBACK-TEST' ||
|
||||
newInvoice.accountingDocId === 'MIXED-FALLBACK-TEST';
|
||||
|
||||
console.log(` UTF-8 fallback works: ${success}`);
|
||||
|
||||
return { success };
|
||||
return { mixedContentHandled };
|
||||
} catch (error) {
|
||||
console.log(`\nTest 2 - Mixed content parsing:`);
|
||||
console.log(` Mixed content parsing failed: ${error.message}`);
|
||||
return { mixedContentHandled: false };
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
console.log(` Mixed Content fallback test completed in ${fallbackMetric.duration}ms`);
|
||||
// Test 3: CDATA sections with mixed content
|
||||
const testCDataMixedContent = async () => {
|
||||
const cdataMixedXml = `<?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:UBLVersionID>2.1</cbc:UBLVersionID>
|
||||
<cbc:ID>CDATA-MIXED-TEST</cbc:ID>
|
||||
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
||||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||||
<cac:AccountingSupplierParty>
|
||||
<cac:Party>
|
||||
<cac:PartyName>
|
||||
<cbc:Name><![CDATA[Company & Co. with <special> chars]]></cbc:Name>
|
||||
</cac:PartyName>
|
||||
</cac:Party>
|
||||
</cac:AccountingSupplierParty>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>1</cbc:ID>
|
||||
<cbc:Note><![CDATA[HTML content: <b>bold</b> and <i>italic</i> text]]></cbc:Note>
|
||||
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
||||
<cac:Item>
|
||||
<cbc:Name>CDATA Test Item</cbc:Name>
|
||||
<cbc:Description><![CDATA[
|
||||
Multi-line description
|
||||
with <XML> markup preserved
|
||||
and "special" characters & symbols
|
||||
]]></cbc:Description>
|
||||
</cac:Item>
|
||||
</cac:InvoiceLine>
|
||||
</Invoice>`;
|
||||
|
||||
try {
|
||||
const einvoice = new EInvoice();
|
||||
await einvoice.fromXmlString(cdataMixedXml);
|
||||
|
||||
const cdataHandled = einvoice.from?.name?.includes('&') &&
|
||||
einvoice.from?.name?.includes('<') &&
|
||||
einvoice.items?.[0]?.name === 'CDATA Test Item';
|
||||
|
||||
console.log(`\nTest 3 - CDATA mixed content:`);
|
||||
console.log(` CDATA content preserved: ${cdataHandled ? 'Yes' : 'No'}`);
|
||||
console.log(` Special characters handled: ${einvoice.from?.name?.includes('&') ? 'Yes' : 'No'}`);
|
||||
|
||||
return { cdataHandled };
|
||||
} catch (error) {
|
||||
console.log(`\nTest 3 - CDATA mixed content:`);
|
||||
console.log(` CDATA parsing failed: ${error.message}`);
|
||||
return { cdataHandled: false };
|
||||
}
|
||||
};
|
||||
|
||||
// Summary
|
||||
console.log('\n=== Mixed Content Encoding Test Summary ===');
|
||||
console.log(`Mixed Content Direct: ${directResult.success ? 'Supported' : 'Not supported (acceptable)'}`);
|
||||
console.log(`UTF-8 Fallback: ${fallbackResult.success ? 'Working' : 'Failed'}`);
|
||||
// Test 4: Whitespace handling in mixed content
|
||||
const testWhitespaceHandling = async () => {
|
||||
const whitespaceXml = `<?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:UBLVersionID>2.1</cbc:UBLVersionID>
|
||||
<cbc:ID>WHITESPACE-TEST</cbc:ID>
|
||||
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
||||
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
||||
<cac:AccountingSupplierParty>
|
||||
<cac:Party>
|
||||
<cac:PartyName>
|
||||
<cbc:Name> Company Name </cbc:Name>
|
||||
</cac:PartyName>
|
||||
</cac:Party>
|
||||
</cac:AccountingSupplierParty>
|
||||
<cac:InvoiceLine>
|
||||
<cbc:ID>1</cbc:ID>
|
||||
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
||||
<cac:Item>
|
||||
<cbc:Name>
|
||||
Test Item
|
||||
</cbc:Name>
|
||||
</cac:Item>
|
||||
</cac:InvoiceLine>
|
||||
</Invoice>`;
|
||||
|
||||
try {
|
||||
const einvoice = new EInvoice();
|
||||
await einvoice.fromXmlString(whitespaceXml);
|
||||
|
||||
// Check how whitespace is handled
|
||||
const whitespacePreserved = einvoice.from?.name === ' Company Name ';
|
||||
const whitespaceNormalized = einvoice.from?.name?.trim() === 'Company Name';
|
||||
|
||||
console.log(`\nTest 4 - Whitespace handling:`);
|
||||
console.log(` Whitespace preserved: ${whitespacePreserved ? 'Yes' : 'No'}`);
|
||||
console.log(` Whitespace normalized: ${whitespaceNormalized ? 'Yes' : 'No'}`);
|
||||
console.log(` Company name value: "${einvoice.from?.name}"`);
|
||||
|
||||
return { whitespacePreserved, whitespaceNormalized };
|
||||
} catch (error) {
|
||||
console.log(`\nTest 4 - Whitespace handling:`);
|
||||
console.log(` Whitespace test failed: ${error.message}`);
|
||||
return { whitespacePreserved: false, whitespaceNormalized: false };
|
||||
}
|
||||
};
|
||||
|
||||
// The test passes if UTF-8 fallback works, since Mixed Content support is optional
|
||||
expect(fallbackResult.success).toBeTrue();
|
||||
// Run all tests
|
||||
const elementResult = await testPureElementContent();
|
||||
const mixedResult = await testMixedContent();
|
||||
const cdataResult = await testCDataMixedContent();
|
||||
const whitespaceResult = await testWhitespaceHandling();
|
||||
|
||||
console.log(`\n=== XML Mixed Content Test Summary ===`);
|
||||
console.log(`Pure element content: ${elementResult.hasProperStructure ? 'Working' : 'Issues'}`);
|
||||
console.log(`Mixed content parsing: ${mixedResult.mixedContentHandled ? 'Working' : 'Issues'}`);
|
||||
console.log(`CDATA mixed content: ${cdataResult.cdataHandled ? 'Working' : 'Issues'}`);
|
||||
console.log(`Whitespace handling: ${whitespaceResult.whitespaceNormalized ? 'Working' : 'Issues'}`);
|
||||
console.log(`Round-trip consistency: ${elementResult.roundTripSuccess ? 'Working' : 'Issues'}`);
|
||||
|
||||
// Test passes if basic element content and mixed content parsing work
|
||||
expect(elementResult.hasProperStructure).toBeTrue();
|
||||
expect(elementResult.roundTripSuccess).toBeTrue();
|
||||
});
|
||||
|
||||
// Run the test
|
||||
|
Reference in New Issue
Block a user