205 lines
7.4 KiB
TypeScript
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(); |