Files
mnote/scripts/task516-onlyoffice-bridge-multisession-browser-smoke.js
T
lix-2026 1882db7681 收口 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 .
2026-06-01 09:29:12 +08:00

195 lines
8.4 KiB
JavaScript

#!/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);
});