feat(transport): introduce transport abstraction and socket-mode support for RustBridge
This commit is contained in:
124
test/helpers/mock-socket-server.mjs
Normal file
124
test/helpers/mock-socket-server.mjs
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Mock "Rust daemon" for testing the SocketTransport and RustBridge socket mode.
|
||||
* Creates a Unix socket server, accepts connections, and speaks the same
|
||||
* JSON-over-newline IPC protocol as mock-rust-binary.mjs.
|
||||
*
|
||||
* Usage: node mock-socket-server.mjs <socket-path>
|
||||
* Signals readiness by writing a JSON line to stdout: {"socketPath":"...","ready":true}
|
||||
*/
|
||||
|
||||
import * as net from 'net';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const socketPath = process.argv[2];
|
||||
if (!socketPath) {
|
||||
process.stderr.write('Usage: mock-socket-server.mjs <socket-path>\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Remove stale socket file
|
||||
try { fs.unlinkSync(socketPath); } catch { /* ignore */ }
|
||||
|
||||
/**
|
||||
* Backpressure-aware write to a socket.
|
||||
*/
|
||||
function writeResponse(conn, data) {
|
||||
const json = JSON.stringify(data) + '\n';
|
||||
if (!conn.write(json)) {
|
||||
conn.once('drain', () => {});
|
||||
}
|
||||
}
|
||||
|
||||
function handleLine(line, conn) {
|
||||
let request;
|
||||
try {
|
||||
request = JSON.parse(line);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const { id, method, params } = request;
|
||||
|
||||
if (method === 'echo') {
|
||||
writeResponse(conn, { id, success: true, result: params });
|
||||
} else if (method === 'largeEcho') {
|
||||
writeResponse(conn, { id, success: true, result: params });
|
||||
} else if (method === 'error') {
|
||||
writeResponse(conn, { id, success: false, error: 'Test error message' });
|
||||
} else if (method === 'emitEvent') {
|
||||
writeResponse(conn, { event: params.eventName, data: params.eventData });
|
||||
writeResponse(conn, { id, success: true, result: null });
|
||||
} else if (method === 'slow') {
|
||||
setTimeout(() => {
|
||||
writeResponse(conn, { id, success: true, result: { delayed: true } });
|
||||
}, 100);
|
||||
} else if (method === 'streamEcho') {
|
||||
const count = params.count || 0;
|
||||
let sent = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (sent < count) {
|
||||
writeResponse(conn, { id, stream: true, data: { index: sent, value: `chunk_${sent}` } });
|
||||
sent++;
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
writeResponse(conn, { id, success: true, result: { totalChunks: count } });
|
||||
}
|
||||
}, 10);
|
||||
} else if (method === 'streamError') {
|
||||
writeResponse(conn, { id, stream: true, data: { index: 0, value: 'before_error' } });
|
||||
setTimeout(() => {
|
||||
writeResponse(conn, { id, success: false, error: 'Stream error after chunk' });
|
||||
}, 20);
|
||||
} else if (method === 'streamEmpty') {
|
||||
writeResponse(conn, { id, success: true, result: { totalChunks: 0 } });
|
||||
} else if (method === 'exit') {
|
||||
writeResponse(conn, { id, success: true, result: null });
|
||||
// In socket mode, 'exit' just closes this connection, not the server
|
||||
setTimeout(() => conn.end(), 50);
|
||||
} else {
|
||||
writeResponse(conn, { id, success: false, error: `Unknown method: ${method}` });
|
||||
}
|
||||
}
|
||||
|
||||
const server = net.createServer((conn) => {
|
||||
// Send ready event on each new connection
|
||||
writeResponse(conn, { event: 'ready', data: { version: '1.0.0' } });
|
||||
|
||||
// Buffer-based newline scanner for incoming data
|
||||
let buffer = Buffer.alloc(0);
|
||||
conn.on('data', (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
let idx;
|
||||
while ((idx = buffer.indexOf(0x0A)) !== -1) {
|
||||
const lineBuffer = buffer.subarray(0, idx);
|
||||
buffer = buffer.subarray(idx + 1);
|
||||
const line = lineBuffer.toString('utf8').trim();
|
||||
if (line) {
|
||||
handleLine(line, conn);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
conn.on('error', () => { /* ignore client errors */ });
|
||||
});
|
||||
|
||||
server.listen(socketPath, () => {
|
||||
// Signal to parent that the server is ready
|
||||
process.stdout.write(JSON.stringify({ socketPath, ready: true }) + '\n');
|
||||
});
|
||||
|
||||
// Handle SIGTERM gracefully
|
||||
process.on('SIGTERM', () => {
|
||||
server.close();
|
||||
try { fs.unlinkSync(socketPath); } catch { /* ignore */ }
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Handle SIGINT
|
||||
process.on('SIGINT', () => {
|
||||
server.close();
|
||||
try { fs.unlinkSync(socketPath); } catch { /* ignore */ }
|
||||
process.exit(0);
|
||||
});
|
||||
Reference in New Issue
Block a user