einvoice/test/suite/einvoice_error-handling/test.err-06.concurrent-errors.ts

490 lines
16 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js';
tap.test('ERR-06: Concurrent Errors - should handle concurrent processing errors', async () => {
console.log('Testing concurrent processing error handling...\n');
// Test 1: Concurrent processing of different invoices
const testConcurrentInvoiceProcessing = async () => {
console.log('Test 1 - Concurrent processing of different invoices:');
let allProcessed = true;
let errorsCaught = 0;
const invoiceCount = 5;
try {
const promises = [];
for (let i = 0; i < invoiceCount; i++) {
const promise = (async () => {
try {
const einvoice = new EInvoice();
einvoice.issueDate = new Date(2024, 0, 1);
einvoice.invoiceId = `CONCURRENT-${i + 1}`;
einvoice.from = {
type: 'company',
name: `Company ${i + 1}`,
description: 'Testing concurrent processing',
address: {
streetName: 'Test Street',
houseNumber: String(i + 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: '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: `Item for Invoice ${i + 1}`,
articleNumber: `ART-${i + 1}`,
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100 + i,
vatPercentage: 19
}];
const xml = await einvoice.toXmlString('ubl');
return { success: true, invoiceId: `CONCURRENT-${i + 1}`, xml };
} catch (error) {
return { success: false, error: error.message, invoiceId: `CONCURRENT-${i + 1}` };
}
})();
promises.push(promise);
}
const results = await Promise.all(promises);
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
allProcessed = successful.length === invoiceCount;
errorsCaught = failed.length;
console.log(` Successful: ${successful.length}/${invoiceCount}`);
console.log(` Failed: ${failed.length}/${invoiceCount}`);
if (failed.length > 0) {
console.log(` Errors: ${failed.map(f => f.error).join(', ')}`);
}
} catch (error) {
console.log(` Concurrent processing failed: ${error.message}`);
allProcessed = false;
}
return { allProcessed, errorsCaught };
};
// Test 2: Mixed valid and invalid concurrent operations
const testMixedConcurrentOperations = async () => {
console.log('\nTest 2 - Mixed valid and invalid concurrent operations:');
let validProcessed = 0;
let invalidHandled = 0;
let totalOperations = 0;
try {
const operations = [
// Valid operations
async () => {
const einvoice = new EInvoice();
einvoice.issueDate = new Date(2024, 0, 1);
einvoice.invoiceId = 'VALID-001';
einvoice.from = {
type: 'company',
name: 'Valid Company',
description: 'Valid invoice',
address: {
streetName: 'Valid Street',
houseNumber: '1',
postalCode: '12345',
city: 'Valid City',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB 12345',
registrationName: 'Commercial Register'
}
};
einvoice.to = {
type: 'person',
name: 'Valid',
surname: 'Customer',
salutation: 'Mr' as const,
sex: 'male' as const,
title: 'Doctor' as const,
description: 'Valid customer',
address: {
streetName: 'Customer Street',
houseNumber: '2',
postalCode: '54321',
city: 'Customer City',
country: 'DE'
}
};
einvoice.items = [{
position: 1,
name: 'Valid Item',
articleNumber: 'VALID-001',
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
await einvoice.toXmlString('ubl');
return { type: 'valid', success: true };
},
// Invalid XML parsing
async () => {
const einvoice = new EInvoice();
await einvoice.fromXmlString('<?xml version="1.0"?><Invalid>broken');
return { type: 'invalid', success: false };
},
// Invalid validation (missing required fields)
async () => {
const einvoice = new EInvoice();
await einvoice.toXmlString('ubl'); // Missing required fields
return { type: 'invalid', success: false };
},
// Another valid operation
async () => {
const einvoice = new EInvoice();
einvoice.issueDate = new Date(2024, 0, 1);
einvoice.invoiceId = 'VALID-002';
einvoice.from = {
type: 'company',
name: 'Another Valid Company',
description: 'Another valid invoice',
address: {
streetName: 'Another Valid Street',
houseNumber: '2',
postalCode: '12345',
city: 'Valid City',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB 12345',
registrationName: 'Commercial Register'
}
};
einvoice.to = {
type: 'person',
name: 'Another',
surname: 'Customer',
salutation: 'Ms' as const,
sex: 'female' as const,
title: 'Doctor' as const,
description: 'Another customer',
address: {
streetName: 'Another Customer Street',
houseNumber: '3',
postalCode: '54321',
city: 'Customer City',
country: 'DE'
}
};
einvoice.items = [{
position: 1,
name: 'Another Valid Item',
articleNumber: 'VALID-002',
unitType: 'EA',
unitQuantity: 2,
unitNetPrice: 200,
vatPercentage: 19
}];
await einvoice.toXmlString('cii');
return { type: 'valid', success: true };
}
];
totalOperations = operations.length;
const results = await Promise.allSettled(operations.map(op => op()));
for (const result of results) {
if (result.status === 'fulfilled') {
if (result.value.type === 'valid' && result.value.success) {
validProcessed++;
}
} else {
// Rejected (error caught)
invalidHandled++;
}
}
console.log(` Valid operations processed: ${validProcessed}`);
console.log(` Invalid operations handled: ${invalidHandled}`);
console.log(` Total operations: ${totalOperations}`);
} catch (error) {
console.log(` Mixed operations test failed: ${error.message}`);
}
return { validProcessed, invalidHandled, totalOperations };
};
// Test 3: Concurrent format conversions
const testConcurrentFormatConversions = async () => {
console.log('\nTest 3 - Concurrent format conversions:');
let conversionsSuccessful = 0;
let conversionErrors = 0;
try {
// Create a base invoice
const createBaseInvoice = () => {
const einvoice = new EInvoice();
einvoice.issueDate = new Date(2024, 0, 1);
einvoice.invoiceId = 'CONVERT-TEST';
einvoice.from = {
type: 'company',
name: 'Conversion Test Company',
description: 'Testing format conversions',
address: {
streetName: 'Convert Street',
houseNumber: '1',
postalCode: '12345',
city: 'Convert City',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB 12345',
registrationName: 'Commercial Register'
}
};
einvoice.to = {
type: 'person',
name: 'Convert',
surname: 'Customer',
salutation: 'Mr' as const,
sex: 'male' as const,
title: 'Doctor' as const,
description: 'Convert customer',
address: {
streetName: 'Customer Street',
houseNumber: '2',
postalCode: '54321',
city: 'Customer City',
country: 'DE'
}
};
einvoice.items = [{
position: 1,
name: 'Convert Item',
articleNumber: 'CONVERT-001',
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
return einvoice;
};
const formats = ['ubl', 'cii', 'xrechnung'];
const conversionPromises = [];
for (let i = 0; i < 3; i++) {
for (const format of formats) {
const promise = (async () => {
try {
const einvoice = createBaseInvoice();
einvoice.invoiceId = `CONVERT-${format.toUpperCase()}-${i + 1}`;
const xml = await einvoice.toXmlString(format as any);
return { format, success: true, length: xml.length };
} catch (error) {
return { format, success: false, error: error.message };
}
})();
conversionPromises.push(promise);
}
}
const results = await Promise.all(conversionPromises);
conversionsSuccessful = results.filter(r => r.success).length;
conversionErrors = results.filter(r => !r.success).length;
console.log(` Successful conversions: ${conversionsSuccessful}/${results.length}`);
console.log(` Conversion errors: ${conversionErrors}/${results.length}`);
if (conversionErrors > 0) {
const errorFormats = results.filter(r => !r.success).map(r => r.format);
console.log(` Failed formats: ${errorFormats.join(', ')}`);
}
} catch (error) {
console.log(` Concurrent conversions failed: ${error.message}`);
}
return { conversionsSuccessful, conversionErrors };
};
// Test 4: Error isolation between concurrent operations
const testErrorIsolation = async () => {
console.log('\nTest 4 - Error isolation between concurrent operations:');
let isolationWorking = false;
let validOperationSucceeded = false;
try {
const operations = [
// This should fail
async () => {
const einvoice = new EInvoice();
await einvoice.fromXmlString('<?xml version="1.0"?><Broken>unclosed');
},
// This should succeed despite the other failing
async () => {
const einvoice = new EInvoice();
einvoice.issueDate = new Date(2024, 0, 1);
einvoice.invoiceId = 'ISOLATION-TEST';
einvoice.from = {
type: 'company',
name: 'Isolation Company',
description: 'Testing error isolation',
address: {
streetName: 'Isolation Street',
houseNumber: '1',
postalCode: '12345',
city: 'Isolation City',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB 12345',
registrationName: 'Commercial Register'
}
};
einvoice.to = {
type: 'person',
name: 'Isolation',
surname: 'Customer',
salutation: 'Mr' as const,
sex: 'male' as const,
title: 'Doctor' as const,
description: 'Isolation customer',
address: {
streetName: 'Customer Street',
houseNumber: '2',
postalCode: '54321',
city: 'Customer City',
country: 'DE'
}
};
einvoice.items = [{
position: 1,
name: 'Isolation Item',
articleNumber: 'ISOLATION-001',
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
const xml = await einvoice.toXmlString('ubl');
return xml.includes('ISOLATION-TEST');
}
];
const results = await Promise.allSettled(operations);
// First operation should fail
const failedAsExpected = results[0].status === 'rejected';
// Second operation should succeed
validOperationSucceeded = results[1].status === 'fulfilled' &&
typeof results[1].value === 'boolean' && results[1].value === true;
isolationWorking = failedAsExpected && validOperationSucceeded;
console.log(` Invalid operation failed as expected: ${failedAsExpected ? 'Yes' : 'No'}`);
console.log(` Valid operation succeeded despite error: ${validOperationSucceeded ? 'Yes' : 'No'}`);
console.log(` Error isolation working: ${isolationWorking ? 'Yes' : 'No'}`);
} catch (error) {
console.log(` Error isolation test failed: ${error.message}`);
}
return { isolationWorking, validOperationSucceeded };
};
// Run all tests
const result1 = await testConcurrentInvoiceProcessing();
const result2 = await testMixedConcurrentOperations();
const result3 = await testConcurrentFormatConversions();
const result4 = await testErrorIsolation();
console.log('\n=== Concurrent Error Handling Summary ===');
console.log(`Concurrent processing: ${result1.allProcessed ? 'Working' : 'Partial/Failed'}`);
console.log(`Mixed operations: ${result2.validProcessed > 0 ? 'Working' : 'Failed'}`);
console.log(`Format conversions: ${result3.conversionsSuccessful > 0 ? 'Working' : 'Failed'}`);
console.log(`Error isolation: ${result4.isolationWorking ? 'Working' : 'Failed'}`);
// Test passes if core concurrent processing capabilities work
const basicConcurrentWorks = result1.allProcessed; // All 5 invoices processed
const formatConversionsWork = result3.conversionsSuccessful === 9; // All 9 conversions successful
const mixedOperationsWork = result2.validProcessed > 0; // Valid operations work in mixed scenarios
expect(basicConcurrentWorks).toBeTrue(); // Must process multiple invoices concurrently
expect(formatConversionsWork).toBeTrue(); // Must handle concurrent format conversions
expect(mixedOperationsWork).toBeTrue(); // Must handle mixed valid/invalid operations
});
tap.start();