Files
mnote/wolai-frontend/src/app/api/tree/runtime/reduce/route.test.ts
T

155 lines
5.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const mockResolveMnoteWebInternalUrl = vi.fn();
const mockBuildForwardHeaders = vi.fn();
const mockFetch = vi.fn();
vi.mock("@/lib/mnote-web/internal-url", () => ({
resolveMnoteWebInternalUrl: (...args: unknown[]) => mockResolveMnoteWebInternalUrl(...args),
}));
vi.mock("@/lib/server/forward-headers", () => ({
buildForwardHeaders: (...args: unknown[]) => mockBuildForwardHeaders(...args),
}));
describe("/api/tree/runtime/reduce route", () => {
beforeEach(() => {
vi.resetModules();
mockResolveMnoteWebInternalUrl.mockReset();
mockBuildForwardHeaders.mockReset();
mockFetch.mockReset();
vi.stubGlobal("fetch", mockFetch);
});
it("通过 3000 同源 POST 代理到 mnote-web runtime reduce,并原样转发 JSON body 与来源 header", async () => {
mockResolveMnoteWebInternalUrl.mockResolvedValue("http://127.0.0.1:3104");
mockBuildForwardHeaders.mockResolvedValue(
new Headers({
cookie: "session=abc",
authorization: "Bearer test-token",
}),
);
mockFetch.mockResolvedValue(
new Response(JSON.stringify({ ok: true, revision: 7 }), {
status: 200,
statusText: "OK",
headers: {
"content-type": "application/json; charset=utf-8",
"set-cookie": "debug=1",
connection: "keep-alive",
"transfer-encoding": "chunked",
"x-upstream": "mnote-web-runtime",
},
}),
);
const { POST } = await import("./route");
const body = JSON.stringify({
workspaceId: "ws_body",
op: "tree.node.rename",
payload: { documentId: "doc_1", title: "新标题" },
});
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/runtime/reduce?workspaceId=ws_query", {
method: "POST",
headers: {
"content-type": "application/json",
accept: "application/json",
cookie: "session=abc",
},
body,
}),
);
expect(mockFetch).toHaveBeenCalledWith(
"http://127.0.0.1:3104/api/tree/runtime/reduce?workspaceId=ws_query",
expect.objectContaining({
method: "POST",
headers: expect.any(Headers),
body,
cache: "no-store",
redirect: "manual",
}),
);
const fetchHeaders = mockFetch.mock.calls[0]?.[1]?.headers as Headers;
expect(fetchHeaders.get("cookie")).toBe("session=abc");
expect(fetchHeaders.get("authorization")).toBe("Bearer test-token");
expect(fetchHeaders.get("content-type")).toBe("application/json");
expect(fetchHeaders.get("accept")).toBe("application/json");
expect(fetchHeaders.get("x-mnote-source-channel")).toBe("next_tree_runtime_reduce_proxy");
expect(fetchHeaders.get("x-mnote-source-client")).toBe("wolai-frontend");
expect(fetchHeaders.get("x-mnote-workspace-id")).toBe("ws_query");
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toContain("application/json");
expect(response.headers.get("cache-control")).toBe("no-store");
expect(response.headers.get("set-cookie")).toBeNull();
expect(response.headers.get("connection")).toBeNull();
expect(response.headers.get("transfer-encoding")).toBeNull();
expect(response.headers.get("x-upstream")).toBe("mnote-web-runtime");
expect(await response.json()).toEqual({ ok: true, revision: 7 });
});
it("没有 workspaceId query 时使用 x-mnote-workspace-id header fallback", async () => {
mockResolveMnoteWebInternalUrl.mockResolvedValue("http://127.0.0.1:3104");
mockBuildForwardHeaders.mockResolvedValue(
new Headers({
"x-mnote-workspace-id": "ws_header",
}),
);
mockFetch.mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 202,
headers: {
"content-type": "application/json",
},
}),
);
const { POST } = await import("./route");
await POST(
new Request("http://127.0.0.1:3000/api/tree/runtime/reduce", {
method: "POST",
headers: {
"content-type": "application/json",
"x-mnote-workspace-id": "ws_header",
},
body: JSON.stringify({ op: "noop" }),
}),
);
expect(mockFetch).toHaveBeenCalledWith(
"http://127.0.0.1:3104/api/tree/runtime/reduce",
expect.objectContaining({
method: "POST",
}),
);
const fetchHeaders = mockFetch.mock.calls[0]?.[1]?.headers as Headers;
expect(fetchHeaders.get("x-mnote-workspace-id")).toBe("ws_header");
});
it("上游调用异常时返回 502 JSON", async () => {
mockResolveMnoteWebInternalUrl.mockResolvedValue("http://127.0.0.1:3104");
mockBuildForwardHeaders.mockResolvedValue(new Headers());
mockFetch.mockRejectedValue(new Error("runtime reduce unavailable"));
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/runtime/reduce", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({ op: "noop" }),
}),
);
expect(response.status).toBe(502);
expect(response.headers.get("cache-control")).toBe("no-store");
expect(await response.json()).toEqual({
error: "runtime reduce unavailable",
});
});
});