155 lines
5.3 KiB
TypeScript
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",
|
|
});
|
|
});
|
|
});
|