import { createServer, type IncomingMessage, type ServerResponse, } from "node:http"; import type { AddressInfo } from "node:net"; import { afterEach, describe, expect, it } from "vitest"; import { WebSocketServer } from "ws"; import { HttpClient } from "../../src/_shared/http.js"; import { WsConnection } from "../../src/_shared/websocket.js"; import { ConflictError, TimeoutError } from "../../src/exceptions.js"; interface CapturedRequest { method?: string; url?: string; headers: IncomingMessage["headers"]; body: string; } function readRequestBody(request: IncomingMessage): Promise { return new Promise((resolve, reject) => { let body = ""; request.setEncoding("utf8"); request.on("data", (chunk) => { body += chunk; }); request.on("end", () => resolve(body)); request.on("error", reject); }); } function listen(server: ReturnType): Promise { return new Promise((resolve) => { server.listen(0, "127.0.0.1", () => { const address = server.address() as AddressInfo; resolve(address.port); }); }); } function closeHttpServer( server: ReturnType, ): Promise { return new Promise((resolve, reject) => { server.close((error) => { if (error) reject(error); else resolve(); }); }); } function closeWebSocketServer(server: WebSocketServer): Promise { return new Promise((resolve, reject) => { server.close((error) => { if (error) reject(error); else resolve(); }); }); } describe("foundation integration", () => { const cleanup: Array<() => Promise> = []; afterEach(async () => { await Promise.all(cleanup.splice(0).map((close) => close())); }); it("sends JSON requests through a real HTTP server", async () => { let captured: CapturedRequest | undefined; const server = createServer(async (request, response: ServerResponse) => { captured = { method: request.method, url: request.url, headers: request.headers, body: await readRequestBody(request), }; response.writeHead(200, { "Content-Type": "application/json" }); response.end(JSON.stringify({ ok: true })); }); const port = await listen(server); cleanup.push(() => closeHttpServer(server)); const client = new HttpClient({ baseUrl: `http://127.0.0.1:${port}`, apiKey: "api-key", token: "jwt-token", hostToken: "host-token", }); await expect( client.post( "/v1/foundation", { hello: "world" }, { params: { page: 1 } }, ), ).resolves.toEqual({ ok: true }); expect(captured).toMatchObject({ method: "POST", url: "/v1/foundation?page=1", body: JSON.stringify({ hello: "world" }), }); expect(captured?.headers["content-type"]).toBe("application/json"); expect(captured?.headers.accept).toBe("application/json"); expect(captured?.headers.authorization).toBe("Bearer jwt-token"); expect(captured?.headers["x-api-key"]).toBe("api-key"); expect(captured?.headers["x-host-token"]).toBe("host-token"); }); it("maps real HTTP error responses to SDK errors", async () => { const server = createServer((_request, response) => { response.writeHead(409, { "Content-Type": "application/json" }); response.end( JSON.stringify({ error: { code: "capsule_busy", message: "Capsule is busy" }, }), ); }); const port = await listen(server); cleanup.push(() => closeHttpServer(server)); const client = new HttpClient({ baseUrl: `http://127.0.0.1:${port}` }); await expect(client.get("/v1/error")).rejects.toMatchObject({ code: "capsule_busy", message: "Capsule is busy", statusCode: 409, }); await expect(client.get("/v1/error")).rejects.toBeInstanceOf(ConflictError); }); it("aborts real HTTP requests using timeoutMs", async () => { const server = createServer((_request, response) => { setTimeout(() => { response.writeHead(200, { "Content-Type": "application/json" }); response.end(JSON.stringify({ ok: true })); }, 200); }); const port = await listen(server); cleanup.push(() => closeHttpServer(server)); const client = new HttpClient({ baseUrl: `http://127.0.0.1:${port}` }); await expect( client.get("/v1/slow", { timeoutMs: 10 }), ).rejects.toBeInstanceOf(TimeoutError); }); it("exchanges JSON messages through a real WebSocket server", async () => { const server = new WebSocketServer({ port: 0, host: "127.0.0.1" }); cleanup.push(() => closeWebSocketServer(server)); const receivedByServer = new Promise((resolve) => { server.on("connection", (socket, request) => { expect(request.headers["x-api-key"]).toBe("api-key"); expect(request.headers["x-host-token"]).toBe("host-token"); socket.on("message", (raw) => resolve(JSON.parse(raw.toString()))); socket.send(JSON.stringify({ type: "ready" })); }); }); await new Promise((resolve) => server.once("listening", resolve)); const address = server.address() as AddressInfo; const messages: unknown[] = []; const connection = await WsConnection.connect({ baseUrl: `http://127.0.0.1:${address.port}`, path: "/v1/ws", apiKey: "api-key", hostToken: "host-token", onMessage: (message) => messages.push(message), }); connection.send({ type: "start" }); await expect(receivedByServer).resolves.toEqual({ type: "start" }); expect(messages).toEqual([{ type: "ready" }]); expect(connection.isClosed).toBe(false); connection.close(); }); });