mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
test(ci): OS-assigned ephemeral ports for subprocess suites — kill the port-collision flake (#176)
The subprocess-booting test suites hand-picked fixed ports in a cramped ~3955-4021 range, and 156-schedule-read-path deviated to a RANDOM port (3900 + rand%90) that overlapped those fixed ports. Under CI load two servers could race on the same port, surfacing as flaky "no such table: devices" / "FOREIGN KEY constraint failed" (a server answering a request against a half-migrated or wrong DB). It's environmental — the suites pass locally and in isolation. Fix: a shared test/helpers/free-port.js (bind :0 on loopback, read the OS-assigned port, release) called in before() so every suite gets a guaranteed-unique ephemeral port — concurrent suites can no longer collide, and no one has to hand-assign ports. - Codemod converted 30 suites: const PORT = <fixed|random> -> let PORT (+ BASE) assigned via `PORT = await freePort()` at the top of before(). - 3 hand-fixed (different structure): 148-eviction-storm (lowercase `base`), boot-health (no before() — allocates PORT + a throwaway SEED_PORT inside the test, replacing the hardcoded 3894), totp-keyrotation (no before() — allocates at the test start before bootServer()). No fixed 39xx/40xx ports remain. Full server suite 435/435; the 4 hand-touched suites pass in isolation. Pure test-infra change — no app code touched.
This commit is contained in:
parent
40efaa1fed
commit
12c0004245
|
|
@ -10,13 +10,16 @@ const { spawn } = require('node:child_process');
|
|||
const ioClient = require('socket.io-client');
|
||||
const path = require('node:path'); const os = require('node:os'); const fs = require('node:fs'); const crypto = require('node:crypto');
|
||||
|
||||
const PORT = 3955;
|
||||
const base = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT;
|
||||
let base;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-storm-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc;
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
base = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-storm.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ const WebSocket = require('../node_modules/ws');
|
|||
const ioClient = require('socket.io-client');
|
||||
const path = require('node:path'); const os = require('node:os'); const fs = require('node:fs'); const crypto = require('node:crypto');
|
||||
|
||||
const PORT = 3957;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-ho-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc;
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-ho.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ const os = require('node:os');
|
|||
const fs = require('node:fs');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const PORT = 3900 + (crypto.randomBytes(1)[0] % 90); // avoid clashes with sibling subprocess suites
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT; // avoid clashes with sibling subprocess suites
|
||||
let BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-156-test-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-156-test-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
|
||||
|
|
@ -60,6 +61,8 @@ async function loadItem(itemId) {
|
|||
}
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ const os = require('node:os');
|
|||
const fs = require('node:fs');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const PORT = 3992;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-agency-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-agency.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ const crypto = require('node:crypto');
|
|||
const ioClient = require('socket.io-client');
|
||||
const { PUBLIC_ROUTERS, JWT_ONLY_ROUTERS } = require('../config/api-surface');
|
||||
|
||||
const PORT = 3978;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-api-test-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-api-test-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
|
||||
|
|
@ -38,6 +38,8 @@ const auth = (tok, extra = {}) => ({ headers: { Authorization: 'Bearer ' + tok,
|
|||
const post = (tok, obj, extra) => ({ method: 'POST', ...auth(tok, extra), body: JSON.stringify(obj) });
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 4011;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-billauthz-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc, db;
|
||||
|
||||
|
|
@ -30,6 +30,8 @@ const setRole = (email, role) => db.prepare('UPDATE users SET role = ? WHERE ema
|
|||
let adminJwt, userJwt, billingToken, billingTokenId, readToken;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-billauthz.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3999;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-billing-ep-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc, db;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-billing-ep.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 4021;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-billmint-' + crypto.randomBytes(4).toString('hex'));
|
||||
process.env.DATA_DIR = DATA_DIR; // so requiring lib/billing-token's deps resolves this db too
|
||||
|
||||
|
|
@ -25,6 +25,8 @@ const { hashToken } = require('../middleware/apiToken');
|
|||
let proc, db, minted;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-billmint.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3993;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-blockauthz-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc, db;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-blockauthz.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -14,17 +14,19 @@ const os = require('node:os');
|
|||
const fs = require('node:fs');
|
||||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
|
||||
const PORT = 3995;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-boot-' + crypto.randomBytes(4).toString('hex'));
|
||||
const DBPATH = path.join(DATA_DIR, 'db', 'remote_display.db');
|
||||
|
||||
test('boots + serves /api/status quickly against a pre-bloated table; prune drains in background', async () => {
|
||||
const PORT = await freePort();
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const SEED_PORT = await freePort();
|
||||
// 1) Create + migrate the DB in a throwaway boot, then seed a large backlog.
|
||||
{
|
||||
const p = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: '3894', NODE_ENV: 'test' }, stdio: 'ignore' });
|
||||
for (let i = 0; i < 60; i++) { try { const r = await fetch('http://127.0.0.1:3894/api/status'); if (r.ok) break; } catch { /* */ } await new Promise(r => setTimeout(r, 200)); }
|
||||
const p = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: String(SEED_PORT), NODE_ENV: 'test' }, stdio: 'ignore' });
|
||||
for (let i = 0; i < 60; i++) { try { const r = await fetch(`http://127.0.0.1:${SEED_PORT}/api/status`); if (r.ok) break; } catch { /* */ } await new Promise(r => setTimeout(r, 200)); }
|
||||
p.kill('SIGKILL');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const ioClient = require('socket.io-client');
|
||||
|
||||
const PORT = 3984;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-ack-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-ack-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
const DEDUP_MS = 600;
|
||||
|
|
@ -24,6 +24,8 @@ let proc;
|
|||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -15,14 +15,16 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const ioClient = require('socket.io-client');
|
||||
|
||||
const PORT = 3985;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-flood-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-flood-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
let proc;
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -15,14 +15,16 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const ioClient = require('socket.io-client');
|
||||
|
||||
const PORT = 3986;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-valve-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-valve-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
let proc;
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ const crypto = require('node:crypto');
|
|||
const ioClient = require('socket.io-client');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3987;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-block-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-block-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
const DB_PATH = path.join(DATA_DIR, 'db', 'remote_display.db');
|
||||
|
|
@ -30,6 +30,8 @@ let tdb; // ONE long-lived operator-style connection (mirrors how the server hol
|
|||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -8,11 +8,14 @@ const assert = require('node:assert/strict');
|
|||
const { spawn } = require('node:child_process');
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
const PORT = 3977; const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-doqr-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc, JWT;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-doqr.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: String(PORT), NODE_ENV: 'test' }, stdio: ['ignore', logFd, logFd] });
|
||||
let up = false; for (let i = 0; i < 80; i++) { try { if ((await fetch(BASE + '/api/status')).ok) { up = true; break; } } catch {} await sleep(250); }
|
||||
|
|
|
|||
|
|
@ -16,14 +16,16 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const ioClient = require('socket.io-client');
|
||||
|
||||
const PORT = 3989;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-pair-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-pair-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
let proc, JWT;
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: String(PORT), NODE_ENV: 'test' }, stdio: ['ignore', logFd, logFd] });
|
||||
let up = false;
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3996;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-zone-test-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-zone-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
const PW = 'Passw0rd123';
|
||||
|
|
@ -55,6 +55,8 @@ async function getOrphanCount() {
|
|||
}
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const crypto = require('node:crypto');
|
|||
const ioClient = require('socket.io-client');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3988;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-recl-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-recl-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
const DB_PATH = path.join(DATA_DIR, 'db', 'remote_display.db');
|
||||
|
|
@ -25,6 +25,8 @@ let proc, tdb;
|
|||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ const { spawn } = require('node:child_process');
|
|||
const ioClient = require('../node_modules/socket.io-client');
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
const PORT = 3976; const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-gsync-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc, JWT;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-gsync.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: String(PORT), NODE_ENV: 'test' }, stdio: ['ignore', logFd, logFd] });
|
||||
let up = false; for (let i = 0; i < 80; i++) { try { if ((await fetch(BASE + '/api/status')).ok) { up = true; break; } } catch {} await sleep(250); }
|
||||
|
|
|
|||
21
server/test/helpers/free-port.js
Normal file
21
server/test/helpers/free-port.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
'use strict';
|
||||
const net = require('net');
|
||||
|
||||
// Allocate a free TCP port from the OS (bind :0 on loopback, read it back, release it).
|
||||
// Subprocess test suites call this in before() instead of hand-picking a fixed/random port —
|
||||
// the old 39xx scheme collided under CI load (a random port in the shared range, or a new
|
||||
// suite reusing one), surfacing as flaky "no such table: devices" / FK errors when two servers
|
||||
// raced on the same port. An OS-assigned ephemeral port per suite can't collide.
|
||||
function freePort() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const srv = net.createServer();
|
||||
srv.unref();
|
||||
srv.on('error', reject);
|
||||
srv.listen(0, '127.0.0.1', () => {
|
||||
const port = srv.address().port;
|
||||
srv.close(() => resolve(port));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { freePort };
|
||||
|
|
@ -13,13 +13,15 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3982;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-lag-int-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-lag-int-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
let proc;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3994;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-mute-test-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-mute-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
const PW = 'Passw0rd123';
|
||||
|
|
@ -33,6 +33,8 @@ const post = (tok, obj) => ({ method: 'POST', ...auth(tok), body: JSON.stringify
|
|||
const put = (tok, obj) => ({ method: 'PUT', ...auth(tok), body: JSON.stringify(obj || {}) });
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ const os = require('node:os');
|
|||
const fs = require('node:fs');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const PORT = 3991;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-ota-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-ota-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
let proc, LATEST;
|
||||
|
|
@ -25,6 +25,8 @@ const check = async (version, deviceId) => {
|
|||
};
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
// the breaker only reports update_available when an APK actually exists — give the
|
||||
// test server a dummy one (resolveApkPath checks DATA_DIR/ScreenTinker.apk).
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ const crypto = require('node:crypto');
|
|||
const ioClient = require('socket.io-client');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3997; // must be unique across the suite (files run concurrently under `node --test`)
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT; // must be unique across the suite (files run concurrently under `node --test`)
|
||||
let BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-storm-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-storm-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
let proc, rdb;
|
||||
|
|
@ -37,6 +38,8 @@ let proc, rdb;
|
|||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const ioClient = require('socket.io-client');
|
||||
|
||||
const PORT = 3983;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-thr-int-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-thr-int-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
let proc;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ const crypto = require('node:crypto');
|
|||
const Database = require('better-sqlite3');
|
||||
const ioClient = require('socket.io-client');
|
||||
|
||||
const PORT = 3998;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-statusdbg-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc, db;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-statusdbg.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
const PORT = 3990;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-thumb-test-' + crypto.randomBytes(4).toString('hex'));
|
||||
const CONTENT_DIR = path.join(DATA_DIR, 'uploads', 'content');
|
||||
const LOG = path.join(os.tmpdir(), 'st-thumb-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
|
|
@ -47,6 +47,8 @@ function makeContent(thumbnailPath, { mime = 'image/png' } = {}) {
|
|||
}
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
// Mock upstream standing in for img.youtube.com. /missing/* -> 404 to exercise the
|
||||
// clean-failure path; everything else -> a 200 image/png.
|
||||
upstream = http.createServer((req, res) => {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const { authenticator } = require('otplib');
|
||||
|
||||
const PORT = 3980;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-totp-rot-' + crypto.randomBytes(4).toString('hex'));
|
||||
|
||||
function bootServer(jwtSecret) {
|
||||
|
|
@ -44,6 +44,8 @@ const post = (o) => ({ method: 'POST', headers: { 'Content-Type': 'application/j
|
|||
const postAuth = (tok, o) => ({ method: 'POST', headers: { Authorization: 'Bearer ' + tok, 'Content-Type': 'application/json' }, body: JSON.stringify(o || {}) });
|
||||
|
||||
test('#100 key rotation does NOT brick TOTP: recovery survives; TOTP fails cleanly (no 500)', async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
let proc = bootServer('keyA-' + crypto.randomBytes(8).toString('hex'));
|
||||
try {
|
||||
await waitUp();
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ const fs = require('node:fs');
|
|||
const crypto = require('node:crypto');
|
||||
const { authenticator } = require('otplib');
|
||||
|
||||
const PORT = 3979;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-totp-test-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-totp-' + crypto.randomBytes(4).toString('hex') + '.log');
|
||||
let proc;
|
||||
|
|
@ -28,6 +28,8 @@ const auth = (tok, extra = {}) => ({ headers: { Authorization: 'Bearer ' + tok,
|
|||
const post = (tok, obj, extra) => ({ method: 'POST', ...auth(tok, extra), body: JSON.stringify(obj || {}) });
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
|
|
|
|||
|
|
@ -78,14 +78,16 @@ test('cross-client conformance FINDING: APK socket.io TRANSPORT backoff diverges
|
|||
});
|
||||
|
||||
// ============================ E2E: MIXED FLEET against the real server ============================
|
||||
const PORT = 3968;
|
||||
const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-v4core-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-v4core.log');
|
||||
let proc, JWT;
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: String(PORT), NODE_ENV: 'test' }, stdio: ['ignore', logFd, logFd] });
|
||||
let up = false;
|
||||
|
|
|
|||
|
|
@ -48,12 +48,15 @@ test('A2 window: reconnects older than 60s drop out (a past flap does not stay D
|
|||
});
|
||||
|
||||
// ================= E2E: the shared !isPlaylistRefresh gate + change-detection =================
|
||||
const PORT = 3972; const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-rg-e2e-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-rg-e2e.log');
|
||||
let proc, JWT;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: String(PORT), NODE_ENV: 'test' }, stdio: ['ignore', logFd, logFd] });
|
||||
let up = false; for (let i = 0; i < 80; i++) { try { if ((await fetch(BASE + '/api/status')).ok) { up = true; break; } } catch { /* */ } await sleep(250); }
|
||||
|
|
|
|||
|
|
@ -83,10 +83,13 @@ test('B/wgt CLEAN-CLOSE: pagehide(false) -> clean_exit; BACKGROUNDING pagehide(t
|
|||
});
|
||||
|
||||
// ============ PART A — server-side socket / #148 / reconnect-vs-exit safety ============
|
||||
const PORT = 3975; const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-exit3-' + crypto.randomBytes(4).toString('hex'));
|
||||
let proc, JWT;
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(path.join(os.tmpdir(), 'st-exit3.log'), 'w');
|
||||
proc = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: String(PORT), NODE_ENV: 'test' }, stdio: ['ignore', logFd, logFd] });
|
||||
let up = false; for (let i = 0; i < 80; i++) { try { if ((await fetch(BASE + '/api/status')).ok) { up = true; break; } } catch {} await sleep(250); }
|
||||
|
|
|
|||
|
|
@ -22,12 +22,15 @@ test('sanitizeExitReason: only crashed/clean_exit accepted; silent + unknown REJ
|
|||
});
|
||||
|
||||
// ===== E2E =====
|
||||
const PORT = 3974; const BASE = `http://127.0.0.1:${PORT}`;
|
||||
const { freePort } = require('./helpers/free-port');
|
||||
let PORT, BASE;
|
||||
const DATA_DIR = path.join(os.tmpdir(), 'st-exit-' + crypto.randomBytes(4).toString('hex'));
|
||||
const LOG = path.join(os.tmpdir(), 'st-exit.log');
|
||||
let proc, JWT;
|
||||
|
||||
before(async () => {
|
||||
PORT = await freePort();
|
||||
BASE = `http://127.0.0.1:${PORT}`;
|
||||
const logFd = fs.openSync(LOG, 'w');
|
||||
proc = spawn('node', ['server.js'], { cwd: path.join(__dirname, '..'), env: { ...process.env, DATA_DIR, SELF_HOSTED: 'true', PORT: String(PORT), NODE_ENV: 'test' }, stdio: ['ignore', logFd, logFd] });
|
||||
let up = false; for (let i = 0; i < 80; i++) { try { if ((await fetch(BASE + '/api/status')).ok) { up = true; break; } } catch { /* */ } await sleep(250); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue