test: bind the port-sharing test servers to ephemeral ports; guard new fixed ports

Four ports were shared by two files each — 3162 (qr-auth / auth-security), 3170
(multiuser-auth / routes/ws-routes), 3230 and 3231 (cod54-hook-event-auth /
routes/voice-routes). Files run serially (`fileParallelism: false`), so the pairs
never met inside one run; they collide between two runs on one host, or with
anything else holding the port. All six files now bind port 0 and read the
number back (`boundPort` for WebServer, `server.address()` after each listen for
the raw Fastify / ws servers).

test/test-ports-guard.test.ts fails on a WebServer built under test/ whose port
argument is not the literal 0 — `new WebServer(…)`, a subclass, or a destructured
alias (`{ WebServer: T }`, as quick-start.test.ts does) — outside a legacy list of
the 43 files that construct one with a non-zero port today; the follow-up sweep
converts them. A converted file cannot stay listed. What it does not cover (helper
parameters, `import { WebServer as X }`, raw listen sites) is written down in it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Randalix
2026-10-08 14:56:42 +02:00
co-authored by Claude Opus 5.5
parent 03629c966e
commit bb4e7943c5
7 changed files with 187 additions and 52 deletions
+6 -9
View File
@@ -1,12 +1,12 @@
/**
* @fileoverview Phase 2 multi-user auth integration tests (live server, port 3170+).
* @fileoverview Phase 2 multi-user auth integration tests (live server, ephemeral port).
*
* Verifies the multi-user auth branch end to end: per-user Basic verify, cookie
* identity, wrong-password / disabled-user rejection, the mustChangePassword
* lockbox + self-service change, per-account rate limiting, and QR identity binding
* (tunnel-manager unit level). Single-user auth is covered by auth-security.test.ts.
*
* Ports: 3170 (multi-user server), 3171 (rate-limit server).
* Ports: ephemeral (`new WebServer(0, …)`, read back through `boundPort`).
*/
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
@@ -21,9 +21,6 @@ import { AUTH_FAILURE_MAX } from '../src/config/auth-config.js';
vi.spyOn(TmuxManager, 'isTmuxAvailable').mockReturnValue(true);
const PORT = 3170;
const RATE_PORT = 3171;
function basic(user: string, pass: string): string {
return 'Basic ' + Buffer.from(`${user}:${pass}`).toString('base64');
}
@@ -68,7 +65,7 @@ beforeAll(async () => {
const { updateUser } = await import('../src/user-store.js');
await updateUser('carol', { disabled: true });
server = new WebServer(PORT, false, true);
server = new WebServer(0, false, true);
await server.start();
});
@@ -84,7 +81,7 @@ afterAll(async () => {
await fs.rm(spacesDir, { recursive: true, force: true }).catch(() => {});
});
const url = (p: string) => `http://localhost:${PORT}${p}`;
const url = (p: string) => `http://localhost:${server.boundPort}${p}`;
describe('multi-user auth', () => {
it('rejects unauthenticated requests', async () => {
@@ -161,9 +158,9 @@ describe('multi-user auth', () => {
});
it('verify-first: a correct password is never rate-limited and self-heals failures (#17)', async () => {
rateServer = new WebServer(RATE_PORT, false, true);
rateServer = new WebServer(0, false, true);
await rateServer.start();
const rurl = (p: string) => `http://localhost:${RATE_PORT}${p}`;
const rurl = (p: string) => `http://localhost:${rateServer.boundPort}${p}`;
// Nine wrong passwords (one below the cap) are each rejected 401 — not throttled yet.
for (let i = 0; i < AUTH_FAILURE_MAX - 1; i++) {