UNPKG

@node-dlc/messaging

Version:
375 lines (316 loc) 13.4 kB
/* eslint-disable no-console */ import { expect } from 'chai'; import { execSync } from 'child_process'; import * as fs from 'fs'; import JSONbig from 'json-bigint'; import * as path from 'path'; import { DlcAccept, DlcOffer, DlcSign } from '../../lib'; // Configure json-bigint to handle large integers as BigInt but preserve floating point numbers const JSONBigInt = JSONbig({ storeAsString: false, // Store as BigInt, not string useNativeBigInt: true, // Use native BigInt support alwaysParseAsBig: false, // Don't convert all numbers to BigInt - preserve smaller numbers and floats // This will automatically use BigInt for integers that exceed MAX_SAFE_INTEGER // and keep regular numbers for smaller integers and floats }); interface RustDlcCliResult { status: 'success' | 'error'; // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any; messageType?: string; message: string; } // Helper function to call rust-dlc CLI function callRustCli(command: string, input?: string): RustDlcCliResult { const rustCliPath = path.join(__dirname, '../../../../rust-dlc-cli'); const cliCmd = `cd ${rustCliPath} && cargo run --bin dlc-compat -- ${command}`; try { // Use shorter timeout in CI environments to avoid test timeouts const isCI = process.env.CI === 'true'; const cliTimeout = isCI ? 60000 : 300000; // 1 minute in CI, 5 minutes locally const result = execSync(cliCmd, { input: input || '', encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: cliTimeout, }); return JSON.parse(result.trim()); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { // If the command failed, try to parse stderr or stdout as JSON try { const errorOutput = error.stdout || error.stderr || ''; if (errorOutput.trim().startsWith('{')) { return JSON.parse(errorOutput.trim()); } // If not JSON, create error response return { status: 'error', message: `CLI execution failed: ${error.message}. Output: ${errorOutput}`, }; } catch { return { status: 'error', message: `CLI execution failed: ${error.message}`, }; } } } // Load test vectors at module level so they're available for test generation const testVectorsDir = path.join(__dirname, '../../test_vectors/dlcspecs'); const testVectorFiles: string[] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any const allTestData: { [filename: string]: any } = {}; // Load test data synchronously at module level if (fs.existsSync(testVectorsDir)) { const files = fs .readdirSync(testVectorsDir) .filter((f) => f.endsWith('.json')) .sort(); // Sort for consistent test order files.forEach((filename) => { // if (filename !== 'enum_3_of_3_test.json') return; const filePath = path.join(testVectorsDir, filename); try { const fileContent = fs.readFileSync(filePath, 'utf8'); allTestData[filename] = JSONBigInt.parse(fileContent); testVectorFiles.push(filename); } catch (error) { console.warn( `Failed to load test vector ${filename}:`, (error as Error).message, ); } }); } describe('True DLC Serialization Compatibility Tests', () => { describe('Comprehensive Test Vector Coverage', () => { it('should discover and load all available test vector files', () => { expect(testVectorFiles.length).to.be.greaterThan( 0, 'Should find test vector files', ); expect(Object.keys(allTestData).length).to.be.greaterThan( 0, 'Should load test data', ); console.log(`\nTest Vector Discovery:`); console.log(` Found ${testVectorFiles.length} test vector files:`); testVectorFiles.forEach((file, index) => { const hasOfferMessage = allTestData[file]?.offer_message ? '[OFFER]' : '[NO_OFFER]'; const hasAcceptMessage = allTestData[file]?.accept_message ? '[ACCEPT]' : '[NO_ACCEPT]'; const hasSignMessage = allTestData[file]?.sign_message ? '[SIGN]' : '[NO_SIGN]'; console.log( ` ${ index + 1 }. ${file} (${hasOfferMessage} ${hasAcceptMessage} ${hasSignMessage})`, ); }); }); }); describe('Round-trip Serialization Compatibility', () => { // Test representative samples against current Rust-DLC CLI for compatibility const rustCompatTestVectors = [ 'enum_3_of_3_test.json', 'single_oracle_numerical_test.json', ]; rustCompatTestVectors.forEach((filename) => { const testData = allTestData[filename]; if (!testData) return; if (testData.offer_message) { it(`should have Node.js DlcOffer compatible with Rust-DLC CLI for ${filename}`, function () { // Use longer timeout in CI to account for slower build times const isCI = process.env.CI === 'true'; this.timeout(isCI ? 90000 : 15000); // 90s in CI, 15s locally const nodeOffer = DlcOffer.fromJSON(testData.offer_message.message); const nodeJson = nodeOffer.toJSON(); const nodeHex = nodeOffer.serialize().toString('hex'); const rustSerialization = callRustCli( 'serialize -t offer', JSONBigInt.stringify(nodeJson), ); expect(rustSerialization.status).to.equal( 'success', `Rust CLI serialization failed: ${rustSerialization.message}`, ); const rustHex = rustSerialization.data; expect(nodeHex).to.equal( rustHex, `Node.js vs Rust-DLC CLI serialization mismatch for ${filename}`, ); }); } if (testData.accept_message) { it(`should have Node.js DlcAccept compatible with Rust-DLC CLI for ${filename}`, function () { // Use longer timeout in CI to account for slower build times const isCI = process.env.CI === 'true'; this.timeout(isCI ? 90000 : 15000); // 90s in CI, 15s locally const nodeAccept = DlcAccept.fromJSON( testData.accept_message.message, ); const nodeJson = nodeAccept.toJSON(); const nodeHex = nodeAccept.serialize().toString('hex'); const rustSerialization = callRustCli( 'serialize -t accept', JSONBigInt.stringify(nodeJson), ); expect(rustSerialization.status).to.equal( 'success', `Rust CLI serialization failed: ${rustSerialization.message}`, ); const rustHex = rustSerialization.data; expect(nodeHex).to.equal( rustHex, `Node.js vs Rust-DLC CLI serialization mismatch for ${filename}`, ); }); } if (testData.sign_message) { it(`should have Node.js DlcSign compatible with Rust-DLC CLI for ${filename}`, function () { // Use longer timeout in CI to account for slower build times const isCI = process.env.CI === 'true'; this.timeout(isCI ? 90000 : 15000); // 90s in CI, 15s locally const nodeSign = DlcSign.fromJSON(testData.sign_message.message); const nodeJson = nodeSign.toJSON(); const nodeHex = nodeSign.serialize().toString('hex'); const rustSerialization = callRustCli( 'serialize -t sign', JSONBigInt.stringify(nodeJson), ); expect(rustSerialization.status).to.equal( 'success', `Rust CLI serialization failed: ${rustSerialization.message}`, ); const rustHex = rustSerialization.data; expect(nodeHex).to.equal( rustHex, `Node.js vs Rust-DLC CLI serialization mismatch for ${filename}`, ); }); } }); // Generate individual test cases for DlcOffer serialization testVectorFiles.forEach((filename) => { const testData = allTestData[filename]; if (!testData?.offer_message) return; // Skip DlcInput test vectors as they were programmatically generated without serialized hex if (filename === 'enum_3_of_5_with_dlc_input_test.json') { it.skip( `should correctly serialize DlcOffer for ${filename} (SKIP: Programmatically generated test vector)`, ); return; } it(`should correctly serialize DlcOffer for ${filename}`, () => { const expectedHex = testData.offer_message.serialized; const messageJson = testData.offer_message.message; const offer = DlcOffer.fromJSON(messageJson); const actualHex = offer.serialize().toString('hex'); expect(actualHex).to.equal( expectedHex, `DlcOffer serialization mismatch for ${filename}`, ); }); }); // Generate individual test cases for DlcAccept serialization testVectorFiles.forEach((filename) => { const testData = allTestData[filename]; if (!testData?.accept_message) return; // Skip DlcInput test vectors as they were programmatically generated without serialized hex if (filename === 'enum_3_of_5_with_dlc_input_test.json') { it.skip( `should correctly serialize DlcAccept for ${filename} (SKIP: Programmatically generated test vector)`, ); return; } it(`should correctly serialize DlcAccept for ${filename}`, () => { const expectedHex = testData.accept_message.serialized; const messageJson = testData.accept_message.message; const accept = DlcAccept.fromJSON(messageJson); const actualHex = accept.serialize().toString('hex'); expect(actualHex).to.equal( expectedHex, `DlcAccept serialization mismatch for ${filename}`, ); }); }); // Generate individual test cases for DlcSign serialization testVectorFiles.forEach((filename) => { const testData = allTestData[filename]; if (!testData?.sign_message) return; it(`should correctly serialize DlcSign for ${filename}`, () => { const expectedHex = testData.sign_message.serialized; const messageJson = testData.sign_message.message; const sign = DlcSign.fromJSON(messageJson); const actualHex = sign.serialize().toString('hex'); expect(actualHex).to.equal( expectedHex, `DlcSign serialization mismatch for ${filename}`, ); }); }); // Generate individual test cases for DlcOffer deserialization testVectorFiles.forEach((filename) => { const testData = allTestData[filename]; if (!testData?.offer_message) return; // Skip DlcInput test vectors as they were programmatically generated without serialized hex if (filename === 'enum_3_of_5_with_dlc_input_test.json') { it.skip( `should correctly deserialize DlcOffer for ${filename} (SKIP: Programmatically generated test vector)`, ); return; } it(`should correctly deserialize DlcOffer for ${filename}`, () => { const serializedHex = testData.offer_message.serialized; const inputBuffer = Buffer.from(serializedHex, 'hex'); const offer = DlcOffer.deserialize(inputBuffer); const reserializedHex = offer.serialize().toString('hex'); expect(reserializedHex).to.equal( serializedHex, `DlcOffer deserialization round-trip failed for ${filename}`, ); }); }); // Generate individual test cases for DlcAccept deserialization testVectorFiles.forEach((filename) => { const testData = allTestData[filename]; if (!testData?.accept_message) return; // Skip DlcInput test vectors as they were programmatically generated without serialized hex if (filename === 'enum_3_of_5_with_dlc_input_test.json') { it.skip( `should correctly deserialize DlcAccept for ${filename} (SKIP: Programmatically generated test vector)`, ); return; } it(`should correctly deserialize DlcAccept for ${filename}`, () => { const serializedHex = testData.accept_message.serialized; const inputBuffer = Buffer.from(serializedHex, 'hex'); const accept = DlcAccept.deserialize(inputBuffer); const reserializedHex = accept.serialize().toString('hex'); expect(reserializedHex).to.equal( serializedHex, `DlcAccept deserialization round-trip failed for ${filename}`, ); }); }); // Generate individual test cases for DlcSign deserialization testVectorFiles.forEach((filename) => { const testData = allTestData[filename]; if (!testData?.sign_message) return; it(`should correctly deserialize DlcSign for ${filename}`, () => { const serializedHex = testData.sign_message.serialized; const inputBuffer = Buffer.from(serializedHex, 'hex'); const sign = DlcSign.deserialize(inputBuffer); const reserializedHex = sign.serialize().toString('hex'); expect(reserializedHex).to.equal( serializedHex, `DlcSign deserialization round-trip failed for ${filename}`, ); }); }); }); });