// A host with several routes connects through the first one that answers. import { afterEach, describe, expect, it } from "bun:test"; import type { Server } from "bun"; import { T3Host } from "../src/t3/host.ts"; const ENVIRONMENT_ID = "env-laptop"; let servers: Server[] = []; afterEach(() => { for (const server of servers) server.stop(true); servers = []; }); /** * Just enough of a T3 server: the descriptor, a ticket, config, and an empty shell snapshot. * While `state.up` is false its descriptor returns 503, like a route that is down. */ function fakeT3(state = { up: true }) { const server = Bun.serve({ port: 0, fetch(req, srv) { const { pathname } = new URL(req.url); if (pathname === "/.well-known/t3/environment") { if (!state.up) return new Response("down", { status: 503 }); return Response.json({ environmentId: ENVIRONMENT_ID, label: "framework-gib", serverVersion: "0.1.5", orchestrationProtocolVersion: 2 }); } if (pathname === "/api/auth/websocket-ticket/rpc") return Response.json({ ticket: "t" }); if (pathname === "/ws" && srv.upgrade(req)) return undefined; return new Response("not found", { status: 404 }); }, websocket: { message(ws, data) { const frame = JSON.parse(String(data)) as { _tag: string; id: string; tag: string }; if (frame._tag !== "Request") return; if (frame.tag === "server.getConfig") { ws.send(JSON.stringify({ _tag: "Exit", requestId: frame.id, exit: { _tag: "Success", value: {} } })); } else if (frame.tag === "orchestration.subscribeShell") { const snapshot = { snapshotSequence: 1, projects: [], threads: [] }; ws.send(JSON.stringify({ _tag: "Chunk", requestId: frame.id, values: [{ kind: "snapshot", snapshot }] })); } }, }, }); servers.push(server); return `http://localhost:${server.port}`; } /** Resolves once `check` passes, polling every few milliseconds. */ async function until(check: () => boolean) { while (!check()) await Bun.sleep(5); } describe("T3Host routes", () => { it("falls back to the next route when the preferred one does not answer", async () => { const working = fakeT3(); // Port 9 (discard) refuses connections, standing in for a LAN route that is down. const refused = "http://127.0.0.1:9"; const spare = "https://t3laptop.example.org"; const host = new T3Host({ id: ENVIRONMENT_ID, label: "Laptop", urls: [refused, working, spare], token: "x", environmentId: ENVIRONMENT_ID }); host.start(); await until(() => host.status === "online"); expect(host.url).toBe(working); expect(host.routeAttempts.get(refused)).toMatchObject({ ok: false, error: "refused" }); expect(host.routeAttempts.get(working)).toMatchObject({ ok: true, error: null }); expect(host.routeAttempts.has(spare)).toBe(false); host.stop(); }); it("moves back to the preferred route on reconnect once it answers", async () => { const preferredState = { up: false }; const preferred = fakeT3(preferredState); const fallback = fakeT3(); const host = new T3Host({ id: ENVIRONMENT_ID, label: "Laptop", urls: [preferred, fallback], token: "x", environmentId: ENVIRONMENT_ID }); host.start(); await until(() => host.status === "online"); expect(host.url).toBe(fallback); preferredState.up = true; host.reconnect(); await until(() => host.status === "online" && host.url === preferred); expect(host.lastOnlineAt).not.toBeNull(); host.stop(); }); });