/** 照壁代理行为测试:头透传、SSE 流顺序、断线续传头、上游错误与不可达映射。 */ import http from "node:http"; import type { AddressInfo } from "node:net"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { buildServer } from "../src/server.js"; let upstream: http.Server; let upstreamUrl = ""; let upstreamCalls: Array<{ method: string; url: string; headers: http.IncomingHttpHeaders; body: string }> = []; beforeAll(async () => { upstream = http.createServer((req, res) => { let body = ""; req.on("data", (chunk) => (body += chunk)); req.on("end", () => { upstreamCalls.push({ method: req.method ?? "", url: req.url ?? "", headers: req.headers, body }); if (req.url === "/api/v1/echo") { res.writeHead(200, { "content-type": "application/json", "x-trace-id": String(req.headers["x-trace-id"] ?? "") }); res.end(JSON.stringify({ ok: true, auth: req.headers.authorization ?? null, body })); } else if (req.url === "/api/v1/stream") { res.writeHead(200, { "content-type": "text/event-stream; charset=utf-8" }); const frames = ["id: 1\nevent: run.started\n\n", "id: 2\nevent: llm.delta\n\n", "id: 3\nevent: run.finished\n\n"]; frames.forEach((frame, i) => setTimeout(() => res.write(frame), i * 20)); setTimeout(() => res.end(), frames.length * 20 + 10); } else if (req.url === "/api/v1/boom") { res.writeHead(404, { "content-type": "application/json" }); res.end(JSON.stringify({ error: { code: "HJ-1002", message: "不存在" } })); } else { res.writeHead(200, { "content-type": "application/json" }); res.end("{}"); } }); }); await new Promise((resolve) => upstream.listen(0, "127.0.0.1", resolve)); upstreamUrl = `http://127.0.0.1:${(upstream.address() as AddressInfo).port}`; }); afterAll(async () => { await new Promise((resolve) => upstream.close(() => resolve())); }); function app() { return buildServer({ port: 0, backendUrl: upstreamUrl, bodyLimit: 1024 * 1024, logLevel: "silent", }); } describe("照壁代理", () => { it("转发 Authorization 与 Idempotency-Key,透传响应体与 x-trace-id", async () => { const res = await app().inject({ method: "POST", url: "/api/v1/echo", headers: { authorization: "Bearer tok", "idempotency-key": "idem-1", "x-trace-id": "trace-9" }, payload: JSON.stringify({ a: 1 }), }); expect(res.statusCode).toBe(200); const body = res.json(); expect(body.ok).toBe(true); expect(body.auth).toBe("Bearer tok"); expect(body.body).toBe(JSON.stringify({ a: 1 })); expect(res.headers["x-trace-id"]).toBe("trace-9"); const call = upstreamCalls.at(-1)!; expect(call.headers["authorization"]).toBe("Bearer tok"); expect(call.headers["idempotency-key"]).toBe("idem-1"); }); it("SSE 流式透传:帧序完整、禁缓冲头、Last-Event-ID 透传", async () => { const res = await app().inject({ method: "GET", url: "/api/v1/stream", headers: { "last-event-id": "2", authorization: "Bearer tok" }, }); expect(res.statusCode).toBe(200); expect(res.headers["content-type"]).toContain("text/event-stream"); expect(res.headers["x-accel-buffering"]).toBe("no"); expect(res.body.indexOf("run.started")).toBeLessThan(res.body.indexOf("run.finished")); const call = upstreamCalls.at(-1)!; expect(call.headers["last-event-id"]).toBe("2"); }); it("上游错误状态与错误体原样透传", async () => { const res = await app().inject({ method: "GET", url: "/api/v1/boom" }); expect(res.statusCode).toBe(404); expect(res.json().error.code).toBe("HJ-1002"); }); it("上游不可达映射 HJ-3001", async () => { const dead = buildServer({ port: 0, backendUrl: "http://127.0.0.1:1", bodyLimit: 1024, logLevel: "silent", }); const res = await dead.inject({ method: "GET", url: "/api/v1/echo" }); expect(res.statusCode).toBe(502); expect(res.json().error.code).toBe("HJ-3001"); }); });