import { tap } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; tap.test('EDGE-07: Maximum Field Lengths - should handle fields at maximum allowed lengths', async () => { console.log('Testing maximum field lengths in e-invoices...\n'); // Test 1: Standard field length limits per EN16931 const testStandardFieldLimits = async () => { const fieldTests = [ { field: 'invoiceId', maxLength: 30, testValue: 'INV' }, // BT-1 Invoice number { field: 'customerName', maxLength: 200, testValue: 'ACME' }, // BT-44 Buyer name { field: 'streetName', maxLength: 1000, testValue: 'Street' }, // BT-35 Buyer address line 1 { field: 'subject', maxLength: 100, testValue: 'SUBJ' }, // Invoice subject { field: 'notes', maxLength: 5000, testValue: 'NOTE' } // BT-22 Invoice note ]; console.log('Test 1 - Standard field limits:'); for (const test of fieldTests) { try { const einvoice = new EInvoice(); einvoice.issueDate = new Date(2024, 0, 1); // Test at max length const maxValue = test.testValue.repeat(Math.ceil(test.maxLength / test.testValue.length)).substring(0, test.maxLength); if (test.field === 'invoiceId') { einvoice.invoiceId = maxValue; } else if (test.field === 'subject') { einvoice.subject = 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' } }; 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 }]; // Generate XML const xmlString = await einvoice.toXmlString('ubl'); // Test round-trip const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); 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 === 'subject') { preserved = newInvoice.subject === maxValue; } else if (test.field === 'notes') { preserved = newInvoice.notes?.[0] === maxValue; } console.log(` ${test.field} (${test.maxLength} chars): ${preserved ? 'preserved' : 'truncated'}`); // Test over max length (+50 chars) const overValue = test.testValue.repeat(Math.ceil((test.maxLength + 50) / test.testValue.length)).substring(0, test.maxLength + 50); const overInvoice = new EInvoice(); overInvoice.issueDate = new Date(2024, 0, 1); if (test.field === 'invoiceId') { overInvoice.invoiceId = overValue; } else if (test.field === 'subject') { overInvoice.subject = 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 { await overInvoice.toXmlString('ubl'); console.log(` ${test.field} (+50 chars): handled gracefully`); } catch (error) { console.log(` ${test.field} (+50 chars): properly rejected`); } } catch (error) { console.log(` ${test.field}: Failed - ${error.message}`); } } }; // Test 2: Unicode character length vs byte length const testUnicodeLengthVsBytes = async () => { console.log('\nTest 2 - Unicode length vs bytes:'); const testCases = [ { name: 'ASCII', 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) { try { 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' }, 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' }, 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 }]; const xmlString = await einvoice.toXmlString('cii'); const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const retrievedValue = newInvoice.from.name; const preserved = retrievedValue === value; console.log(` ${test.name}: chars=${value.length}, bytes=${byteLength}, preserved=${preserved ? 'Yes' : 'No'}`); } catch (error) { console.log(` ${test.name}: Failed - ${error.message}`); } } }; // Test 3: Long invoice numbers per EN16931 BT-1 const testLongInvoiceNumbers = async () => { console.log('\nTest 3 - Long invoice numbers:'); const lengths = [10, 20, 30, 50]; // EN16931 recommends max 30 for (const length of lengths) { try { 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' }, 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: 'Test Item', articleNumber: 'TEST-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; const xmlString = await einvoice.toXmlString('xrechnung'); const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const preserved = newInvoice.invoiceId.length === length; const status = length <= 30 ? 'within spec' : 'over spec'; console.log(` Invoice ID ${length} chars: ${preserved ? 'preserved' : 'modified'} (${status})`); } catch (error) { console.log(` Invoice ID ${length} chars: Failed - ${error.message}`); } } }; // Test 4: Line item count limits const testLineItemCountLimits = async () => { console.log('\nTest 4 - Line item count limits:'); const itemCounts = [10, 50, 100, 500]; for (const count of itemCounts) { try { 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' } }; // 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 }); } const xmlString = await einvoice.toXmlString('ubl'); const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const itemsParsed = newInvoice.items.length; console.log(` Line items ${count}: parsed=${itemsParsed}, preserved=${itemsParsed === count ? 'Yes' : 'No'}`); } catch (error) { console.log(` Line items ${count}: Failed - ${error.message}`); } } }; // Test 5: Long email addresses per RFC 5321 const testLongEmailAddresses = async () => { console.log('\nTest 5 - Long email addresses:'); const emailLengths = [50, 100, 254]; // RFC 5321 limit is 254 for (const length of emailLengths) { try { const localPart = 'x'.repeat(Math.max(1, length - 20)); const email = localPart + '@example.com'; const finalEmail = email.substring(0, length); const einvoice = new EInvoice(); einvoice.issueDate = new Date(2024, 0, 1); einvoice.invoiceId = 'EMAIL-TEST'; einvoice.electronicAddress = { scheme: 'EMAIL', value: finalEmail }; 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' } }; 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 }]; const xmlString = await einvoice.toXmlString('ubl'); const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const preserved = newInvoice.electronicAddress?.value === finalEmail; const status = length <= 254 ? 'within RFC' : 'over RFC'; console.log(` Email ${length} chars: ${preserved ? 'preserved' : 'modified'} (${status})`); } catch (error) { console.log(` Email ${length} chars: Failed - ${error.message}`); } } }; // Test 6: Decimal precision limits const testDecimalPrecisionLimits = async () => { console.log('\nTest 6 - Decimal precision limits:'); const precisionTests = [ { decimals: 2, value: 123456789.12, description: 'Standard 2 decimals' }, { decimals: 4, value: 123456.1234, description: 'High precision 4 decimals' }, { decimals: 6, value: 123.123456, description: 'Very high precision 6 decimals' } ]; for (const test of precisionTests) { try { 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' } }; 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: 'High Precision Item', articleNumber: 'DECIMAL-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: test.value, vatPercentage: 19 }]; const xmlString = await einvoice.toXmlString('cii'); const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const parsedValue = newInvoice.items[0].unitNetPrice; const preserved = Math.abs(parsedValue - test.value) < 0.000001; console.log(` ${test.description}: original=${test.value}, parsed=${parsedValue}, preserved=${preserved ? 'Yes' : 'No'}`); } catch (error) { console.log(` ${test.description}: Failed - ${error.message}`); } } }; // Run all tests await testStandardFieldLimits(); await testUnicodeLengthVsBytes(); await testLongInvoiceNumbers(); await testLineItemCountLimits(); await testLongEmailAddresses(); await testDecimalPrecisionLimits(); console.log('\n=== Maximum Field Lengths Test Summary ==='); console.log('Standard field limits: Tested'); console.log('Unicode handling: Tested'); console.log('Long invoice numbers: Tested'); console.log('Line item limits: Tested'); console.log('Email address limits: Tested'); console.log('Decimal precision: Tested'); }); tap.start();