import { createServer } from "node:net"; import { once } from "node:events"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { resetDb, TEST_DATABASE_URL, TEST_SECRET_KEY, TEST_SECRET_KEY_ID } from "./helpers.js"; const ENV_KEYS = [ "DATABASE_URL", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY", "FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_BOT_OPEN_ID", "HUB_PROJECT_WORKSPACE_ROOT", "HUB_SESSION_SECRET", "HUB_PUBLIC_BASE_URL", "HUB_FEISHU_LISTENER_ENABLED", "HUB_SECRET_KEYRING_FILE", "HOST", "PORT", ] as const; describe("Hub startup", () => { const previousEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]])); afterEach(() => { for (const [key, value] of previousEnv) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } }); it("rejects startup before the Feishu listener when the HTTP bind fails", async () => { await resetDb(); const blocker = createServer(); blocker.listen(0, "127.0.0.1"); await once(blocker, "listening"); const address = blocker.address(); if (address === null || typeof address === "string") throw new Error("expected an INET test listener"); const keyringDirectory = await mkdtemp(join(tmpdir(), "cph-hub-start-keyring-")); const keyringFile = join(keyringDirectory, "keyring.json"); await writeFile(keyringFile, JSON.stringify({ version: 1, activeKeyId: TEST_SECRET_KEY_ID, keys: { [TEST_SECRET_KEY_ID]: TEST_SECRET_KEY.toString("base64") }, }), { mode: 0o600 }); Object.assign(process.env, { DATABASE_URL: TEST_DATABASE_URL, FEISHU_APP_ID: "test-app", FEISHU_APP_SECRET: "test-secret", FEISHU_BOT_OPEN_ID: "ou_test_bot", HUB_PROJECT_WORKSPACE_ROOT: "/tmp/cph-hub-server-start-test", HUB_SESSION_SECRET: "integration-session-secret-with-32-bytes", HUB_PUBLIC_BASE_URL: "https://hub.example.test", HUB_FEISHU_LISTENER_ENABLED: "false", HUB_SECRET_KEYRING_FILE: keyringFile, HOST: "127.0.0.1", PORT: String(address.port), }); try { const { startHub } = await import("../../src/hub.js"); await expect(startHub()).rejects.toMatchObject({ code: "EADDRINUSE" }); } finally { blocker.close(); await once(blocker, "close"); await rm(keyringDirectory, { recursive: true, force: true }); } }); });