🎉 init: 小龙的工作空间

This commit is contained in:
大海
2026-06-06 10:40:48 +08:00
commit a188ee1426
3201 changed files with 231817 additions and 0 deletions
+213
View File
@@ -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);
});