🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
// 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);
|
||||
});
|
||||
Reference in New Issue
Block a user