import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import test from "node:test"; import { ConfigurationError, parseEndpoint, validateInvocation, } from "../src/config.ts"; const repositoryRoot = fileURLToPath(new URL("..", import.meta.url)); const executable = fileURLToPath( new URL("../bin/codex-app-server-bridge.js", import.meta.url), ); test("only the exact app-server invocation is accepted", () => { validateInvocation(["app-server"]); for (const arguments_ of [[], ["help"], ["app-server", "extra"], ["--version"]]) { assert.throws(() => validateInvocation(arguments_), ConfigurationError); } }); test("strict loopback endpoints are accepted", () => { assert.deepEqual(parseEndpoint("ws://127.0.0.1:4500"), { url: "ws://127.0.0.1:4500", display: "ws://127.0.0.1:4500", }); assert.deepEqual(parseEndpoint("ws://[::1]:4500/"), { url: "ws://[::1]:4500", display: "ws://[::1]:4500", }); }); test("expanded, ambiguous, and invalid endpoints are rejected", () => { const rejected = [ undefined, "", "ws://localhost:4500", "ws://127.0.0.1", "ws://127.0.0.1:0", "ws://127.0.0.1:65536", "ws://127.0.0.2:4500", "wss://127.0.0.1:4500", "http://127.0.0.1:4500", "ws://user:secret@127.0.0.1:4500", "ws://127.0.0.1:4500/path", "ws://127.0.0.1:4500/?query=value", "ws://127.0.0.1:4500/#fragment", ]; for (const endpoint of rejected) { assert.throws(() => parseEndpoint(endpoint), ConfigurationError, String(endpoint)); } }); test("the executable reports the intentional unsupported Node error", () => { const result = spawnSync(process.execPath, [executable, "app-server"], { cwd: repositoryRoot, encoding: "utf8", env: { ...process.env, CODEX_APP_SERVER_BRIDGE_TEST_NODE_VERSION: "23.9.0", CODEX_APP_SERVER_URL: "ws://127.0.0.1:4500", }, }); assert.equal(result.status, 1); assert.equal(result.stdout, ""); assert.match(result.stderr, /requires Node\.js 24 or newer/); }); test("strict type checking rejects non-erasable TypeScript syntax", () => { const compiler = fileURLToPath(new URL("../node_modules/typescript/bin/tsc", import.meta.url)); const fixture = fileURLToPath(new URL("fixtures/non-erasable.ts", import.meta.url)); const result = spawnSync( process.execPath, [ compiler, "--ignoreConfig", "--noEmit", "--strict", "--erasableSyntaxOnly", "--module", "NodeNext", "--moduleResolution", "NodeNext", "--target", "ES2024", fixture, ], { cwd: repositoryRoot, encoding: "utf8" }, ); assert.notEqual(result.status, 0); assert.match(`${result.stdout}${result.stderr}`, /erasableSyntaxOnly|not supported/i); });