feat(editor): save leptos island and page aggregate alignment progress
- switch main document flow toward leptos tiptap island host and generated runtime assets - align page aggregate loading, page head single-source updates, and AI tool result recovery - add tests and smoke scripts for title sync, AI route recovery, and editor host cutover
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mockBuildMnoteWebForwardHeaders = vi.fn();
|
||||
const mockBuildMnoteWebStreamUrl = vi.fn();
|
||||
const mockDocumentBridgeErrorResponse = vi.fn((error: unknown) =>
|
||||
Response.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 },
|
||||
),
|
||||
);
|
||||
|
||||
vi.mock("@/lib/server/mnote-web", () => ({
|
||||
buildMnoteWebForwardHeaders: mockBuildMnoteWebForwardHeaders,
|
||||
buildMnoteWebStreamUrl: mockBuildMnoteWebStreamUrl,
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/documents/bridge", () => ({
|
||||
documentBridgeErrorResponse: mockDocumentBridgeErrorResponse,
|
||||
}));
|
||||
|
||||
describe("/api/mnote-web/stream route", () => {
|
||||
beforeEach(() => {
|
||||
mockBuildMnoteWebForwardHeaders.mockReset();
|
||||
mockBuildMnoteWebStreamUrl.mockReset();
|
||||
mockDocumentBridgeErrorResponse.mockClear();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("透传上游 SSE 并去掉 set-cookie", async () => {
|
||||
const upstreamHeaders = new Headers({
|
||||
"content-type": "text/event-stream; charset=utf-8",
|
||||
"cache-control": "no-cache",
|
||||
"set-cookie": "secret=1",
|
||||
});
|
||||
const upstreamResponse = new Response("event: snapshot\ndata: {\"ok\":true}\n\n", {
|
||||
status: 200,
|
||||
headers: upstreamHeaders,
|
||||
});
|
||||
|
||||
mockBuildMnoteWebForwardHeaders.mockResolvedValue(new Headers({ cookie: "a=1" }));
|
||||
mockBuildMnoteWebStreamUrl.mockReturnValue(
|
||||
new URL("http://127.0.0.1:3104/api/stream/events?stream=workspace&projection=sidebar_tree&workspaceId=ws_1"),
|
||||
);
|
||||
|
||||
const fetchMock = vi.fn(async () => upstreamResponse);
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const { GET } = await import("./route");
|
||||
const response = await GET(
|
||||
new Request("http://127.0.0.1:3000/api/mnote-web/stream?workspaceId=ws_1", {
|
||||
method: "GET",
|
||||
headers: { cookie: "a=1" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"http://127.0.0.1:3104/api/stream/events?stream=workspace&projection=sidebar_tree&workspaceId=ws_1",
|
||||
expect.objectContaining({
|
||||
method: "GET",
|
||||
cache: "no-store",
|
||||
redirect: "follow",
|
||||
headers: expect.any(Headers),
|
||||
}),
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("content-type")).toContain("text/event-stream");
|
||||
expect(response.headers.get("set-cookie")).toBeNull();
|
||||
await expect(response.text()).resolves.toContain("event: snapshot");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { documentBridgeErrorResponse } from "@/lib/documents/bridge";
|
||||
import {
|
||||
buildMnoteWebForwardHeaders,
|
||||
buildMnoteWebStreamUrl,
|
||||
} from "@/lib/server/mnote-web";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const requestUrl = new URL(request.url);
|
||||
const workspaceId = String(requestUrl.searchParams.get("workspaceId") || "").trim();
|
||||
const cursor = String(requestUrl.searchParams.get("cursor") || "").trim() || null;
|
||||
|
||||
if (!workspaceId) {
|
||||
return Response.json({ error: "缺少 workspaceId" }, { status: 400 });
|
||||
}
|
||||
|
||||
const targetUrl = buildMnoteWebStreamUrl({ workspaceId, cursor });
|
||||
const headers = await buildMnoteWebForwardHeaders(request);
|
||||
headers.set("accept", "text/event-stream");
|
||||
headers.set("x-mnote-workspace-id", workspaceId);
|
||||
|
||||
const upstream = await fetch(targetUrl.toString(), {
|
||||
method: "GET",
|
||||
headers,
|
||||
cache: "no-store",
|
||||
redirect: "follow",
|
||||
});
|
||||
|
||||
const responseHeaders = new Headers(upstream.headers);
|
||||
responseHeaders.delete("set-cookie");
|
||||
responseHeaders.set("cache-control", "no-store");
|
||||
|
||||
return new Response(upstream.body, {
|
||||
status: upstream.status,
|
||||
statusText: upstream.statusText,
|
||||
headers: responseHeaders,
|
||||
});
|
||||
} catch (error) {
|
||||
return documentBridgeErrorResponse(error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user