import { createServer } from "node:http"; import { Readable } from "node:stream"; import type { AddressInfo } from "node:net"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { HttpClient } from "../src/http/client"; const readBody = async (stream: NodeJS.ReadableStream): Promise => { const chunks: Buffer[] = []; for await (const chunk of stream) { chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); } return Buffer.concat(chunks); }; describe("HttpClient integration", () => { const requests: Array<{ url: string; method: string; headers: Record; body: Buffer; }> = []; const server = createServer((req, res) => { void (async () => { const body = await readBody(req); requests.push({ url: req.url ?? "", method: req.method ?? "", headers: req.headers, body, }); if (req.url?.startsWith("/json")) { res.writeHead(200, { "content-type": "application/json", "x-request-id": "req-json" }); res.end(JSON.stringify({ ok: true })); return; } if (req.url === "/stream") { res.writeHead(200, { "content-type": "text/event-stream" }); res.end('data: {"answer":"hello"}\n\ndata: {"delta":" world"}\n\n'); return; } if (req.url === "/bytes") { res.writeHead(200, { "content-type": "application/octet-stream" }); res.end(Buffer.from([1, 2, 3, 4])); return; } if (req.url === "/upload-stream") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ received: body.toString("utf8") })); return; } res.writeHead(404, { "content-type": "application/json" }); res.end(JSON.stringify({ message: "not found" })); })(); }); let client: HttpClient; beforeAll(async () => { await new Promise((resolve) => { server.listen(0, "127.0.0.1", () => resolve()); }); const address = server.address() as AddressInfo; client = new HttpClient({ apiKey: "test-key", baseUrl: `http://127.0.0.1:${address.port}`, maxRetries: 0, retryDelay: 0, }); }); afterAll(async () => { await new Promise((resolve, reject) => { server.close((error) => { if (error) { reject(error); return; } resolve(); }); }); }); it("uses real fetch for query serialization and json bodies", async () => { const response = await client.request({ method: "POST", path: "/json", query: { tag_ids: ["a", "b"], limit: 2 }, data: { user: "u" }, }); expect(response.requestId).toBe("req-json"); expect(response.data).toEqual({ ok: true }); expect(requests.at(-1)).toMatchObject({ url: "/json?tag_ids=a&tag_ids=b&limit=2", method: "POST", }); expect(requests.at(-1)?.headers.authorization).toBe("Bearer test-key"); expect(requests.at(-1)?.headers["content-type"]).toBe("application/json"); expect(requests.at(-1)?.body.toString("utf8")).toBe(JSON.stringify({ user: "u" })); }); it("supports streaming request bodies with duplex fetch", async () => { const response = await client.request<{ received: string }>({ method: "POST", path: "/upload-stream", data: Readable.from(["hello ", "world"]), }); expect(response.data).toEqual({ received: "hello world" }); expect(requests.at(-1)?.body.toString("utf8")).toBe("hello world"); }); it("parses real sse responses into text", async () => { const stream = await client.requestStream({ method: "GET", path: "/stream", }); await expect(stream.toText()).resolves.toBe("hello world"); }); it("parses real byte responses into buffers", async () => { const response = await client.request({ method: "GET", path: "/bytes", responseType: "bytes", }); expect(Array.from(response.data.values())).toEqual([1, 2, 3, 4]); }); });