M0:照壁 BFF 骨架(Fastify 透明代理:鉴权/幂等/续传头透传、SSE 直通、HJ-3001 映射)及测试与镜像
This commit is contained in:
1 parent
f3058a64c3
commit
8d8d81e757
10 files changed
+2700
No files matched your search
@@ -0,0 +1,101 @@
|
||||
/** 照壁代理行为测试:头透传、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<void>((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<void>((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");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user