2025-05-26 04:04:51 +00:00
|
|
|
import { tap } from '@git.zone/tstest/tapbundle';
|
|
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
|
|
import { PerformanceTracker } from '../performance.tracker.js';
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
tap.test('EDGE-07: Maximum Field Lengths - should handle fields at maximum allowed lengths', async () => {
|
2025-05-26 04:04:51 +00:00
|
|
|
// Test 1: Standard field length limits
|
2025-05-27 15:26:22 +00:00
|
|
|
await PerformanceTracker.track('standard-field-limits', async () => {
|
|
|
|
const fieldTests = [
|
|
|
|
{ field: 'invoiceId', maxLength: 200, testValue: 'X' },
|
|
|
|
{ field: 'customerName', maxLength: 200, testValue: 'A' },
|
|
|
|
{ field: 'streetName', maxLength: 1000, testValue: 'B' },
|
|
|
|
{ field: 'notes', maxLength: 5000, testValue: 'C' }
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const test of fieldTests) {
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
|
|
|
|
// Test at max length
|
|
|
|
const maxValue = test.testValue.repeat(test.maxLength);
|
|
|
|
|
|
|
|
if (test.field === 'invoiceId') {
|
|
|
|
einvoice.invoiceId = maxValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: test.field === 'customerName' ? maxValue : 'Test Company',
|
|
|
|
description: 'Testing max field lengths',
|
|
|
|
address: {
|
|
|
|
streetName: test.field === 'streetName' ? maxValue : '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'
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
einvoice.to = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Customer Company',
|
|
|
|
description: '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: 'Commercial Register'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (test.field === 'notes') {
|
|
|
|
einvoice.notes = [maxValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
einvoice.items = [{
|
|
|
|
position: 1,
|
|
|
|
name: 'Test Item',
|
|
|
|
articleNumber: 'TEST-001',
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: 100,
|
|
|
|
vatPercentage: 19
|
|
|
|
}];
|
|
|
|
|
|
|
|
try {
|
|
|
|
const xmlString = await einvoice.toXmlString('ubl');
|
|
|
|
console.log(`Field ${test.field} at max length (${test.maxLength}): XML generated, size: ${xmlString.length} bytes`);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
// Test round-trip
|
|
|
|
const newInvoice = new EInvoice();
|
|
|
|
await newInvoice.fromXmlString(xmlString);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
let preserved = false;
|
|
|
|
if (test.field === 'invoiceId') {
|
|
|
|
preserved = newInvoice.invoiceId === maxValue;
|
|
|
|
} else if (test.field === 'customerName') {
|
|
|
|
preserved = newInvoice.from.name === maxValue;
|
|
|
|
} else if (test.field === 'streetName') {
|
|
|
|
preserved = newInvoice.from.address.streetName === maxValue;
|
|
|
|
} else if (test.field === 'notes') {
|
|
|
|
preserved = newInvoice.notes?.[0] === maxValue;
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
|
|
|
|
console.log(`Field ${test.field} preservation: ${preserved ? 'preserved' : 'truncated'}`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`Field ${test.field} at max length failed: ${error.message}`);
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
// Test over max length
|
|
|
|
const overValue = test.testValue.repeat(test.maxLength + 100);
|
|
|
|
const overInvoice = new EInvoice();
|
|
|
|
overInvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
|
|
|
|
if (test.field === 'invoiceId') {
|
|
|
|
overInvoice.invoiceId = overValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
overInvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: test.field === 'customerName' ? overValue : 'Test Company',
|
|
|
|
description: 'Testing over max field lengths',
|
|
|
|
address: {
|
|
|
|
streetName: test.field === 'streetName' ? overValue : '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'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
overInvoice.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'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (test.field === 'notes') {
|
|
|
|
overInvoice.notes = [overValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
overInvoice.items = [{
|
|
|
|
position: 1,
|
|
|
|
name: 'Test Item',
|
|
|
|
articleNumber: 'TEST-001',
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: 100,
|
|
|
|
vatPercentage: 19
|
|
|
|
}];
|
|
|
|
|
|
|
|
try {
|
|
|
|
const overXmlString = await overInvoice.toXmlString('ubl');
|
|
|
|
console.log(`Field ${test.field} over max length: still generated XML (${overXmlString.length} bytes)`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`Field ${test.field} over max length: properly rejected - ${error.message}`);
|
|
|
|
}
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 2: Unicode character length vs byte length
|
2025-05-27 15:26:22 +00:00
|
|
|
await PerformanceTracker.track('unicode-length-vs-bytes', async () => {
|
|
|
|
const testCases = [
|
|
|
|
{ name: 'ascii-only', char: 'A', bytesPerChar: 1 },
|
|
|
|
{ name: 'latin-extended', char: 'ñ', bytesPerChar: 2 },
|
|
|
|
{ name: 'chinese', char: '中', bytesPerChar: 3 },
|
|
|
|
{ name: 'emoji', char: '😀', bytesPerChar: 4 }
|
|
|
|
];
|
|
|
|
|
|
|
|
const maxChars = 100;
|
|
|
|
|
|
|
|
for (const test of testCases) {
|
|
|
|
const value = test.char.repeat(maxChars);
|
|
|
|
const byteLength = Buffer.from(value, 'utf8').length;
|
|
|
|
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
einvoice.invoiceId = 'UNICODE-TEST';
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: value,
|
|
|
|
description: `Unicode test: ${test.name}`,
|
|
|
|
address: {
|
|
|
|
streetName: 'Unicode Street',
|
|
|
|
houseNumber: '1',
|
|
|
|
postalCode: '12345',
|
|
|
|
city: 'Unicode City',
|
|
|
|
country: 'DE'
|
2025-05-26 04:04:51 +00:00
|
|
|
},
|
2025-05-27 15:26:22 +00:00
|
|
|
status: 'active',
|
|
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
|
|
registrationDetails: {
|
|
|
|
vatId: 'DE123456789',
|
|
|
|
registrationId: 'HRB 12345',
|
|
|
|
registrationName: 'Commercial Register'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
einvoice.to = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Customer Company',
|
|
|
|
description: 'Customer',
|
|
|
|
address: {
|
|
|
|
streetName: 'Customer Street',
|
|
|
|
houseNumber: '2',
|
|
|
|
postalCode: '54321',
|
|
|
|
city: 'Customer City',
|
|
|
|
country: 'DE'
|
2025-05-26 04:04:51 +00:00
|
|
|
},
|
2025-05-27 15:26:22 +00:00
|
|
|
status: 'active',
|
|
|
|
foundedDate: { year: 2019, month: 1, day: 1 },
|
|
|
|
registrationDetails: {
|
|
|
|
vatId: 'DE987654321',
|
|
|
|
registrationId: 'HRB 54321',
|
|
|
|
registrationName: 'Commercial Register'
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
};
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
einvoice.items = [{
|
|
|
|
position: 1,
|
|
|
|
name: 'Test Item',
|
|
|
|
articleNumber: 'TEST-001',
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: 100,
|
|
|
|
vatPercentage: 19
|
|
|
|
}];
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
try {
|
|
|
|
const xmlString = await einvoice.toXmlString('cii');
|
|
|
|
const newInvoice = new EInvoice();
|
|
|
|
await newInvoice.fromXmlString(xmlString);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
const retrievedValue = newInvoice.from.name;
|
|
|
|
const preserved = retrievedValue === value;
|
|
|
|
const retrievedBytes = Buffer.from(retrievedValue, 'utf8').length;
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
console.log(`Unicode ${test.name}: chars=${value.length}, bytes=${byteLength}, expectedBytes=${maxChars * test.bytesPerChar}`);
|
|
|
|
console.log(` Preserved: ${preserved}, retrieved chars=${retrievedValue.length}, bytes=${retrievedBytes}`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`Unicode ${test.name} failed: ${error.message}`);
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
// Test 3: Long invoice numbers
|
|
|
|
await PerformanceTracker.track('long-invoice-numbers', async () => {
|
|
|
|
const lengths = [50, 100, 200, 500];
|
|
|
|
|
|
|
|
for (const length of lengths) {
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
einvoice.invoiceId = 'INV-' + '0'.repeat(length - 4);
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Test Company',
|
|
|
|
description: 'Testing long invoice numbers',
|
|
|
|
address: {
|
|
|
|
streetName: 'Test Street',
|
|
|
|
houseNumber: '1',
|
|
|
|
postalCode: '12345',
|
|
|
|
city: 'Test City',
|
|
|
|
country: 'DE'
|
2025-05-26 04:04:51 +00:00
|
|
|
},
|
2025-05-27 15:26:22 +00:00
|
|
|
status: 'active',
|
|
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
|
|
registrationDetails: {
|
|
|
|
vatId: 'DE123456789',
|
|
|
|
registrationId: 'HRB 12345',
|
|
|
|
registrationName: 'Commercial Register'
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
};
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
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'
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
};
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
einvoice.items = [{
|
|
|
|
position: 1,
|
|
|
|
name: 'Test Item',
|
|
|
|
articleNumber: 'TEST-001',
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: 100,
|
|
|
|
vatPercentage: 19
|
|
|
|
}];
|
|
|
|
|
|
|
|
try {
|
|
|
|
const xmlString = await einvoice.toXmlString('xrechnung');
|
|
|
|
const newInvoice = new EInvoice();
|
|
|
|
await newInvoice.fromXmlString(xmlString);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
console.log(`Invoice number length ${length}: ${newInvoice.invoiceId.length === length ? 'preserved' : 'modified'}`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`Invoice number length ${length} failed: ${error.message}`);
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
// Test 4: Line item count limits
|
|
|
|
await PerformanceTracker.track('line-item-count-limits', async () => {
|
|
|
|
const itemCounts = [100, 500, 1000];
|
|
|
|
|
|
|
|
for (const count of itemCounts) {
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
einvoice.invoiceId = `MANY-ITEMS-${count}`;
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Bulk Seller Company',
|
|
|
|
description: 'Testing many line items',
|
|
|
|
address: {
|
|
|
|
streetName: 'Bulk Street',
|
|
|
|
houseNumber: '1',
|
|
|
|
postalCode: '12345',
|
|
|
|
city: 'Bulk 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: 'Bulk Buyer Company',
|
|
|
|
description: 'Customer buying many items',
|
|
|
|
address: {
|
|
|
|
streetName: 'Buyer Street',
|
|
|
|
houseNumber: '2',
|
|
|
|
postalCode: '54321',
|
|
|
|
city: 'Buyer City',
|
|
|
|
country: 'DE'
|
|
|
|
},
|
|
|
|
status: 'active',
|
|
|
|
foundedDate: { year: 2019, month: 1, day: 1 },
|
|
|
|
registrationDetails: {
|
|
|
|
vatId: 'DE987654321',
|
|
|
|
registrationId: 'HRB 54321',
|
|
|
|
registrationName: 'Commercial Register'
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Create many items
|
|
|
|
einvoice.items = [];
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
einvoice.items.push({
|
|
|
|
position: i + 1,
|
|
|
|
name: `Item ${i + 1}`,
|
|
|
|
articleNumber: `ART-${String(i + 1).padStart(5, '0')}`,
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: 10 + (i % 100),
|
|
|
|
vatPercentage: 19
|
|
|
|
});
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
try {
|
|
|
|
const xmlString = await einvoice.toXmlString('ubl');
|
|
|
|
const endTime = Date.now();
|
|
|
|
const timeTaken = endTime - startTime;
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
const newInvoice = new EInvoice();
|
|
|
|
await newInvoice.fromXmlString(xmlString);
|
|
|
|
const itemsParsed = newInvoice.items.length;
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
console.log(`Line items ${count}: parsed=${itemsParsed}, time=${timeTaken}ms, avg=${(timeTaken/count).toFixed(2)}ms/item`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`Line items ${count} failed: ${error.message}`);
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
// Test 5: Long email addresses
|
|
|
|
await PerformanceTracker.track('long-email-addresses', async () => {
|
|
|
|
const emailLengths = [50, 100, 254]; // RFC 5321 limit
|
|
|
|
|
|
|
|
for (const length of emailLengths) {
|
|
|
|
const localPart = 'x'.repeat(Math.max(1, length - 20));
|
|
|
|
const email = localPart + '@example.com';
|
|
|
|
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
einvoice.invoiceId = 'EMAIL-TEST';
|
|
|
|
einvoice.electronicAddress = {
|
|
|
|
scheme: 'EMAIL',
|
|
|
|
value: email.substring(0, length)
|
|
|
|
};
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Email Test Company',
|
|
|
|
description: 'Testing long email addresses',
|
|
|
|
address: {
|
|
|
|
streetName: 'Email Street',
|
|
|
|
houseNumber: '1',
|
|
|
|
postalCode: '12345',
|
|
|
|
city: 'Email City',
|
|
|
|
country: 'DE'
|
|
|
|
},
|
|
|
|
status: 'active',
|
|
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
|
|
registrationDetails: {
|
|
|
|
vatId: 'DE123456789',
|
|
|
|
registrationId: 'HRB 12345',
|
|
|
|
registrationName: 'Commercial Register'
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
};
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
einvoice.to = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Customer Company',
|
|
|
|
description: '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: 'Commercial Register'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
einvoice.items = [{
|
|
|
|
position: 1,
|
|
|
|
name: 'Test Item',
|
|
|
|
articleNumber: 'TEST-001',
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: 100,
|
|
|
|
vatPercentage: 19
|
|
|
|
}];
|
|
|
|
|
|
|
|
try {
|
|
|
|
const xmlString = await einvoice.toXmlString('ubl');
|
|
|
|
const newInvoice = new EInvoice();
|
|
|
|
await newInvoice.fromXmlString(xmlString);
|
|
|
|
|
|
|
|
const preserved = newInvoice.electronicAddress?.value === email.substring(0, length);
|
|
|
|
console.log(`Email length ${length}: ${preserved ? 'preserved' : 'modified'}`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`Email length ${length} failed: ${error.message}`);
|
|
|
|
}
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
// Test 6: Decimal precision limits
|
|
|
|
await PerformanceTracker.track('decimal-precision-limits', async () => {
|
|
|
|
const precisionTests = [
|
|
|
|
{ decimals: 2, value: 12345678901234567890.12 },
|
|
|
|
{ decimals: 4, value: 123456789012345.1234 },
|
|
|
|
{ decimals: 6, value: 1234567890.123456 }
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const test of precisionTests) {
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
einvoice.invoiceId = 'DECIMAL-TEST';
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Decimal Test Company',
|
|
|
|
description: 'Testing decimal precision',
|
|
|
|
address: {
|
|
|
|
streetName: 'Decimal Street',
|
|
|
|
houseNumber: '1',
|
|
|
|
postalCode: '12345',
|
|
|
|
city: 'Decimal City',
|
|
|
|
country: 'DE'
|
|
|
|
},
|
|
|
|
status: 'active',
|
|
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
|
|
registrationDetails: {
|
|
|
|
vatId: 'DE123456789',
|
|
|
|
registrationId: 'HRB 12345',
|
|
|
|
registrationName: 'Commercial Register'
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
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'
|
|
|
|
}
|
|
|
|
};
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
einvoice.items = [{
|
|
|
|
position: 1,
|
|
|
|
name: 'High Precision Item',
|
|
|
|
articleNumber: 'DECIMAL-001',
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: test.value,
|
|
|
|
vatPercentage: 19.123456 // Test VAT precision too
|
|
|
|
}];
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
try {
|
|
|
|
const xmlString = await einvoice.toXmlString('cii');
|
|
|
|
const newInvoice = new EInvoice();
|
|
|
|
await newInvoice.fromXmlString(xmlString);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
const originalStr = test.value.toString();
|
|
|
|
const parsedValue = newInvoice.items[0].unitNetPrice;
|
|
|
|
const parsedStr = parsedValue.toString();
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
console.log(`Decimal precision ${test.decimals}: original=${originalStr}, parsed=${parsedStr}`);
|
|
|
|
console.log(` Preserved: ${parsedValue === test.value}`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`Decimal precision ${test.decimals} failed: ${error.message}`);
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
// Test 7: Long postal codes and phone numbers
|
|
|
|
await PerformanceTracker.track('postal-codes-phone-numbers', async () => {
|
|
|
|
const tests = [
|
|
|
|
{ field: 'postalCode', lengths: [5, 10, 20] },
|
|
|
|
{ field: 'phone', lengths: [10, 20, 30] }
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const test of tests) {
|
|
|
|
for (const length of test.lengths) {
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
einvoice.invoiceId = `${test.field.toUpperCase()}-${length}`;
|
|
|
|
|
|
|
|
const value = '1234567890'.repeat(Math.ceil(length / 10)).substring(0, length);
|
|
|
|
|
|
|
|
einvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Field Length Test Company',
|
|
|
|
description: `Testing ${test.field} length`,
|
|
|
|
address: {
|
|
|
|
streetName: 'Test Street',
|
|
|
|
houseNumber: '1',
|
|
|
|
postalCode: test.field === 'postalCode' ? value : '12345',
|
|
|
|
city: 'Test City',
|
|
|
|
country: 'DE'
|
|
|
|
},
|
|
|
|
status: 'active',
|
|
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
|
|
registrationDetails: {
|
|
|
|
vatId: 'DE123456789',
|
|
|
|
registrationId: 'HRB 12345',
|
|
|
|
registrationName: 'Commercial Register'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (test.field === 'phone') {
|
|
|
|
(einvoice.from as any).phone = value;
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
|
|
|
|
einvoice.to = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Customer Company',
|
|
|
|
description: '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: 'Commercial Register'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
einvoice.items = [{
|
|
|
|
position: 1,
|
|
|
|
name: 'Test Item',
|
|
|
|
articleNumber: 'TEST-001',
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: 100,
|
|
|
|
vatPercentage: 19
|
|
|
|
}];
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
try {
|
2025-05-27 15:26:22 +00:00
|
|
|
const xmlString = await einvoice.toXmlString('ubl');
|
|
|
|
const newInvoice = new EInvoice();
|
|
|
|
await newInvoice.fromXmlString(xmlString);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
let preserved = false;
|
|
|
|
if (test.field === 'postalCode') {
|
|
|
|
preserved = newInvoice.from.address.postalCode === value;
|
|
|
|
} else if (test.field === 'phone') {
|
|
|
|
preserved = (newInvoice.from as any).phone === value;
|
|
|
|
}
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
console.log(`${test.field} length ${length}: ${preserved ? 'preserved' : 'modified'}`);
|
2025-05-26 04:04:51 +00:00
|
|
|
} catch (error) {
|
2025-05-27 15:26:22 +00:00
|
|
|
console.log(`${test.field} length ${length} failed: ${error.message}`);
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
// Test 8: Performance impact of field lengths
|
|
|
|
await PerformanceTracker.track('field-length-performance-impact', async () => {
|
|
|
|
const lengths = [10, 100, 1000, 10000];
|
|
|
|
const performanceResults = [];
|
|
|
|
|
|
|
|
for (const length of lengths) {
|
|
|
|
const iterations = 5;
|
|
|
|
const times = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < iterations; i++) {
|
|
|
|
const value = 'X'.repeat(length);
|
|
|
|
const einvoice = new EInvoice();
|
|
|
|
einvoice.issueDate = new Date(2024, 0, 1);
|
|
|
|
einvoice.invoiceId = 'PERF-TEST';
|
|
|
|
einvoice.subject = value;
|
|
|
|
einvoice.notes = [value, value, value];
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
einvoice.from = {
|
|
|
|
type: 'company',
|
|
|
|
name: value.substring(0, 200),
|
|
|
|
description: value,
|
|
|
|
address: {
|
|
|
|
streetName: value.substring(0, 1000),
|
|
|
|
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'
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
};
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
einvoice.to = {
|
|
|
|
type: 'company',
|
|
|
|
name: 'Customer Company',
|
|
|
|
description: '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: 'Commercial Register'
|
|
|
|
}
|
|
|
|
};
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
einvoice.items = [{
|
|
|
|
position: 1,
|
|
|
|
name: value.substring(0, 500),
|
|
|
|
articleNumber: 'TEST-001',
|
|
|
|
unitType: 'EA',
|
|
|
|
unitQuantity: 1,
|
|
|
|
unitNetPrice: 100,
|
|
|
|
vatPercentage: 19
|
|
|
|
}];
|
|
|
|
|
|
|
|
const startTime = process.hrtime.bigint();
|
|
|
|
|
|
|
|
try {
|
|
|
|
await einvoice.toXmlString('ubl');
|
|
|
|
} catch (error) {
|
|
|
|
// Ignore errors for performance testing
|
|
|
|
}
|
|
|
|
|
|
|
|
const endTime = process.hrtime.bigint();
|
|
|
|
times.push(Number(endTime - startTime) / 1000000); // Convert to ms
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
|
2025-05-27 15:26:22 +00:00
|
|
|
const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
|
|
|
|
performanceResults.push({
|
|
|
|
fieldLength: length,
|
|
|
|
avgParseTime: avgTime,
|
|
|
|
timePerKB: avgTime / (length * 5 / 1024) // 5 fields with this length
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log(`Field length ${length}: avg time=${avgTime.toFixed(2)}ms, per KB=${(avgTime / (length * 5 / 1024)).toFixed(2)}ms`);
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-27 15:26:22 +00:00
|
|
|
|
|
|
|
// Check performance scaling
|
|
|
|
for (let i = 1; i < performanceResults.length; i++) {
|
|
|
|
const ratio = performanceResults[i].avgParseTime / performanceResults[i-1].avgParseTime;
|
|
|
|
const lengthRatio = performanceResults[i].fieldLength / performanceResults[i-1].fieldLength;
|
|
|
|
console.log(`Performance scaling ${performanceResults[i-1].fieldLength}→${performanceResults[i].fieldLength}: time ratio=${ratio.toFixed(2)}, length ratio=${lengthRatio}`);
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Run the test
|
|
|
|
tap.start();
|