214 lines
6.8 KiB
JavaScript
214 lines
6.8 KiB
JavaScript
// E2E test: Two clients connect to relay node and exchange messages
|
|
const net = require('net');
|
|
const http = require('http');
|
|
|
|
const SERVER = 'http://localhost:3001';
|
|
const RELAY_HOST = '127.0.0.1';
|
|
const RELAY_PORT = 4101;
|
|
|
|
function api(path, method = 'GET', body, token) {
|
|
return new Promise((resolve, reject) => {
|
|
const url = new URL(path, SERVER);
|
|
const data = body ? JSON.stringify(body) : undefined;
|
|
const opts = {
|
|
hostname: url.hostname, port: url.port, path: url.pathname, method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
|
|
},
|
|
};
|
|
const req = http.request(opts, (res) => {
|
|
let b = '';
|
|
res.on('data', c => b += c);
|
|
res.on('end', () => { try { resolve(JSON.parse(b)) } catch { resolve({error: b}) } });
|
|
});
|
|
req.on('error', reject);
|
|
if (data) req.write(data);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
function frameParser() {
|
|
let buf = Buffer.alloc(0);
|
|
return {
|
|
push(chunk) {
|
|
buf = Buffer.concat([buf, chunk]);
|
|
const msgs = [];
|
|
while (buf.length >= 4) {
|
|
const len = buf.readUInt32BE(0);
|
|
if (buf.length < 4 + len) break;
|
|
try {
|
|
msgs.push(JSON.parse(buf.subarray(4, 4 + len).toString()));
|
|
} catch {}
|
|
buf = buf.subarray(4 + len);
|
|
}
|
|
return msgs;
|
|
}
|
|
};
|
|
}
|
|
|
|
function encodeMsg(msg) {
|
|
const json = JSON.stringify(msg);
|
|
const payload = Buffer.from(json, 'utf-8');
|
|
const header = Buffer.alloc(4);
|
|
header.writeUInt32BE(payload.length, 0);
|
|
return Buffer.concat([header, payload]);
|
|
}
|
|
|
|
async function main() {
|
|
console.log('=== E2E Relay Test ===\n');
|
|
|
|
// 1. Register & login
|
|
console.log('1. Register user...');
|
|
const reg = await api('/api/auth/register', 'POST', { email: 'e2e@test.com', password: '123456' });
|
|
const token = reg.data.token;
|
|
console.log(' OK, user_id:', reg.data.user.id);
|
|
|
|
// 2. Register two devices
|
|
console.log('2. Register devices...');
|
|
const d1 = await api('/api/devices/register', 'POST', {
|
|
device_name: 'Client-A', device_fingerprint: 'e2e-fp-a', public_key: ''
|
|
}, token);
|
|
const d2 = await api('/api/devices/register', 'POST', {
|
|
device_name: 'Client-B', device_fingerprint: 'e2e-fp-b', public_key: ''
|
|
}, token);
|
|
const devA = d1.data.id, devB = d2.data.id;
|
|
console.log(' OK, Device-A:', devA, 'Device-B:', devB);
|
|
|
|
// 3. Get relay node (assume already running)
|
|
console.log('3. Get relay node...');
|
|
const nodes = await api('/api/nodes/available', 'GET', null, token);
|
|
if (!nodes.data || nodes.data.length === 0) {
|
|
console.error(' FAIL: No relay nodes available. Make sure relay node is running.');
|
|
process.exit(1);
|
|
}
|
|
const node = nodes.data[0];
|
|
console.log(' OK, Node:', node.name, node.host + ':' + node.port);
|
|
|
|
// 4. Request connection token for Device-A
|
|
console.log('4. Request connection token...');
|
|
const conn = await api('/api/connections/request', 'POST', {
|
|
device_id: devA, node_id: node.id
|
|
}, token);
|
|
const connToken = conn.data.token;
|
|
console.log(' OK, Token obtained');
|
|
|
|
// 5. Connect Device-A to relay
|
|
console.log('5. Connect Device-A to relay...');
|
|
const socketA = new net.Socket();
|
|
const parserA = frameParser();
|
|
|
|
await new Promise((resolve, reject) => {
|
|
socketA.connect(node.port, node.host, () => {
|
|
// Send auth
|
|
socketA.write(encodeMsg({
|
|
type: 'auth', from_device_id: devA, to_device_id: 0,
|
|
payload: JSON.stringify({ action: 'connect', device_id: devA, token: connToken }),
|
|
timestamp: Date.now(), message_id: 'auth-1',
|
|
}));
|
|
});
|
|
socketA.on('data', (chunk) => {
|
|
for (const msg of parserA.push(chunk)) {
|
|
if (msg.type === 'control') {
|
|
const ctrl = JSON.parse(msg.payload);
|
|
if (ctrl.action === 'connect_ok') {
|
|
console.log(' OK, Device-A authenticated');
|
|
resolve(null);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
socketA.on('error', reject);
|
|
setTimeout(() => reject(new Error('Auth timeout')), 5000);
|
|
});
|
|
|
|
// 6. Connect Device-B to relay (simulate another client using same token pattern)
|
|
console.log('6. Request token for Device-B and connect...');
|
|
const conn2 = await api('/api/connections/request', 'POST', {
|
|
device_id: devB, node_id: node.id
|
|
}, token);
|
|
const connToken2 = conn2.data.token;
|
|
|
|
const socketB = new net.Socket();
|
|
const parserB = frameParser();
|
|
|
|
await new Promise((resolve, reject) => {
|
|
socketB.connect(node.port, node.host, () => {
|
|
socketB.write(encodeMsg({
|
|
type: 'auth', from_device_id: devB, to_device_id: 0,
|
|
payload: JSON.stringify({ action: 'connect', device_id: devB, token: connToken2 }),
|
|
timestamp: Date.now(), message_id: 'auth-2',
|
|
}));
|
|
});
|
|
socketB.on('data', (chunk) => {
|
|
for (const msg of parserB.push(chunk)) {
|
|
if (msg.type === 'control') {
|
|
if (JSON.parse(msg.payload).action === 'connect_ok') {
|
|
console.log(' OK, Device-B authenticated');
|
|
resolve(null);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
socketB.on('error', reject);
|
|
setTimeout(() => reject(new Error('Auth timeout')), 5000);
|
|
});
|
|
|
|
// 7. Device-A sends message to Device-B
|
|
console.log('7. Device-A -> Device-B: "Hello from A!"');
|
|
let msgReceived = false;
|
|
socketB.on('data', (chunk) => {
|
|
for (const msg of parserB.push(chunk)) {
|
|
if (msg.type === 'data' && msg.payload === 'Hello from A!') {
|
|
console.log(' OK, Device-B received: "' + msg.payload + '"');
|
|
msgReceived = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
socketA.write(encodeMsg({
|
|
type: 'data', from_device_id: devA, to_device_id: devB,
|
|
payload: 'Hello from A!',
|
|
timestamp: Date.now(), message_id: 'msg-1',
|
|
}));
|
|
|
|
// Wait for message delivery
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
|
|
// 8. Device-B sends reply
|
|
console.log('8. Device-B -> Device-A: "Hello from B!"');
|
|
let replyReceived = false;
|
|
socketA.on('data', (chunk) => {
|
|
for (const msg of parserA.push(chunk)) {
|
|
if (msg.type === 'data' && msg.payload === 'Hello from B!') {
|
|
console.log(' OK, Device-A received: "' + msg.payload + '"');
|
|
replyReceived = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
socketB.write(encodeMsg({
|
|
type: 'data', from_device_id: devB, to_device_id: devA,
|
|
payload: 'Hello from B!',
|
|
timestamp: Date.now(), message_id: 'msg-2',
|
|
}));
|
|
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
|
|
// Cleanup
|
|
socketA.destroy();
|
|
socketB.destroy();
|
|
|
|
console.log('\n=== Results ===');
|
|
console.log('Message A->B:', msgReceived ? '✅ PASS' : '❌ FAIL');
|
|
console.log('Message B->A:', replyReceived ? '✅ PASS' : '❌ FAIL');
|
|
console.log('\n=== E2E Test Complete ===');
|
|
process.exit(msgReceived && replyReceived ? 0 : 1);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Test failed:', err);
|
|
process.exit(1);
|
|
});
|