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