169 lines
6.0 KiB
JavaScript
169 lines
6.0 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const assert = require("node:assert");
|
|
const {
|
|
fetchWithTimeout,
|
|
findFreePort,
|
|
startGateway,
|
|
waitForGateway,
|
|
} = require("./task114-rust-web-gateway-entry-smoke.js");
|
|
|
|
const EXTERNAL_BASE_URL = (process.env.MNOTE_UI_BASE_URL || "").replace(/\/+$/, "");
|
|
const CONFIGURED_DOCUMENT_ID = (process.env.MNOTE_DOCUMENT_ID || "").trim();
|
|
const CONFIGURED_WORKSPACE_ID = (process.env.MNOTE_WORKSPACE_ID || "").trim();
|
|
const CONFIGURED_TITLE = (process.env.MNOTE_DOCUMENT_TITLE || "").trim();
|
|
|
|
async function readJsonResponse(response, label) {
|
|
const text = await response.text();
|
|
let payload = null;
|
|
try {
|
|
payload = text ? JSON.parse(text) : null;
|
|
} catch {
|
|
throw new Error(`${label} 返回了非 JSON 内容: ${text.slice(0, 1200)}`);
|
|
}
|
|
assert(response.ok, `${label} 请求失败: ${response.status}; body: ${text.slice(0, 1200)}`);
|
|
return payload;
|
|
}
|
|
|
|
async function createTempDocument(baseUrl) {
|
|
const title = `task115-rust-web-${Date.now()}`;
|
|
const requestBody = {
|
|
action: "create",
|
|
title,
|
|
};
|
|
if (CONFIGURED_WORKSPACE_ID) {
|
|
requestBody.workspaceId = CONFIGURED_WORKSPACE_ID;
|
|
}
|
|
|
|
const response = await fetchWithTimeout(`${baseUrl}/api/tree/commands`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify(requestBody),
|
|
});
|
|
const payload = await readJsonResponse(response, "创建临时文档");
|
|
const result = payload && typeof payload.result === "object" ? payload.result : null;
|
|
const documentId = result && typeof result.documentId === "string" ? result.documentId : "";
|
|
const workspaceId = result && typeof result.workspaceId === "string" ? result.workspaceId : "";
|
|
assert(documentId, "创建临时文档缺少 documentId");
|
|
assert(workspaceId, "创建临时文档缺少 workspaceId");
|
|
return { documentId, workspaceId, title };
|
|
}
|
|
|
|
async function purgeTempDocument(baseUrl, target) {
|
|
const response = await fetchWithTimeout(`${baseUrl}/api/tree/commands`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
action: "purge",
|
|
workspaceId: target.workspaceId,
|
|
documentId: target.documentId,
|
|
}),
|
|
});
|
|
await readJsonResponse(response, "清理临时文档");
|
|
}
|
|
|
|
async function validateDocumentShell(baseUrl, target) {
|
|
const targetPath = `/documents/${encodeURIComponent(target.documentId)}?workspaceId=${encodeURIComponent(target.workspaceId)}`;
|
|
const response = await fetchWithTimeout(`${baseUrl}${targetPath}`);
|
|
const html = await response.text();
|
|
assert.equal(response.status, 200, `文档 shell 请求失败: ${response.status}; body: ${html.slice(0, 1200)}`);
|
|
assert.equal(response.headers.get("x-mnote-web-owner"), "mnote-web");
|
|
assert.equal(response.headers.get("x-mnote-web-shell"), "document");
|
|
assert.match(response.headers.get("content-type") || "", /text\/html/);
|
|
assert.match(html, /data-mnote-shell="document"/);
|
|
assert.match(html, /data-testid="wolai-sidebar"/);
|
|
assert.match(html, /data-testid="wolai-topbar"/);
|
|
assert.match(html, /data-testid="wolai-floating-ai"/);
|
|
assert.match(html, /data-page-aggregate-snapshot="mnote\.page_aggregate\.v1"/);
|
|
assert.match(html, /id="__MNOTE_PAGE_AGGREGATE__"/);
|
|
assert.match(html, /data-editor-host="leptos_tiptap_island"/);
|
|
assert.doesNotMatch(html, /mnote-web-document-shell/);
|
|
assert.doesNotMatch(html, /convex_upstream_error|未登录/);
|
|
|
|
const aggregate = await fetchWithTimeout(
|
|
`${baseUrl}/api/page-aggregate/${encodeURIComponent(target.documentId)}?workspaceId=${encodeURIComponent(target.workspaceId)}`,
|
|
);
|
|
const aggregateText = await aggregate.text();
|
|
assert.equal(aggregate.status, 200, `page aggregate 请求失败: ${aggregate.status}; body: ${aggregateText.slice(0, 1200)}`);
|
|
const payload = JSON.parse(aggregateText);
|
|
assert.equal(aggregate.headers.get("x-mnote-web-owner"), "mnote-web");
|
|
assert.equal(payload.owner, "mnote-web");
|
|
assert.equal(payload.schema, "mnote.page_aggregate.v1");
|
|
assert.equal(payload.result.identity.documentId, target.documentId);
|
|
assert.equal(payload.result.identity.workspaceId, target.workspaceId);
|
|
assert.equal(payload.result.schema, "mnote.page_aggregate.v1");
|
|
assert.equal(typeof payload.result.head.title, "string");
|
|
assert(payload.result.head.title.trim(), "Page Aggregate 标题不能为空");
|
|
if (target.expectedTitle) {
|
|
assert.equal(payload.result.head.title, target.expectedTitle);
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
let gateway = null;
|
|
let baseUrl = EXTERNAL_BASE_URL;
|
|
let createdTarget = null;
|
|
|
|
if (!baseUrl) {
|
|
const port = await findFreePort();
|
|
baseUrl = `http://127.0.0.1:${port}`;
|
|
gateway = startGateway(port);
|
|
await waitForGateway(baseUrl);
|
|
}
|
|
|
|
try {
|
|
const target = CONFIGURED_DOCUMENT_ID
|
|
? {
|
|
documentId: CONFIGURED_DOCUMENT_ID,
|
|
workspaceId: CONFIGURED_WORKSPACE_ID || "ws_demo",
|
|
expectedTitle: CONFIGURED_TITLE || null,
|
|
}
|
|
: gateway
|
|
? {
|
|
documentId: "doc_1",
|
|
workspaceId: "ws_demo",
|
|
expectedTitle: CONFIGURED_TITLE || "服务端页面",
|
|
}
|
|
: await createTempDocument(baseUrl);
|
|
if (!CONFIGURED_DOCUMENT_ID && !gateway) {
|
|
createdTarget = target;
|
|
target.expectedTitle = target.title;
|
|
}
|
|
|
|
await validateDocumentShell(baseUrl, target);
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
ok: true,
|
|
baseUrl,
|
|
owner: "mnote-web",
|
|
shell: "document",
|
|
editorHost: "leptos_tiptap_island",
|
|
documentId: target.documentId,
|
|
workspaceId: target.workspaceId,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
} finally {
|
|
if (createdTarget) {
|
|
await purgeTempDocument(baseUrl, createdTarget);
|
|
}
|
|
if (gateway && gateway.pid) {
|
|
gateway.kill("SIGTERM");
|
|
setTimeout(() => {
|
|
if (!gateway.killed) {
|
|
gateway.kill("SIGKILL");
|
|
}
|
|
}, 2000).unref();
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.stack || error.message : String(error));
|
|
process.exit(1);
|
|
});
|