import { tap, expect } from '@push.rocks/tapbundle'; import * as getInvoices from './assets/getasset.js'; import { FacturXEncoder } from '../ts/formats/facturx.encoder.js'; import { FacturXDecoder } from '../ts/formats/facturx.decoder.js'; import { XInvoice } from '../ts/classes.xinvoice.js'; import * as tsclass from '@tsclass/tsclass'; // Test for circular conversion functionality // This test ensures that when we encode an invoice to XML and then decode it back, // we get the same essential data // Sample test letter data from our test assets const testLetterData = getInvoices.letterObjects.letter1.demoLetter; // Helper function to compare two letter objects for essential equality // We don't expect exact object equality due to format limitations and defaults function compareLetterEssentials(original: tsclass.business.ILetter, decoded: tsclass.business.ILetter): boolean { // Check basic invoice information if (original.content?.invoiceData?.id !== decoded.content?.invoiceData?.id) { console.log('Invoice ID mismatch'); return false; } // Check seller information if (original.content?.invoiceData?.billedBy?.name !== decoded.content?.invoiceData?.billedBy?.name) { console.log('Seller name mismatch'); return false; } // Check buyer information if (original.content?.invoiceData?.billedTo?.name !== decoded.content?.invoiceData?.billedTo?.name) { console.log('Buyer name mismatch'); return false; } // Check address details - a common point of data loss in XML conversion const originalSellerAddress = original.content?.invoiceData?.billedBy?.address; const decodedSellerAddress = decoded.content?.invoiceData?.billedBy?.address; if (originalSellerAddress?.city !== decodedSellerAddress?.city) { console.log('Seller city mismatch'); return false; } if (originalSellerAddress?.postalCode !== decodedSellerAddress?.postalCode) { console.log('Seller postal code mismatch'); return false; } // Basic verification passed return true; } // Basic circular test - encode and decode the same data tap.test('Basic circular encode/decode test', async () => { // Create an encoder and generate XML const encoder = new FacturXEncoder(); const xml = encoder.createFacturXXml(testLetterData); // Verify XML was created properly expect(xml).toBeTypeOf('string'); expect(xml.length).toBeGreaterThan(100); expect(xml).toInclude('CrossIndustryInvoice'); expect(xml).toInclude(testLetterData.content.invoiceData.id); // Now create a decoder to parse the XML back const decoder = new FacturXDecoder(xml); const decodedLetter = await decoder.getLetterData(); // Verify we got a letter back expect(decodedLetter).toBeTypeOf('object'); expect(decodedLetter.content?.invoiceData).toBeDefined(); // For now we only check basic structure since our decoder has a basic implementation expect(decodedLetter.content?.invoiceData?.id).toBeDefined(); expect(decodedLetter.content?.invoiceData?.billedBy).toBeDefined(); expect(decodedLetter.content?.invoiceData?.billedTo).toBeDefined(); }); // Test with modified letter data to ensure variations are handled properly tap.test('Circular encode/decode with different invoice types', async () => { // Create a modified version of the test letter - change type to credit note const creditNoteLetter = {...testLetterData}; creditNoteLetter.content = {...testLetterData.content}; creditNoteLetter.content.invoiceData = {...testLetterData.content.invoiceData}; creditNoteLetter.content.invoiceData.type = 'creditnote'; creditNoteLetter.content.invoiceData.id = 'CN-' + testLetterData.content.invoiceData.id; // Create an encoder and generate XML const encoder = new FacturXEncoder(); const xml = encoder.createFacturXXml(creditNoteLetter); // Verify XML was created properly for a credit note expect(xml).toBeTypeOf('string'); expect(xml).toInclude('CrossIndustryInvoice'); expect(xml).toInclude('TypeCode'); expect(xml).toInclude('381'); // Credit note type code expect(xml).toInclude(creditNoteLetter.content.invoiceData.id); // Now create a decoder to parse the XML back const decoder = new FacturXDecoder(xml); const decodedLetter = await decoder.getLetterData(); // Verify we got data back expect(decodedLetter).toBeTypeOf('object'); expect(decodedLetter.content?.invoiceData).toBeDefined(); // Our decoder only needs to detect the general structure at this point // Future enhancements would include full identification of CN prefixes expect(decodedLetter.content?.invoiceData?.id).toBeDefined(); expect(decodedLetter.content?.invoiceData?.id.length).toBeGreaterThan(0); }); // Test with full XInvoice class for complete cycle tap.test('Full XInvoice circular processing test', async () => { // First, generate XML from our letter data const encoder = new FacturXEncoder(); const xml = encoder.createFacturXXml(testLetterData); // Create XInvoice from XML const xInvoice = await XInvoice.fromXml(xml); // Extract structured data from the loaded invoice const content = xInvoice.content; // Verify we got invoice data back expect(content).toBeDefined(); expect(content.invoiceData).toBeDefined(); expect(content.invoiceData.id).toBeDefined(); expect(content.invoiceData.billedBy).toBeDefined(); expect(content.invoiceData.billedTo).toBeDefined(); // Verify that the data matches our input expect(content.invoiceData.id).toBeDefined(); expect(content.invoiceData.id.length).toBeGreaterThan(0); expect(content.invoiceData.billedBy.name).toBeDefined(); expect(content.invoiceData.billedTo.name).toBeDefined(); }); // Test with different invoice contents tap.test('Circular test with varying item counts', async () => { // Create a modified version of the test letter - fewer items const simpleLetter = {...testLetterData}; simpleLetter.content = {...testLetterData.content}; simpleLetter.content.invoiceData = {...testLetterData.content.invoiceData}; // Just take first 3 items simpleLetter.content.invoiceData.items = testLetterData.content.invoiceData.items.slice(0, 3); // Create an encoder and generate XML const encoder = new FacturXEncoder(); const xml = encoder.createFacturXXml(simpleLetter); // Verify XML line count is appropriate (fewer items should mean smaller XML) const lineCount = xml.split('\n').length; expect(lineCount).toBeGreaterThan(20); // Minimum lines for header etc. // Now create a decoder to parse the XML back const decoder = new FacturXDecoder(xml); const decodedLetter = await decoder.getLetterData(); // Verify the item count isn't multiplied in the round trip // This checks that we aren't duplicating data through the encoding/decoding cycle if (decodedLetter.content?.invoiceData?.items) { // This is a relaxed test since we don't expect exact object recovery // But let's ensure we don't have exploding item counts expect(decodedLetter.content.invoiceData.items.length).toBeLessThanOrEqual( testLetterData.content.invoiceData.items.length ); } }); // Test with invoice containing special characters tap.test('Circular test with special characters', async () => { // Create a modified version with special characters const specialCharsLetter = {...testLetterData}; specialCharsLetter.content = {...testLetterData.content}; specialCharsLetter.content.invoiceData = {...testLetterData.content.invoiceData}; specialCharsLetter.content.invoiceData.items = [...testLetterData.content.invoiceData.items]; // Add items with special characters specialCharsLetter.content.invoiceData.items.push({ name: 'Special item with < & > characters', unitQuantity: 1, unitNetPrice: 100, unitType: 'hours', vatPercentage: 19, position: 100, }); // Create an encoder and generate XML const encoder = new FacturXEncoder(); const xml = encoder.createFacturXXml(specialCharsLetter); // Verify XML doesn't have raw special characters (they should be escaped) expect(xml).not.toInclude('<&>'); // Now create a decoder to parse the XML back const decoder = new FacturXDecoder(xml); const decodedLetter = await decoder.getLetterData(); // Verify the basic structure was recovered expect(decodedLetter).toBeTypeOf('object'); expect(decodedLetter.content).toBeDefined(); expect(decodedLetter.content?.invoiceData).toBeDefined(); }); // Start the test suite tap.start();