/** * JavaScript Mix Payloads Sender Test * Tests the smartsend function with mixed payload types * * This test mirrors test_julia_mix_payloads_sender.jl and demonstrates that * any combination and any number of mixed content can be sent correctly. */ const NATSBridge = require('../src/natsbridge.js'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const TEST_SUBJECT = '/natsbridge'; const TEST_BROKER_URL = process.env.NATS_URL || 'nats.yiem.cc'; const TEST_FILESERVER_URL = process.env.FILESERVER_URL || 'http://192.168.88.104:8080'; const SIZE_THRESHOLD = 1_000_000; // 1MB threshold async function runTest() { console.log('=== JavaScript Mix Payloads Sender Test ===\n'); const correlationId = crypto.randomUUID(); console.log(`Correlation ID: ${correlationId}`); console.log(`Subject: ${TEST_SUBJECT}`); console.log(`Broker URL: ${TEST_BROKER_URL}`); console.log(`Fileserver URL: ${TEST_FILESERVER_URL}`); console.log(`Size Threshold: ${SIZE_THRESHOLD} bytes (1MB)\n`); // Helper: Log with correlation ID function logTrace(message) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] [Correlation: ${correlationId}] ${message}`); } // Create sample data for each type (mirroring Julia test) const textData = 'Hello! This is a test chat message. šŸŽ‰\nHow are you doing today? 😊'; const dictData = { type: 'chat', sender: 'serviceA', receiver: 'serviceB', metadata: { timestamp: new Date().toISOString(), priority: 'high', tags: ['urgent', 'chat', 'test'] }, content: { text: 'This is a JSON-formatted chat message with nested structure.', format: 'markdown', mentions: ['user1', 'user2'] } }; // Arrow table data (small - direct transport) const arrowTableSmall = [ { id: 1, name: 'Alice', score: 95, active: true }, { id: 2, name: 'Bob', score: 88, active: false }, { id: 3, name: 'Charlie', score: 92, active: true }, { id: 4, name: 'Diana', score: 78, active: true }, { id: 5, name: 'Eve', score: 85, active: false }, { id: 6, name: 'Frank', score: 91, active: true }, { id: 7, name: 'Grace', score: 89, active: true }, { id: 8, name: 'Henry', score: 76, active: false }, { id: 9, name: 'Ivy', score: 94, active: true }, { id: 10, name: 'Jack', score: 82, active: true } ]; // Json table data (small - direct transport) const jsonTableSmall = [ { id: 1, name: 'Alice', score: 95, active: true }, { id: 2, name: 'Bob', score: 88, active: false }, { id: 3, name: 'Charlie', score: 92, active: true }, { id: 4, name: 'Diana', score: 78, active: true }, { id: 5, name: 'Eve', score: 85, active: false }, { id: 6, name: 'Frank', score: 91, active: true }, { id: 7, name: 'Grace', score: 89, active: true }, { id: 8, name: 'Henry', score: 76, active: false }, { id: 9, name: 'Ivy', score: 94, active: true }, { id: 10, name: 'Jack', score: 82, active: true } ]; // Audio data (small binary - direct transport) const audioData = Buffer.alloc(100); for (let i = 0; i < 100; i++) { audioData[i] = Math.floor(Math.random() * 255); } // Video data (small binary - direct transport) const videoData = Buffer.alloc(150); for (let i = 0; i < 150; i++) { videoData[i] = Math.floor(Math.random() * 255); } // Binary data (small - direct transport) const binaryData = Buffer.alloc(200); for (let i = 0; i < 200; i++) { binaryData[i] = Math.floor(Math.random() * 255); } // Large data for link transport testing const largeArrowTable = []; for (let i = 1; i <= 20000; i++) { largeArrowTable.push({ id: i, name: `user_${i}`, score: Math.floor(Math.random() * 51) + 50, active: Math.random() > 0.5, timestamp: new Date().toISOString() }); } const largeJsonTable = []; for (let i = 1; i <= 50000; i++) { largeJsonTable.push({ id: i, name: `user_${i}`, score: Math.floor(Math.random() * 51) + 50, active: Math.random() > 0.5 }); } const largeAudioData = Buffer.alloc(1_500_000); for (let i = 0; i < 1_500_000; i++) { largeAudioData[i] = Math.floor(Math.random() * 255); } const largeVideoData = Buffer.alloc(1_500_000); for (let i = 0; i < 1_500_000; i++) { largeVideoData[i] = Math.floor(Math.random() * 255); } const largeBinaryData = Buffer.alloc(1_500_000); for (let i = 0; i < 1_500_000; i++) { largeBinaryData[i] = Math.floor(Math.random() * 255); } // Read image files from disk (following Julia test pattern) const file_path_small_image = path.join(__dirname, 'small_image.jpg'); const file_data_small_image = fs.readFileSync(file_path_small_image); const filename_small_image = path.basename(file_path_small_image); const file_path_large_image = path.join(__dirname, 'large_image.png'); const file_data_large_image = fs.readFileSync(file_path_large_image); const filename_large_image = path.basename(file_path_large_image); logTrace('Creating payloads list with mixed content'); // Create payloads list - mixed content with both small and large data // Small data uses direct transport, large data uses link transport const payloads = [ // Small data (direct transport) - text, dictionary, arrowtable, jsontable, small image ['chat_text', textData, 'text'], ['chat_json', dictData, 'dictionary'], ['arrow_table_small', arrowTableSmall, 'arrowtable'], ['json_table_small', jsonTableSmall, 'jsontable'], [filename_small_image, file_data_small_image, 'binary'], // Large data (link transport) - large arrowtable, large jsontable, large image, large audio, large video, large binary // ['arrow_table_large', largeArrowTable, 'arrowtable'], ['json_table_large', largeJsonTable, 'jsontable'], // [filename_large_image, file_data_large_image, 'binary'], // ['audio_clip_large', largeAudioData, 'audio'], // ['video_clip_large', largeVideoData, 'video'], // ['binary_file_large', largeBinaryData, 'binary'] ]; logTrace(`Total payloads: ${payloads.length}`); try { // Send the message console.log('Sending mixed payloads...\n'); const [env, envJsonStr] = await NATSBridge.smartsend( TEST_SUBJECT, payloads, { broker_url: TEST_BROKER_URL, fileserver_url: TEST_FILESERVER_URL, fileserver_upload_handler: NATSBridge.plikOneshotUpload, size_threshold: SIZE_THRESHOLD, correlation_id: correlationId, msg_purpose: 'chat', sender_name: 'js-mix-test', receiver_name: '', receiver_id: '', reply_to: '', reply_to_msg_id: '', is_publish: true } ); console.log('\n=== Envelope Created ==='); console.log(`Correlation ID: ${env.correlation_id}`); console.log(`Message ID: ${env.msg_id}`); console.log(`Timestamp: ${env.timestamp}`); console.log(`Subject: ${env.send_to}`); console.log(`Purpose: ${env.msg_purpose}`); console.log(`Sender: ${env.sender_name}`); console.log(`Payloads: ${env.payloads.length}\n`); // Log transport type for each payload console.log('=== Payload Details ==='); for (let i = 0; i < env.payloads.length; i++) { const payload = env.payloads[i]; logTrace(`Payload ${i + 1} ('${payload.dataname}'):`); logTrace(` Transport: ${payload.transport}`); logTrace(` Type: ${payload.payload_type}`); logTrace(` Size: ${payload.size} bytes`); logTrace(` Encoding: ${payload.encoding}`); if (payload.transport === 'link') { logTrace(` URL: ${payload.data}`); } } // Summary console.log('\n=== Transport Summary ==='); const directCount = env.payloads.filter(p => p.transport === 'direct').length; const linkCount = env.payloads.filter(p => p.transport === 'link').length; logTrace(`Direct transport: ${directCount} payloads`); logTrace(`Link transport: ${linkCount} payloads`); // Validate envelope structure console.log('\n=== Validation ==='); let passed = true; if (env.payloads.length !== 11) { console.log(`āŒ Expected 11 payloads, got ${env.payloads.length}`); passed = false; } else { console.log('āœ… Correct number of payloads'); } // Test each payload const expectedDatanames = [ 'chat_text', 'chat_json', 'arrow_table_small', 'json_table_small', filename_small_image, 'arrow_table_large', 'json_table_large', filename_large_image, 'audio_clip_large', 'video_clip_large', 'binary_file_large' ]; const expectedTypes = [ 'text', 'dictionary', 'arrowtable', 'jsontable', 'binary', 'arrowtable', 'jsontable', 'binary', 'audio', 'video', 'binary' ]; for (let i = 0; i < env.payloads.length; i++) { const payload = env.payloads[i]; if (payload.dataname !== expectedDatanames[i]) { console.log(`āŒ Payload ${i + 1}: Expected dataname '${expectedDatanames[i]}', got '${payload.dataname}'`); passed = false; } else { console.log(`āœ… Payload ${i + 1}: Correct dataname ('${payload.dataname}')`); } if (payload.payload_type !== expectedTypes[i]) { console.log(`āŒ Payload ${i + 1}: Expected type '${expectedTypes[i]}', got '${payload.payload_type}'`); passed = false; } else { console.log(`āœ… Payload ${i + 1}: Correct type ('${payload.payload_type}')`); } // Validate transport based on expected size if (i < 5 || i >= 6 && i <= 10) { // First 5 should be direct (small), rest should be link (large) const shouldBeDirect = i < 5; const isDirect = payload.transport === 'direct'; if (shouldBeDirect && isDirect) { console.log(`āœ… Payload ${i + 1}: Correct transport (direct)`); } else if (!shouldBeDirect && payload.transport === 'link') { console.log(`āœ… Payload ${i + 1}: Correct transport (link)`); } else { console.log(`āŒ Payload ${i + 1}: Expected ${shouldBeDirect ? 'direct' : 'link'} transport, got '${payload.transport}'`); passed = false; } } // Validate encoding based on payload type let expectedEncoding; if (payload.payload_type === 'jsontable') { expectedEncoding = 'json'; } else if (payload.payload_type === 'arrowtable') { expectedEncoding = 'arrow-ipc'; } else { expectedEncoding = 'base64'; } if (payload.encoding !== expectedEncoding) { console.log(`āŒ Payload ${i + 1}: Expected encoding '${expectedEncoding}', got '${payload.encoding}'`); passed = false; } else { console.log(`āœ… Payload ${i + 1}: Correct encoding ('${payload.encoding}')`); } // Validate size field if (payload.size > 0) { console.log(`āœ… Payload ${i + 1}: Size field present (${payload.size} bytes)`); } else { console.log(`āŒ Payload ${i + 1}: Size field is 0`); passed = false; } // Validate ID field if (payload.id && payload.id.length > 0) { console.log(`āœ… Payload ${i + 1}: ID field present`); } else { console.log(`āŒ Payload ${i + 1}: ID field is empty`); passed = false; } // Validate metadata field if (payload.metadata !== undefined && payload.metadata !== null) { console.log(`āœ… Payload ${i + 1}: Metadata field present`); } else { console.log(`āŒ Payload ${i + 1}: Metadata field is missing`); passed = false; } console.log(''); } // Test with chat-like payload (text + image + audio) console.log('=== Chat-like Payload Test ==='); const chatData = [ ['text', 'Hello!', 'text'], ['image', Buffer.from([0xFF, 0xD8, 0xFF, 0xE0]), 'image'], ['audio', Buffer.from([0x46, 0x4C, 0x41, 0x43]), 'audio'] ]; const [chatEnv, _] = await NATSBridge.smartsend( TEST_SUBJECT, chatData, { broker_url: TEST_BROKER_URL, fileserver_url: TEST_FILESERVER_URL, correlation_id: 'chat-' + correlationId, msg_purpose: 'chat', sender_name: 'js-mix-test', is_publish: true } ); if (chatEnv.payloads.length === 3) { console.log('āœ… Chat-like payloads handled correctly (3 payloads)'); } else { console.log(`āŒ Chat-like payloads handling failed (expected 3, got ${chatEnv.payloads.length})`); passed = false; } // Verify all payload types in chat are direct transport const allDirect = chatEnv.payloads.every(p => p.transport === 'direct'); if (allDirect) { console.log('āœ… All chat payloads use direct transport (small size)'); } else { console.log('āŒ Some chat payloads use link transport (unexpected for small data)'); passed = false; } // Final result console.log('\n=== Test Result ==='); if (passed) { console.log('āœ… ALL TESTS PASSED'); console.log('\nNote: Run test_js_mix_payloads_receiver.js to receive the messages.'); process.exit(0); } else { console.log('āŒ SOME TESTS FAILED'); process.exit(1); } } catch (error) { console.error('\nāŒ Test failed with error:', error.message); console.error(error.stack); process.exit(1); } } runTest();