einvoice/test/suite/einvoice_edge-cases/test.edge-07.max-field-lengths.ts

752 lines
24 KiB
TypeScript
Raw Normal View History

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();