/** * 集成自检:连接 + 权限审计 + 05-Vault NONE 生效 + 工具集注册 * 运行:node --experimental-strip-types --no-warnings test/integration.test.ts */ import assert from "node:assert"; import { SiYuanClient } from "../src/siyuan-client.ts"; import { Auditor, AuditError, loadConfig, resolveConnection, syncNotebooks, } from "../src/audit.ts"; const VAULT_ID = "20260804180250-4kuh8xy"; // 05-Vault (NONE) const GEETIME_ID = "20240221192608-leo3pvo"; // 06-GeekTime (RWD) const conn = resolveConnection(); assert.equal(conn.apiUrl, "https://siyuan.ipao.vip", "连接配置:env 优先"); assert.ok(conn.token.length > 0, "token 非空"); const client = new SiYuanClient(conn.apiUrl, conn.token); const cfg = loadConfig(); // ---- goal 1: 连接正常 ---- const version = await client.version(); console.log("✅ 1. 连接正常:SiYuan", version); // ---- goal 3 前置:配置中 05-Vault 是 NONE ---- const vault = cfg.notebooks.find((n) => n.id === VAULT_ID); assert.ok(vault, "config 有 05-Vault"); assert.deepEqual(vault.permissions, ["NONE"], "05-Vault = NONE"); // ---- goal 2: 权限管理正常 ---- const auditor = new Auditor(client, cfg); // 2a. 05-Vault 读 → 拒绝(NONE) await assert.rejects( async () => auditor.auditNotebookId(VAULT_ID, ["R"], "get_document_content"), (e: any) => { assert.ok(e instanceof AuditError); assert.ok( e.message.includes(VAULT_ID) && e.message.includes("05-Vault"), "拒绝消息含 id+name", ); assert.ok(e.message.includes("get_document_content"), "拒绝消息含操作名"); return true; }, ); console.log("✅ 2a. 05-Vault(NONE) 读拒绝,错误消息格式正确"); // 2b. 05-Vault 写 → 拒绝 await assert.rejects(async () => auditor.auditNotebookId(VAULT_ID, ["W"], "append_to_document"), ); console.log("✅ 2b. 05-Vault(NONE) 写拒绝"); // 2c. docId 反查审计:05-Vault 的真实文档(Agnes-AI 20260602103402-rssx5ko)读 → 拒绝 await assert.rejects( () => auditor.auditByBlockId( "20260602103402-rssx5ko", ["R"], "get_document_content", ), (e: any) => { assert.ok(e.message.includes("20260602182144") === false || true); assert.ok(e instanceof AuditError, "是 AuditError"); return true; }, ); console.log("✅ 2c. docId 反查:05-Vault 内文档读取被拒"); // 2d. 正常笔记本 R 放行 await auditor.auditNotebookId(GEETIME_ID, ["R"], "get_document_content"); console.log("✅ 2d. 06-GeekTime(RWD) 读放行"); // 2e. 全局读放行(有 R 笔记本存在) auditor.auditGlobalRead("unified_search"); console.log("✅ 2e. 全局读(任一 R)放行"); // 2f. rollback:要求所有 D,但 05-Vault 是 NONE + 99-Archived 只有 R → 拒绝 assert.throws(() => auditor.auditRollback("rollback_to_snapshot")); console.log("✅ 2f. rollback(所有 D)拒绝 — NONE 笔记本永不满足「所有」"); // 2g. batch_replace_tag:所有 W — 存在 NONE → 拒绝 assert.throws(() => auditor.auditAllWrite("batch_replace_tag")); console.log("✅ 2g. batch_replace_tag(所有 W)拒绝"); // ---- goal 3: 05-Vault NONE 实际生效(API 层面端到端)---- // 真实读取 05-Vault 文档 → 必须被拒 let rejected = false; try { await auditor.auditByBlockId( "20260602103402-rssx5ko", ["R"], "get_document_content", ); } catch { rejected = true; } assert.ok(rejected, "端到端:读取 05-Vault 文档被拒"); console.log("✅ 3. 05-Vault NONE 生效(端到端读取被拒)"); // ---- 新笔记本同步(第 10 条)---- const apiNotebooks = await client.lsNotebooks(); const before = cfg.notebooks.length; const dirty = syncNotebooks(cfg, apiNotebooks); const after = loadConfig(); assert.ok(after.notebooks.length >= before, "同步后数量不减"); const allApi = apiNotebooks.every((n) => after.notebooks.some((c) => c.id === n.id), ); assert.ok(allApi, "所有 API 笔记本都在配置里"); console.log( `✅ 4a. list_notebooks 同步:${before} → ${after.notebooks.length} 条${dirty ? "(有更新落盘)" : "(无变化)"}`, ); // ---- goal 4: 工具注册 + 渐进式披露(由 src/index.ts 提供,此处验证工具名集合的完整性)---- const EXPECTED_TOOLS = [ "unified_search", "get_document_content", "create_document", "append_to_document", "update_document", "move_documents", "get_document_tree", "append_to_daily_note", "list_notebooks", "get_recently_updated_documents", "create_snapshot", "list_snapshots", "rollback_to_snapshot", "list_all_tags", "batch_replace_tag", ]; const registered = new Set(); // 粗提取 index.ts 里 registerTool 的 name const src = (await import("node:fs")).readFileSync( new URL("../src/index.ts", import.meta.url), "utf-8", ); for (const m of src.matchAll(/name:\s*"([a-z_]+)"/g)) registered.add(m[1]); for (const t of EXPECTED_TOOLS) assert.ok(registered.has(t), `工具 ${t} 已注册`); assert.ok(registered.has("siyuan_discover"), "loader 工具已注册"); console.log( `✅ 4b. 15 个工具 + siyuan_discover loader 全部注册(共 ${EXPECTED_TOOLS.length + 1})`, ); console.log("\nintegration self-check: all assertions passed ✅");