收口 MNote P0 P1 P2 审查尾项
- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目 - 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线 - 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径 验证: - cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1 - cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1 - git diff --check - git diff --cached --check - codegraph index . --force && codegraph status . - codegraph sync . && codegraph status .
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert");
|
||||
const { chromium } = require("playwright");
|
||||
|
||||
const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
|
||||
const UI_TIMEOUT_MS = Number(process.env.MNOTE_UI_TIMEOUT_MS || 45000);
|
||||
|
||||
async function main() {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
const consoleErrors = [];
|
||||
const networkFailures = [];
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") consoleErrors.push(message.text());
|
||||
});
|
||||
page.on("requestfailed", (request) => {
|
||||
networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" });
|
||||
});
|
||||
|
||||
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
||||
const result = await page.evaluate(async () => {
|
||||
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
const sessionA = `mnote-oo-browser-a-${suffix}`;
|
||||
const sessionB = `mnote-oo-browser-b-${suffix}`;
|
||||
const tokenA = `token-a-${suffix}`;
|
||||
const tokenB = `token-b-${suffix}`;
|
||||
|
||||
async function postJson(path, payload) {
|
||||
const response = await fetch(path, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const body = await response.json().catch(() => null);
|
||||
return { status: response.status, body };
|
||||
}
|
||||
|
||||
async function getJson(path) {
|
||||
const response = await fetch(path);
|
||||
const body = response.status === 204 ? null : await response.json().catch(() => null);
|
||||
return { status: response.status, body };
|
||||
}
|
||||
|
||||
const registeredA = await postJson("/api/onlyoffice/bridge/session", {
|
||||
sessionId: sessionA,
|
||||
token: tokenA,
|
||||
editorType: "word",
|
||||
documentId: "doc_a",
|
||||
assetId: "asset_a",
|
||||
fileType: "docx",
|
||||
docKey: "doc_key_a",
|
||||
pageOrigin: location.origin,
|
||||
});
|
||||
const registeredB = await postJson("/api/onlyoffice/bridge/session", {
|
||||
sessionId: sessionB,
|
||||
token: tokenB,
|
||||
editorType: "word",
|
||||
documentId: "doc_b",
|
||||
assetId: "asset_b",
|
||||
fileType: "docx",
|
||||
docKey: "doc_key_b",
|
||||
pageOrigin: location.origin,
|
||||
});
|
||||
const wrongToken = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenB)}&timeoutMs=1`);
|
||||
|
||||
const commandA = await postJson("/api/onlyoffice/bridge/commands", {
|
||||
sessionId: sessionA,
|
||||
token: tokenA,
|
||||
action: "document.insert_text",
|
||||
payload: { text: "A" },
|
||||
});
|
||||
const commandB = await postJson("/api/onlyoffice/bridge/commands", {
|
||||
sessionId: sessionB,
|
||||
token: tokenB,
|
||||
action: "document.insert_text",
|
||||
payload: { text: "B" },
|
||||
});
|
||||
|
||||
const nextA = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&timeoutMs=1000`);
|
||||
const nextB = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionB)}&token=${encodeURIComponent(tokenB)}&timeoutMs=1000`);
|
||||
|
||||
const postedA = await postJson("/api/onlyoffice/bridge/results", {
|
||||
sessionId: sessionA,
|
||||
token: tokenA,
|
||||
id: nextA.body && nextA.body.id,
|
||||
ok: true,
|
||||
result: { text: "result-a" },
|
||||
});
|
||||
const postedB = await postJson("/api/onlyoffice/bridge/results", {
|
||||
sessionId: sessionB,
|
||||
token: tokenB,
|
||||
id: nextB.body && nextB.body.id,
|
||||
ok: true,
|
||||
result: { text: "result-b" },
|
||||
});
|
||||
|
||||
const resultA = await getJson(`/api/onlyoffice/bridge/results?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&commandId=${encodeURIComponent(nextA.body && nextA.body.id)}&timeoutMs=1000`);
|
||||
const resultB = await getJson(`/api/onlyoffice/bridge/results?sessionId=${encodeURIComponent(sessionB)}&token=${encodeURIComponent(tokenB)}&commandId=${encodeURIComponent(nextB.body && nextB.body.id)}&timeoutMs=1000`);
|
||||
const sessions = await getJson("/api/onlyoffice/bridge/sessions");
|
||||
const currentBeforeClose = await getJson("/api/onlyoffice/bridge/session/current");
|
||||
const closeA = await postJson("/api/onlyoffice/bridge/session/close", {
|
||||
sessionId: sessionA,
|
||||
token: tokenA,
|
||||
});
|
||||
const nextAAfterClose = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&timeoutMs=1`);
|
||||
const sessionsAfterClose = await getJson("/api/onlyoffice/bridge/sessions");
|
||||
|
||||
return {
|
||||
sessionA,
|
||||
sessionB,
|
||||
registeredA,
|
||||
registeredB,
|
||||
wrongToken,
|
||||
commandA,
|
||||
commandB,
|
||||
nextA,
|
||||
nextB,
|
||||
postedA,
|
||||
postedB,
|
||||
resultA,
|
||||
resultB,
|
||||
sessions,
|
||||
currentBeforeClose,
|
||||
closeA,
|
||||
nextAAfterClose,
|
||||
sessionsAfterClose,
|
||||
};
|
||||
});
|
||||
|
||||
assert.equal(result.registeredA.status, 200, JSON.stringify(result.registeredA));
|
||||
assert.equal(result.registeredB.status, 200, JSON.stringify(result.registeredB));
|
||||
assert.equal(result.registeredA.body.docKey, "doc_key_a");
|
||||
assert.equal(result.registeredB.body.docKey, "doc_key_b");
|
||||
assert.equal(result.registeredA.body.pageOrigin, BASE_URL);
|
||||
assert.equal(result.registeredB.body.pageOrigin, BASE_URL);
|
||||
assert.equal(result.wrongToken.status, 401, JSON.stringify(result.wrongToken));
|
||||
assert.equal(result.commandA.status, 200, JSON.stringify(result.commandA));
|
||||
assert.equal(result.commandB.status, 200, JSON.stringify(result.commandB));
|
||||
assert.equal(result.nextA.status, 200, JSON.stringify(result.nextA));
|
||||
assert.equal(result.nextB.status, 200, JSON.stringify(result.nextB));
|
||||
assert.equal(result.nextA.body.action, "document.insert_text");
|
||||
assert.equal(result.nextB.body.action, "document.insert_text");
|
||||
assert.equal(result.nextA.body.payload.text, "A");
|
||||
assert.equal(result.nextB.body.payload.text, "B");
|
||||
assert.notEqual(result.nextA.body.id, result.nextB.body.id);
|
||||
assert.equal(result.postedA.status, 200, JSON.stringify(result.postedA));
|
||||
assert.equal(result.postedB.status, 200, JSON.stringify(result.postedB));
|
||||
assert.equal(result.resultA.status, 200, JSON.stringify(result.resultA));
|
||||
assert.equal(result.resultB.status, 200, JSON.stringify(result.resultB));
|
||||
assert.equal(result.resultA.body.result.text, "result-a");
|
||||
assert.equal(result.resultB.body.result.text, "result-b");
|
||||
const sessionARecord = result.sessions.body.sessions.find((session) => session.sessionId === result.sessionA);
|
||||
const sessionBRecord = result.sessions.body.sessions.find((session) => session.sessionId === result.sessionB);
|
||||
assert(sessionARecord, JSON.stringify(result.sessions));
|
||||
assert(sessionBRecord, JSON.stringify(result.sessions));
|
||||
assert.equal(sessionARecord.docKey, "doc_key_a");
|
||||
assert.equal(sessionBRecord.pageOrigin, BASE_URL);
|
||||
assert.equal(result.currentBeforeClose.status, 200, JSON.stringify(result.currentBeforeClose));
|
||||
assert(result.currentBeforeClose.body.session, JSON.stringify(result.currentBeforeClose));
|
||||
assert.equal(result.closeA.status, 200, JSON.stringify(result.closeA));
|
||||
assert.equal(result.closeA.body.closed, true, JSON.stringify(result.closeA));
|
||||
assert.equal(result.nextAAfterClose.status, 401, JSON.stringify(result.nextAAfterClose));
|
||||
assert(!result.sessionsAfterClose.body.sessions.some((session) => session.sessionId === result.sessionA));
|
||||
assert(result.sessionsAfterClose.body.sessions.some((session) => session.sessionId === result.sessionB));
|
||||
|
||||
await browser.close();
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
task: "task516-onlyoffice-bridge-multisession-browser-smoke",
|
||||
baseUrl: BASE_URL,
|
||||
sessionA: result.sessionA,
|
||||
sessionB: result.sessionB,
|
||||
wrongToken: { status: result.wrongToken.status, code: result.wrongToken.body && result.wrongToken.body.code },
|
||||
nextA: { id: result.nextA.body.id, text: result.nextA.body.payload.text },
|
||||
nextB: { id: result.nextB.body.id, text: result.nextB.body.payload.text },
|
||||
resultA: result.resultA.body.result,
|
||||
resultB: result.resultB.body.result,
|
||||
currentBeforeClose: {
|
||||
status: result.currentBeforeClose.status,
|
||||
sessionId: result.currentBeforeClose.body.session && result.currentBeforeClose.body.session.sessionId,
|
||||
},
|
||||
closeA: result.closeA.body,
|
||||
consoleErrors,
|
||||
networkFailures,
|
||||
}, null, 2));
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error && error.stack ? error.stack : error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user