mirror of
https://github.com/ancsemi/Haven.git
synced 2026-10-27 09:05:01 +00:00
97 lines
3.8 KiB
JavaScript
97 lines
3.8 KiB
JavaScript
'use strict';
|
|
|
|
// Shared by the socket access tests: boot a throwaway server on a scratch
|
|
// port and data dir, register accounts, connect sockets and wait for events.
|
|
// Loading this file on its own does nothing.
|
|
|
|
const http = require('node:http');
|
|
const path = require('node:path');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const { spawn } = require('node:child_process');
|
|
const { io } = require('socket.io-client');
|
|
|
|
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
function createHarness(port, name) {
|
|
const BASE = `http://localhost:${port}`;
|
|
const DATA = path.join(os.tmpdir(), `haven-${name}-${Date.now()}`);
|
|
const sockets = [];
|
|
let server;
|
|
|
|
const post = (p, body) => new Promise((res, rej) => {
|
|
const d = JSON.stringify(body);
|
|
const r = http.request({ host: 'localhost', port, path: p, method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(d) } },
|
|
(x) => { let b = ''; x.on('data', (c) => (b += c)); x.on('end', () => { try { res(JSON.parse(b)); } catch { res({ raw: b }); } }); });
|
|
r.on('error', rej); r.write(d); r.end();
|
|
});
|
|
|
|
async function start() {
|
|
fs.mkdirSync(DATA, { recursive: true });
|
|
server = spawn(process.execPath, ['server.js'], {
|
|
cwd: path.join(__dirname, '..'),
|
|
env: { ...process.env, PORT: String(port), HAVEN_DATA_DIR: DATA, ADMIN_USERNAME: 'admin', FORCE_HTTP: 'true' },
|
|
stdio: 'ignore',
|
|
});
|
|
for (let i = 0; i < 60; i++) {
|
|
try {
|
|
await new Promise((res, rej) => http.get(`${BASE}/api/health`, (r) => (r.statusCode === 200 ? res() : rej())).on('error', rej));
|
|
return;
|
|
} catch { await wait(500); }
|
|
}
|
|
throw new Error('server did not start');
|
|
}
|
|
|
|
// Waits for the server to exit before removing its data folder: Windows
|
|
// will not delete files a running process still holds open, which left a
|
|
// test folder behind in the temp directory on every run.
|
|
async function stop() {
|
|
for (const s of sockets) s.close();
|
|
if (server && server.exitCode === null) {
|
|
const exited = new Promise((r) => server.once('exit', r));
|
|
server.kill();
|
|
await exited;
|
|
}
|
|
try { fs.rmSync(DATA, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }); } catch (err) { console.warn('could not remove test data:', err.message); }
|
|
}
|
|
|
|
async function register(username) {
|
|
const r = await post('/api/auth/register', { username, password: 'access12345', eulaVersion: '2.0', ageVerified: true });
|
|
if (!r.token) throw new Error(`register ${username} failed: ${JSON.stringify(r)}`);
|
|
return { token: r.token, id: r.user.id };
|
|
}
|
|
|
|
function connect(token) {
|
|
const s = io(BASE, { auth: { token }, transports: ['websocket'], forceNew: true });
|
|
sockets.push(s);
|
|
return new Promise((res, rej) => { s.on('connect', () => res(s)); s.on('connect_error', rej); });
|
|
}
|
|
|
|
// The next matching event, or null after `ms`.
|
|
function next(sock, event, filter = () => true, ms = 3000) {
|
|
return new Promise((res) => {
|
|
const t = setTimeout(() => { sock.off(event, h); res(null); }, ms);
|
|
const h = (...args) => { if (!filter(...args)) return; clearTimeout(t); sock.off(event, h); res(args.length > 1 ? args : args[0]); };
|
|
sock.on(event, h);
|
|
});
|
|
}
|
|
|
|
// Emit with an acknowledgement callback.
|
|
const ask = (sock, event, data, ms = 3000) => new Promise((res) => {
|
|
const t = setTimeout(() => res(null), ms);
|
|
sock.emit(event, data, (reply) => { clearTimeout(t); res(reply); });
|
|
});
|
|
|
|
// Every channel the socket's user can see, by name.
|
|
async function channels(sock) {
|
|
const list = next(sock, 'channels-list');
|
|
sock.emit('get-channels');
|
|
return (await list) || [];
|
|
}
|
|
|
|
return { start, stop, register, connect, next, ask, channels, post, dataDir: DATA };
|
|
}
|
|
|
|
module.exports = { createHarness, wait };
|