import { tap } from '@git.zone/tstest/tapbundle';
import * as plugins from '../plugins.js';
import { EInvoice } from '../../../ts/index.js';
import { PerformanceTracker } from '../performance.tracker.js';
const performanceTracker = new PerformanceTracker('EDGE-01: Empty Invoice Files');
tap.test('EDGE-01: Empty Invoice Files - should handle empty and near-empty files gracefully', async (t) => {
const einvoice = new EInvoice();
// Test 1: Completely empty file
const completelyEmpty = await performanceTracker.measureAsync(
'completely-empty-file',
async () => {
const emptyContent = '';
try {
const result = await einvoice.parseDocument(emptyContent);
return {
handled: true,
parsed: !!result,
error: null,
contentLength: emptyContent.length
};
} catch (error) {
return {
handled: true,
parsed: false,
error: error.message,
errorType: error.constructor.name
};
}
}
);
t.ok(completelyEmpty.handled, 'Completely empty file was handled');
t.notOk(completelyEmpty.parsed, 'Empty file was not parsed as valid');
// Test 2: Only whitespace
const onlyWhitespace = await performanceTracker.measureAsync(
'only-whitespace',
async () => {
const whitespaceVariants = [
' ',
'\n',
'\r\n',
'\t',
' \n\n\t\t \r\n ',
' '.repeat(1000)
];
const results = [];
for (const content of whitespaceVariants) {
try {
const result = await einvoice.parseDocument(content);
results.push({
content: content.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t'),
length: content.length,
parsed: !!result,
error: null
});
} catch (error) {
results.push({
content: content.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t'),
length: content.length,
parsed: false,
error: error.message
});
}
}
return results;
}
);
onlyWhitespace.forEach(result => {
t.notOk(result.parsed, `Whitespace-only content not parsed: "${result.content}"`);
});
// Test 3: Empty XML structure
const emptyXMLStructure = await performanceTracker.measureAsync(
'empty-xml-structure',
async () => {
const emptyStructures = [
'',
'\n',
'',
'',
'',
''
];
const results = [];
for (const xml of emptyStructures) {
try {
const result = await einvoice.parseDocument(xml);
const validation = await einvoice.validate(result);
results.push({
xml: xml.substring(0, 50),
parsed: true,
valid: validation?.isValid || false,
hasContent: !!result && Object.keys(result).length > 0
});
} catch (error) {
results.push({
xml: xml.substring(0, 50),
parsed: false,
error: error.message
});
}
}
return results;
}
);
emptyXMLStructure.forEach(result => {
if (result.parsed) {
t.notOk(result.valid, 'Empty XML structure is not valid invoice');
}
});
// Test 4: Empty required fields
const emptyRequiredFields = await performanceTracker.measureAsync(
'empty-required-fields',
async () => {
const testCases = [
{
name: 'empty-id',
xml: `
2024-01-01
`
},
{
name: 'whitespace-id',
xml: `
2024-01-01
`
},
{
name: 'empty-amount',
xml: `
INV-001
`
}
];
const results = [];
for (const testCase of testCases) {
try {
const parsed = await einvoice.parseDocument(testCase.xml);
const validation = await einvoice.validate(parsed);
results.push({
name: testCase.name,
parsed: true,
valid: validation?.isValid || false,
errors: validation?.errors || []
});
} catch (error) {
results.push({
name: testCase.name,
parsed: false,
error: error.message
});
}
}
return results;
}
);
emptyRequiredFields.forEach(result => {
t.notOk(result.valid, `${result.name} is not valid`);
});
// Test 5: Zero-byte file
const zeroByteFile = await performanceTracker.measureAsync(
'zero-byte-file',
async () => {
const zeroByteBuffer = Buffer.alloc(0);
try {
const result = await einvoice.parseDocument(zeroByteBuffer);
return {
handled: true,
parsed: !!result,
bufferLength: zeroByteBuffer.length
};
} catch (error) {
return {
handled: true,
parsed: false,
error: error.message,
bufferLength: zeroByteBuffer.length
};
}
}
);
t.ok(zeroByteFile.handled, 'Zero-byte buffer was handled');
t.equal(zeroByteFile.bufferLength, 0, 'Buffer length is zero');
// Test 6: Empty arrays and objects
const emptyCollections = await performanceTracker.measureAsync(
'empty-collections',
async () => {
const testCases = [
{
name: 'empty-line-items',
xml: `
INV-001
`
},
{
name: 'empty-tax-totals',
xml: `
INV-001
`
}
];
const results = [];
for (const testCase of testCases) {
try {
const parsed = await einvoice.parseDocument(testCase.xml);
results.push({
name: testCase.name,
parsed: true,
hasEmptyCollections: true,
structure: JSON.stringify(parsed).substring(0, 100)
});
} catch (error) {
results.push({
name: testCase.name,
parsed: false,
error: error.message
});
}
}
return results;
}
);
emptyCollections.forEach(result => {
t.ok(result.parsed || result.error, `${result.name} was processed`);
});
// Test 7: Empty PDF files
const emptyPDFFiles = await performanceTracker.measureAsync(
'empty-pdf-files',
async () => {
const pdfTests = [
{
name: 'empty-pdf-header',
content: Buffer.from('%PDF-1.4\n%%EOF')
},
{
name: 'pdf-no-content',
content: Buffer.from('%PDF-1.4\n1 0 obj\n<<>>\nendobj\nxref\n0 1\n0000000000 65535 f\ntrailer\n<>\n%%EOF')
},
{
name: 'zero-byte-pdf',
content: Buffer.alloc(0)
}
];
const results = [];
for (const test of pdfTests) {
try {
const result = await einvoice.extractFromPDF(test.content);
results.push({
name: test.name,
processed: true,
hasXML: !!result?.xml,
hasAttachments: result?.attachments?.length > 0,
size: test.content.length
});
} catch (error) {
results.push({
name: test.name,
processed: false,
error: error.message,
size: test.content.length
});
}
}
return results;
}
);
emptyPDFFiles.forEach(result => {
t.ok(!result.hasXML, `${result.name} has no XML content`);
});
// Test 8: Format detection on empty files
const formatDetectionEmpty = await performanceTracker.measureAsync(
'format-detection-empty',
async () => {
const emptyVariants = [
{ content: '', name: 'empty-string' },
{ content: ' ', name: 'space' },
{ content: '\n', name: 'newline' },
{ content: '', name: 'incomplete-xml-declaration' },
{ content: '<', name: 'single-bracket' },
{ content: Buffer.alloc(0), name: 'empty-buffer' }
];
const results = [];
for (const variant of emptyVariants) {
try {
const format = await einvoice.detectFormat(variant.content);
results.push({
name: variant.name,
detected: !!format,
format: format,
confidence: format?.confidence || 0
});
} catch (error) {
results.push({
name: variant.name,
detected: false,
error: error.message
});
}
}
return results;
}
);
formatDetectionEmpty.forEach(result => {
t.notOk(result.detected, `Format not detected for ${result.name}`);
});
// Test 9: Empty namespace handling
const emptyNamespaces = await performanceTracker.measureAsync(
'empty-namespace-handling',
async () => {
const namespaceTests = [
{
name: 'empty-default-namespace',
xml: ''
},
{
name: 'empty-prefix-namespace',
xml: ''
},
{
name: 'whitespace-namespace',
xml: ''
}
];
const results = [];
for (const test of namespaceTests) {
try {
const parsed = await einvoice.parseDocument(test.xml);
results.push({
name: test.name,
parsed: true,
hasNamespace: !!parsed?.namespace
});
} catch (error) {
results.push({
name: test.name,
parsed: false,
error: error.message
});
}
}
return results;
}
);
emptyNamespaces.forEach(result => {
t.ok(result.parsed !== undefined, `${result.name} was processed`);
});
// Test 10: Recovery from empty files
const emptyFileRecovery = await performanceTracker.measureAsync(
'empty-file-recovery',
async () => {
const recoveryTest = async () => {
const results = {
emptyHandled: false,
normalAfterEmpty: false,
batchWithEmpty: false
};
// Test 1: Handle empty file
try {
await einvoice.parseDocument('');
} catch (error) {
results.emptyHandled = true;
}
// Test 2: Parse normal file after empty
try {
const normal = await einvoice.parseDocument(
'TEST'
);
results.normalAfterEmpty = !!normal;
} catch (error) {
// Should not happen
}
// Test 3: Batch with empty file
try {
const batch = await einvoice.batchProcess([
'1',
'',
'2'
]);
results.batchWithEmpty = batch?.processed === 2;
} catch (error) {
// Batch might fail completely
}
return results;
};
return await recoveryTest();
}
);
t.ok(emptyFileRecovery.normalAfterEmpty, 'Can parse normal file after empty file');
// Print performance summary
performanceTracker.printSummary();
});
// Run the test
tap.start();