收口 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,188 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert");
|
||||
|
||||
const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
|
||||
|
||||
async function requestJson(path, init = {}) {
|
||||
const response = await fetch(`${BASE_URL}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-mnote-actor-id": "user_1",
|
||||
...(init.headers || {}),
|
||||
},
|
||||
});
|
||||
const text = await response.text();
|
||||
let payload = null;
|
||||
try {
|
||||
payload = text ? JSON.parse(text) : null;
|
||||
} catch (_) {
|
||||
payload = { raw: text };
|
||||
}
|
||||
return {
|
||||
status: response.status,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
function callPayload(sessionId, args, toolName = "mnote.onlyoffice.sheet.set_value") {
|
||||
return {
|
||||
toolName,
|
||||
workspaceId: "ws_demo",
|
||||
documentId: "doc_1",
|
||||
actorId: "user_1",
|
||||
sessionId,
|
||||
runId: `${sessionId}-run`,
|
||||
toolCallId: `${sessionId}-call`,
|
||||
traceId: `${sessionId}-trace`,
|
||||
idempotencyKey: `${sessionId}-idem`,
|
||||
dryRun: true,
|
||||
capabilityScope: ["office.write"],
|
||||
args,
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
const bridgeSessionId = `mnote-oo-http-${suffix}`;
|
||||
const bridgeToken = `token-${suffix}`;
|
||||
const register = await requestJson("/api/onlyoffice/bridge/session", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
sessionId: bridgeSessionId,
|
||||
token: bridgeToken,
|
||||
editorType: "cell",
|
||||
documentId: "doc_http",
|
||||
assetId: "asset_http_allowed",
|
||||
fileType: "xlsx",
|
||||
}),
|
||||
});
|
||||
assert.equal(register.status, 200, JSON.stringify(register));
|
||||
assert.equal(register.payload.sessionId, bridgeSessionId);
|
||||
|
||||
const implicit = await requestJson("/api/hermes/tools/mnote/call", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(callPayload("sess_http_implicit", {
|
||||
address: "A1",
|
||||
value: "after",
|
||||
})),
|
||||
});
|
||||
assert.equal(implicit.status, 400, JSON.stringify(implicit));
|
||||
assert.equal(implicit.headers["x-error-code"], "mnote_onlyoffice_session_explicit_required");
|
||||
|
||||
const forbidden = await requestJson("/api/hermes/tools/mnote/call", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(callPayload("sess_http_forbidden", {
|
||||
onlyofficeSessionId: bridgeSessionId,
|
||||
address: "A1",
|
||||
value: "after",
|
||||
aiAccessScope: {
|
||||
permissionLevel: "read_write",
|
||||
allowedResourceIds: ["asset_other"],
|
||||
},
|
||||
})),
|
||||
});
|
||||
assert.equal(forbidden.status, 403, JSON.stringify(forbidden));
|
||||
assert.equal(forbidden.headers["x-error-code"], "mnote_onlyoffice_resource_scope_forbidden");
|
||||
|
||||
const missingScope = await requestJson("/api/hermes/tools/mnote/call", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(callPayload("sess_http_missing_scope", {
|
||||
onlyofficeSessionId: bridgeSessionId,
|
||||
address: "A1",
|
||||
value: "after",
|
||||
})),
|
||||
});
|
||||
assert.equal(missingScope.status, 403, JSON.stringify(missingScope));
|
||||
assert.equal(missingScope.headers["x-error-code"], "mnote_onlyoffice_resource_scope_required");
|
||||
|
||||
const allowed = await requestJson("/api/hermes/tools/mnote/call", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(callPayload("sess_http_allowed", {
|
||||
onlyofficeSessionId: bridgeSessionId,
|
||||
address: "A1",
|
||||
value: "after",
|
||||
aiAccessScope: {
|
||||
permissionLevel: "read_write",
|
||||
allowedResourceIds: ["asset_http_allowed"],
|
||||
},
|
||||
})),
|
||||
});
|
||||
assert.equal(allowed.status, 200, JSON.stringify(allowed));
|
||||
assert.equal(allowed.payload.result.schema, "mnote.onlyoffice.action_plan.v1");
|
||||
assert.equal(allowed.payload.result.sessionId, bridgeSessionId);
|
||||
assert.equal(allowed.payload.result.action, "sheet.set_value");
|
||||
assert.equal(allowed.payload.audit.effect, "dry_run");
|
||||
|
||||
const currentImplicit = await requestJson("/api/hermes/tools/mnote/call", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(callPayload("sess_http_current_implicit", {
|
||||
aiAccessScope: {
|
||||
permissionLevel: "read",
|
||||
allowedResourceIds: ["asset_http_allowed"],
|
||||
},
|
||||
}, "mnote.onlyoffice.session.current")),
|
||||
});
|
||||
assert.equal(currentImplicit.status, 400, JSON.stringify(currentImplicit));
|
||||
assert.equal(currentImplicit.headers["x-error-code"], "mnote_onlyoffice_session_explicit_required");
|
||||
|
||||
const currentForbidden = await requestJson("/api/hermes/tools/mnote/call", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(callPayload("sess_http_current_forbidden", {
|
||||
onlyofficeSessionId: bridgeSessionId,
|
||||
aiAccessScope: {
|
||||
permissionLevel: "read",
|
||||
allowedResourceIds: ["asset_other"],
|
||||
},
|
||||
}, "mnote.onlyoffice.session.current")),
|
||||
});
|
||||
assert.equal(currentForbidden.status, 403, JSON.stringify(currentForbidden));
|
||||
assert.equal(currentForbidden.headers["x-error-code"], "mnote_onlyoffice_resource_scope_forbidden");
|
||||
|
||||
const currentAllowed = await requestJson("/api/hermes/tools/mnote/call", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(callPayload("sess_http_current_allowed", {
|
||||
onlyofficeSessionId: bridgeSessionId,
|
||||
aiAccessScope: {
|
||||
permissionLevel: "read",
|
||||
allowedResourceIds: ["asset_http_allowed"],
|
||||
},
|
||||
}, "mnote.onlyoffice.session.current")),
|
||||
});
|
||||
assert.equal(currentAllowed.status, 200, JSON.stringify(currentAllowed));
|
||||
assert.equal(currentAllowed.payload.result.schema, "mnote.onlyoffice.session.v1");
|
||||
assert.equal(currentAllowed.payload.result.session.sessionId, bridgeSessionId);
|
||||
assert.equal(currentAllowed.payload.result.session.assetId, "asset_http_allowed");
|
||||
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
task: "task515-onlyoffice-live-scope-http-smoke",
|
||||
baseUrl: BASE_URL,
|
||||
bridgeSessionId,
|
||||
implicit: { status: implicit.status, code: implicit.headers["x-error-code"] },
|
||||
forbidden: { status: forbidden.status, code: forbidden.headers["x-error-code"] },
|
||||
missingScope: { status: missingScope.status, code: missingScope.headers["x-error-code"] },
|
||||
allowed: {
|
||||
status: allowed.status,
|
||||
schema: allowed.payload.result.schema,
|
||||
action: allowed.payload.result.action,
|
||||
audit: allowed.payload.audit.effect,
|
||||
},
|
||||
currentImplicit: { status: currentImplicit.status, code: currentImplicit.headers["x-error-code"] },
|
||||
currentForbidden: { status: currentForbidden.status, code: currentForbidden.headers["x-error-code"] },
|
||||
currentAllowed: {
|
||||
status: currentAllowed.status,
|
||||
schema: currentAllowed.payload.result.schema,
|
||||
sessionId: currentAllowed.payload.result.session.sessionId,
|
||||
assetId: currentAllowed.payload.result.session.assetId,
|
||||
},
|
||||
}, null, 2));
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error && error.stack ? error.stack : error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user