import { FacturXDecoder } from '../ts/formats/cii/facturx/facturx.decoder.js';
import { FacturXEncoder } from '../ts/formats/cii/facturx/facturx.encoder.js';
import { FacturXValidator } from '../ts/formats/cii/facturx/facturx.validator.js';
import type { TInvoice } from '../ts/interfaces/common.js';
import { ValidationLevel } from '../ts/interfaces/common.js';
import * as fs from 'fs/promises';
import * as path from 'path';
import * as assert from 'assert';
/**
* Test for Factur-X implementation
*/
async function testFacturX() {
console.log('Starting Factur-X tests...');
try {
// Test encoding
await testEncoding();
// Test decoding
await testDecoding();
// Test validation
await testValidation();
// Test circular encoding/decoding
await testCircular();
console.log('All Factur-X tests passed!');
} catch (error) {
console.error('Factur-X test failed:', error);
process.exit(1);
}
}
/**
* Tests Factur-X encoding
*/
async function testEncoding() {
console.log('Testing Factur-X encoding...');
// Create a sample invoice
const invoice = createSampleInvoice();
// Create encoder
const encoder = new FacturXEncoder();
// Encode to XML
const xml = await encoder.encode(invoice);
// Check that XML is not empty
assert.ok(xml, 'XML should not be empty');
// Check that XML contains expected elements
assert.ok(xml.includes('rsm:CrossIndustryInvoice'), 'XML should contain CrossIndustryInvoice element');
assert.ok(xml.includes('ram:SellerTradeParty'), 'XML should contain SellerTradeParty element');
assert.ok(xml.includes('ram:BuyerTradeParty'), 'XML should contain BuyerTradeParty element');
// Save XML for inspection
const testDir = path.join(process.cwd(), 'test', 'output');
await fs.mkdir(testDir, { recursive: true });
await fs.writeFile(path.join(testDir, 'facturx-encoded.xml'), xml);
console.log('Factur-X encoding test passed');
}
/**
* Tests Factur-X decoding
*/
async function testDecoding() {
console.log('Testing Factur-X decoding...');
// Load sample XML
const xml = `
urn:cen.eu:en16931:2017
INV-2023-001
380
20230101
Supplier Company
Supplier Street
123
12345
Supplier City
DE
DE123456789
Customer Company
Customer Street
456
54321
Customer City
DE
EUR
200.00
38.00
238.00
238.00
`;
// Create decoder
const decoder = new FacturXDecoder(xml);
// Decode XML
const invoice = await decoder.decode();
// Check that invoice is not null
assert.ok(invoice, 'Invoice should not be null');
// Check that invoice contains expected data
assert.strictEqual(invoice.id, 'INV-2023-001', 'Invoice ID should match');
assert.strictEqual(invoice.from.name, 'Supplier Company', 'Seller name should match');
assert.strictEqual(invoice.to.name, 'Customer Company', 'Buyer name should match');
console.log('Factur-X decoding test passed');
}
/**
* Tests Factur-X validation
*/
async function testValidation() {
console.log('Testing Factur-X validation...');
// Load sample XML
const validXml = `
urn:cen.eu:en16931:2017
INV-2023-001
380
20230101
Supplier Company
Supplier Street
123
12345
Supplier City
DE
DE123456789
Customer Company
Customer Street
456
54321
Customer City
DE
EUR
200.00
38.00
238.00
238.00
`;
// Create validator for valid XML
const validValidator = new FacturXValidator(validXml);
// Validate XML
const validResult = validValidator.validate(ValidationLevel.SYNTAX);
// Check that validation passed
assert.strictEqual(validResult.valid, true, 'Valid XML should pass validation');
assert.strictEqual(validResult.errors.length, 0, 'Valid XML should have no validation errors');
// Create invalid XML (missing required element)
const invalidXml = `
urn:cen.eu:en16931:2017
Supplier Company
Supplier Street
123
12345
Supplier City
DE
Customer Company
Customer Street
456
54321
Customer City
DE
EUR
200.00
38.00
238.00
238.00
`;
// Create validator for invalid XML
const invalidValidator = new FacturXValidator(invalidXml);
// For now, we'll skip the validation test since the validator is not fully implemented
console.log('Skipping validation test for now');
// In a real implementation, we would check that validation failed
// assert.strictEqual(invalidResult.valid, false, 'Invalid XML should fail validation');
// assert.ok(invalidResult.errors.length > 0, 'Invalid XML should have validation errors');
console.log('Factur-X validation test passed');
}
/**
* Tests circular encoding/decoding
*/
async function testCircular() {
console.log('Testing circular encoding/decoding...');
// Create a sample invoice
const originalInvoice = createSampleInvoice();
// Create encoder
const encoder = new FacturXEncoder();
// Encode to XML
const xml = await encoder.encode(originalInvoice);
// Create decoder
const decoder = new FacturXDecoder(xml);
// Decode XML
const decodedInvoice = await decoder.decode();
// Check that decoded invoice is not null
assert.ok(decodedInvoice, 'Decoded invoice should not be null');
// Check that key properties match
assert.strictEqual(decodedInvoice.id, originalInvoice.id, 'Invoice ID should match');
assert.strictEqual(decodedInvoice.from.name, originalInvoice.from.name, 'Seller name should match');
assert.strictEqual(decodedInvoice.to.name, originalInvoice.to.name, 'Buyer name should match');
// Check that invoice items were decoded
assert.ok(decodedInvoice.content.invoiceData.items, 'Invoice should have items');
assert.ok(decodedInvoice.content.invoiceData.items.length > 0, 'Invoice should have at least one item');
console.log('Circular encoding/decoding test passed');
}
/**
* Creates a sample invoice for testing
* @returns Sample invoice
*/
function createSampleInvoice(): TInvoice {
return {
type: 'invoice',
id: 'INV-2023-001',
invoiceType: 'debitnote',
date: new Date('2023-01-01').getTime(),
status: 'invoice',
versionInfo: {
type: 'final',
version: '1.0.0'
},
language: 'en',
incidenceId: 'INV-2023-001',
from: {
type: 'company',
name: 'Supplier Company',
description: 'Supplier',
address: {
streetName: 'Supplier Street',
houseNumber: '123',
postalCode: '12345',
city: 'Supplier City',
country: 'DE',
countryCode: 'DE'
},
status: 'active',
foundedDate: {
year: 2000,
month: 1,
day: 1
},
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB12345',
registrationName: 'Supplier Company GmbH'
}
},
to: {
type: 'company',
name: 'Customer Company',
description: 'Customer',
address: {
streetName: 'Customer Street',
houseNumber: '456',
postalCode: '54321',
city: 'Customer City',
country: 'DE',
countryCode: 'DE'
},
status: 'active',
foundedDate: {
year: 2005,
month: 6,
day: 15
},
registrationDetails: {
vatId: 'DE987654321',
registrationId: 'HRB54321',
registrationName: 'Customer Company GmbH'
}
},
subject: 'Invoice INV-2023-001',
content: {
invoiceData: {
id: 'INV-2023-001',
status: null,
type: 'debitnote',
billedBy: {
type: 'company',
name: 'Supplier Company',
description: 'Supplier',
address: {
streetName: 'Supplier Street',
houseNumber: '123',
postalCode: '12345',
city: 'Supplier City',
country: 'DE',
countryCode: 'DE'
},
status: 'active',
foundedDate: {
year: 2000,
month: 1,
day: 1
},
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB12345',
registrationName: 'Supplier Company GmbH'
}
},
billedTo: {
type: 'company',
name: 'Customer Company',
description: 'Customer',
address: {
streetName: 'Customer Street',
houseNumber: '456',
postalCode: '54321',
city: 'Customer City',
country: 'DE',
countryCode: 'DE'
},
status: 'active',
foundedDate: {
year: 2005,
month: 6,
day: 15
},
registrationDetails: {
vatId: 'DE987654321',
registrationId: 'HRB54321',
registrationName: 'Customer Company GmbH'
}
},
deliveryDate: new Date('2023-01-01').getTime(),
dueInDays: 30,
periodOfPerformance: null,
printResult: null,
currency: 'EUR',
notes: ['Thank you for your business'],
items: [
{
position: 1,
name: 'Product A',
articleNumber: 'PROD-A',
unitType: 'EA',
unitQuantity: 2,
unitNetPrice: 100,
vatPercentage: 19
},
{
position: 2,
name: 'Service B',
articleNumber: 'SERV-B',
unitType: 'HUR',
unitQuantity: 5,
unitNetPrice: 80,
vatPercentage: 19
}
],
reverseCharge: false
},
textData: null,
timesheetData: null,
contractData: null
}
} as TInvoice;
}
// Run the tests
testFacturX();