import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { collectUsedInstancePorts, parseServerHost, parseServerPort, suggestAvailablePort } from "../src/cli/setup-server.js"; test("server host parser accepts bind addresses and hostnames", () => { for (const host of ["0.0.0.0", "127.0.0.1", "::", "2001:db8::1", "localhost", "gateway.example.com"]) { assert.equal(parseServerHost(host), host); } }); test("server host parser rejects URLs, ports, paths, whitespace, and invalid labels", () => { for (const host of ["", "http://localhost", "localhost:8787", "[::1]", "host/path", "bad host", "-bad.example", "bad-.example"]) { assert.throws(() => parseServerHost(host), /server host/); } }); test("server port parser accepts only decimal ports in range", () => { assert.equal(parseServerPort("1"), 1); assert.equal(parseServerPort("8787"), 8787); assert.equal(parseServerPort("65535"), 65_535); for (const port of ["", "0", "65536", "1.5", "0x20", "8e3", "-1"]) { assert.throws(() => parseServerPort(port), /server port/); } }); test("available port suggestion advances without wrapping", () => { assert.equal(suggestAvailablePort(8787, new Set([8787, 8788])), 8789); assert.throws(() => suggestAvailablePort(65_535, new Set([65_535])), /No unassigned instance port/); }); test("instance port scan reads only valid Config v3 regular files and excludes target", (t) => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "gori-setup-ports-")); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const writePeer = (name: string, value: unknown): string => { const directory = path.join(root, name); fs.mkdirSync(directory); const file = path.join(directory, "config.json"); fs.writeFileSync(file, JSON.stringify(value)); return file; }; const peer = (port: number, configVersion: number = 3) => ({ configVersion, bot: { id: "peer-bot", workspace: os.tmpdir(), persona: "peer", agent: { id: "test", command: process.execPath, args: [], env: {} }, skills: [], permissions: { mode: "deny", allowedTools: [], allowedCommandPatterns: [] } }, gateway: { server: { host: "127.0.0.1", port, publicBaseUrl: "" }, policy: { allowedUsers: [], allowedChats: [], requireMentionInGroup: true }, platform: { type: "webhook", secret: "test-secret" } }, runtime: {} }); writePeer("valid", peer(8787)); const excluded = writePeer("current", peer(8788)); writePeer("legacy", peer(8789, 2)); writePeer("out-of-range", peer(70_000)); writePeer("broken", "not an object"); fs.writeFileSync(path.join(root, "broken", "config.json"), "{broken"); const linkedDirectory = path.join(root, "linked"); fs.symlinkSync(path.join(root, "valid"), linkedDirectory, "dir"); const symlinkConfigDirectory = path.join(root, "symlink-config"); fs.mkdirSync(symlinkConfigDirectory); fs.symlinkSync(path.join(root, "valid", "config.json"), path.join(symlinkConfigDirectory, "config.json")); assert.deepEqual([...collectUsedInstancePorts(root, excluded)], [8787]); });