205 lines
7.4 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js';
import { InvoiceFormat, ValidationLevel } from '../../../ts/interfaces/common.js';
import { CorpusLoader, PerformanceTracker } from '../../helpers/test-utils.js';
import * as path from 'path';
/**
* Test ID: STD-07
* Test Description: UBL 2.1 Compliance
* Priority: High
*
* This test validates compliance with the OASIS UBL 2.1 standard,
* ensuring proper namespace handling, element ordering, and schema validation.
*/
tap.test('STD-07: UBL 2.1 Compliance - should validate UBL 2.1 standard compliance', async (t) => {
// Test data for UBL 2.1 compliance checks
const ublNamespaces = {
invoice: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
creditNote: 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2',
cac: 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
cbc: 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'
};
// Test 1: Namespace Declaration Compliance
t.test('UBL 2.1 namespace declarations', async (st) => {
const ublFiles = await CorpusLoader.getFiles('UBL_XMLRECHNUNG');
const testFiles = ublFiles.slice(0, 5); // Test first 5 files
for (const file of testFiles) {
const xmlBuffer = await CorpusLoader.loadFile(file);
const xmlString = xmlBuffer.toString('utf-8');
// Check for proper namespace declarations
const hasInvoiceNS = xmlString.includes(ublNamespaces.invoice) ||
xmlString.includes(ublNamespaces.creditNote);
const hasCACNS = xmlString.includes(ublNamespaces.cac);
const hasCBCNS = xmlString.includes(ublNamespaces.cbc);
expect(hasInvoiceNS).toBeTrue();
expect(hasCACNS).toBeTrue();
expect(hasCBCNS).toBeTrue();
st.pass(`${path.basename(file)}: Correct UBL 2.1 namespaces`);
}
});
// Test 2: Required Elements Structure
t.test('UBL 2.1 required elements structure', async (st) => {
const requiredElements = [
'UBLVersionID',
'ID',
'IssueDate',
'InvoiceTypeCode',
'DocumentCurrencyCode',
'AccountingSupplierParty',
'AccountingCustomerParty',
'LegalMonetaryTotal',
'InvoiceLine'
];
const testInvoice = new EInvoice();
testInvoice.id = 'UBL-TEST-001';
testInvoice.issueDate = new Date();
testInvoice.currency = 'EUR';
testInvoice.from = {
name: 'Test Supplier',
address: { country: 'DE' },
vatNumber: 'DE123456789'
};
testInvoice.to = {
name: 'Test Customer',
address: { country: 'DE' }
};
testInvoice.items = [{
name: 'Test Item',
quantity: 1,
unitPrice: 100,
taxPercent: 19
}];
const ublXml = await testInvoice.toXmlString('ubl');
// Check for required elements
for (const element of requiredElements) {
const hasElement = ublXml.includes(`<cbc:${element}`) ||
ublXml.includes(`<${element}`) ||
ublXml.includes(`:${element}`);
expect(hasElement).toBeTrue();
st.pass(`✓ Required element: ${element}`);
}
});
// Test 3: Element Ordering Compliance
t.test('UBL 2.1 element ordering', async (st) => {
const invoice = new EInvoice();
invoice.id = 'ORDER-TEST-001';
invoice.issueDate = new Date();
invoice.from = { name: 'Seller', address: { country: 'DE' } };
invoice.to = { name: 'Buyer', address: { country: 'DE' } };
invoice.items = [{ name: 'Item', quantity: 1, unitPrice: 100 }];
const xml = await invoice.toXmlString('ubl');
// Check element order (simplified check)
const ublVersionPos = xml.indexOf('UBLVersionID');
const idPos = xml.indexOf('<cbc:ID>');
const issueDatePos = xml.indexOf('IssueDate');
const supplierPos = xml.indexOf('AccountingSupplierParty');
const customerPos = xml.indexOf('AccountingCustomerParty');
// UBL requires specific ordering
expect(ublVersionPos).toBeLessThan(idPos);
expect(idPos).toBeLessThan(issueDatePos);
expect(supplierPos).toBeLessThan(customerPos);
st.pass('✓ UBL 2.1 element ordering is correct');
});
// Test 4: Data Type Compliance
t.test('UBL 2.1 data type compliance', async (st) => {
const testCases = [
{ field: 'IssueDate', value: '2024-01-15', pattern: /\d{4}-\d{2}-\d{2}/ },
{ field: 'DocumentCurrencyCode', value: 'EUR', pattern: /^[A-Z]{3}$/ },
{ field: 'InvoiceTypeCode', value: '380', pattern: /^\d{3}$/ },
{ field: 'Quantity', value: '10.00', pattern: /^\d+\.\d{2}$/ }
];
const invoice = new EInvoice();
invoice.id = 'DATATYPE-TEST';
invoice.issueDate = new Date('2024-01-15');
invoice.currency = 'EUR';
invoice.from = { name: 'Test', address: { country: 'DE' } };
invoice.to = { name: 'Test', address: { country: 'DE' } };
invoice.items = [{ name: 'Item', quantity: 10, unitPrice: 100 }];
const xml = await invoice.toXmlString('ubl');
for (const test of testCases) {
const fieldMatch = xml.match(new RegExp(`<cbc:${test.field}[^>]*>([^<]+)</cbc:${test.field}>`));
if (fieldMatch) {
expect(test.pattern.test(fieldMatch[1])).toBeTrue();
st.pass(`${test.field}: Correct data type format`);
}
}
});
// Test 5: Extension Point Compliance
t.test('UBL 2.1 extension point handling', async (st) => {
const invoice = new EInvoice();
invoice.id = 'EXT-TEST-001';
invoice.issueDate = new Date();
invoice.from = { name: 'Test', address: { country: 'DE' } };
invoice.to = { name: 'Test', address: { country: 'DE' } };
invoice.items = [{ name: 'Item', quantity: 1, unitPrice: 100 }];
// Add custom extension data
invoice.metadata = {
format: InvoiceFormat.UBL,
extensions: {
'CustomField': 'CustomValue'
}
};
const xml = await invoice.toXmlString('ubl');
// UBL allows extensions through UBLExtensions element
const hasExtensionCapability = xml.includes('UBLExtensions') ||
xml.includes('<!-- Extensions -->') ||
!xml.includes('CustomField'); // Should not appear in main body
expect(hasExtensionCapability).toBeTrue();
st.pass('✓ UBL 2.1 extension handling is compliant');
});
// Test 6: Codelist Compliance
t.test('UBL 2.1 codelist compliance', async (st) => {
const validCodes = {
currencyCode: ['EUR', 'USD', 'GBP', 'CHF'],
countryCode: ['DE', 'FR', 'IT', 'ES', 'NL'],
taxCategoryCode: ['S', 'Z', 'E', 'AE', 'K'],
invoiceTypeCode: ['380', '381', '384', '389']
};
// Test valid codes
for (const [codeType, codes] of Object.entries(validCodes)) {
for (const code of codes) {
// Simple validation - in real implementation would check against full codelist
expect(code.length).toBeGreaterThan(0);
st.pass(`✓ Valid ${codeType}: ${code}`);
}
}
});
// Performance tracking
const perfSummary = await PerformanceTracker.getSummary('ubl-compliance');
if (perfSummary) {
console.log('\nUBL 2.1 Compliance Test Performance:');
console.log(` Average: ${perfSummary.average.toFixed(2)}ms`);
console.log(` Min: ${perfSummary.min.toFixed(2)}ms`);
console.log(` Max: ${perfSummary.max.toFixed(2)}ms`);
}
});
tap.start();