Files
oc_session/tui-plugins/readmcpcfg.test.mjs
T
ypshidifu 6e0ed1743a TUI插件v9:修复dirname导入缺失致自愈静默失效,新增reload前mcp配置推送兜底(双保险)
- 补回 node:path 的 dirname 导入:healShadowPath 引用它,缺导入时
  ReferenceError 被自身 catch 吞掉,影子路径自愈静默失效
- 新增 pushMcpConfig:swap+flag 之后、reload 之前把活配置 mcp 推进
  运行中 OC(SDK config.update → body 形参 → experimental 三连),
  推送成功+reload 双保险,失败降级原单通道
- toast 区分双保险/单通道文案;log/toast 版本标识升级 v9
- 单测新增 S0 回归断言:源文件 path 导入必须含 dirname
  (prelude 复制的导入曾掩盖源码缺失,15/15 假绿 -> 16/16)
2026-09-23 15:31:55 +08:00

106 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// readMcpCfg 单测:从 oc-session-all.js 源文件动态提取解析区块来测,
// 保证测的是源码本体(不是复制的逻辑)。
// 场景:1) 键值格式 2) 旧三行格式+自动迁移 3) 路径失效自愈
import { readFileSync, writeFileSync, mkdtempSync, existsSync, rmSync, mkdirSync } from "node:fs"
import { tmpdir } from "node:os"
import { join, dirname, basename } from "node:path"
const SRC = join(import.meta.dirname, "oc-session-all.js")
const text = readFileSync(SRC, "utf8")
// 抽取 CFG_TXT 定义到 activeHasMcp 之前的全部函数(含 ON_FLAG/writeMcpCfg/heal/readMcpCfg)
const start = text.indexOf("const CFG_TXT =")
const end = text.indexOf("function activeHasMcp")
if (start < 0 || end < 0 || end < start) {
console.error("EXTRACT_FAIL: markers not found")
process.exit(1)
}
const block = text.slice(start, end)
const work = mkdtempSync(join(tmpdir(), "mcpcfg-test-"))
const LOG_DIR = work
const CFG_TXT = join(work, "mcpcfg.txt")
const ON_FLAG = join(work, "mcp.on.flag")
function log(m) {
/* silent in tests */
}
const moduleSrc = `
import { existsSync, readFileSync, writeFileSync, readdirSync } from "node:fs"
import { join, basename, dirname } from "node:path"
const LOG_DIR = ${JSON.stringify(LOG_DIR)}
function log(m) {}
${block}
export { readMcpCfg, parseMode, writeMcpCfg }
`
const modPath = join(work, "extracted.mjs")
writeFileSync(modPath, moduleSrc, "utf8")
const { pathToFileURL } = await import("node:url")
const { readMcpCfg } = await import(pathToFileURL(modPath).href)
let pass = 0
let fail = 0
function check(name, cond, extra) {
if (cond) {
pass++
console.log("PASS " + name)
} else {
fail++
console.log("FAIL " + name + (extra ? " | " + extra : ""))
}
}
// ---- 回归:源文件 path 导入必须含 dirname ----
// healShadowPath 用了 dirname;源文件缺导入时 ReferenceError 会被它自己的
// catch 吞掉 → 影子自愈静默失效。prelude 里复制的导入会掩盖这个缺失
// (曾导致 15/15 假绿),所以必须对源文件本体断言。
const pathImportLine = (text.match(/^import\s*\{[^}]*\}\s*from\s*["']path["']/m) || [""])[0]
check(
"S0 源文件 path 导入含 dirname",
/dirname/.test(pathImportLine),
"import line=" + pathImportLine,
)
// ---- 场景1:键值格式(注释+中文冒号)----
const active = join(work, "opencode.json")
const shadow = join(work, "opencode.json.2")
writeFileSync(active, JSON.stringify({ mcp: { servers: { a: {} } }, tag: "on" }))
writeFileSync(shadow, JSON.stringify({ tag: "quiet" }))
writeFileSync(
CFG_TXT,
"# 注释行\n模式: 自动\n含MCP的配置: " + active + "\n安静配置: " + shadow + "\n",
"utf8",
)
let r = readMcpCfg()
check("S1 mode=auto", r.mode === "auto", "got=" + r.mode)
check("S1 active识别", r.activePath === active, "got=" + r.activePath)
check("S1 无error", !r.error, "err=" + r.error)
check("S1 不迁移(已是键值)", !r.migrated, "migrated=" + r.migrated)
// ---- 场景2:旧三行格式 -> 解析 + 自动迁移写回 ----
writeFileSync(CFG_TXT, "手动\n" + active + "\n" + shadow + "\n", "utf8")
r = readMcpCfg()
check("S2 mode=manual", r.mode === "manual", "got=" + r.mode)
check("S2 active识别", r.activePath === active, "got=" + r.activePath)
check("S2 迁移写回", existsSync(CFG_TXT) && readFileSync(CFG_TXT, "utf8").includes("模式:"), "no kv rewrite")
check("S2 迁移后路径保留", readFileSync(CFG_TXT, "utf8").includes(shadow), "shadow lost")
check("S2 再读仍正确", readMcpCfg().mode === "manual" && !readMcpCfg().error)
// ---- 场景3:真实 bug 复现 —— 含MCP行指向的备份文件被删(.1 不存在)----
const gone = join(work, "opencode.json.1")
writeFileSync(CFG_TXT, "模式: 自动\n含MCP的配置: " + gone + "\n安静配置: " + active + "\n", "utf8")
r = readMcpCfg()
check("S3 自愈后无error", !r.error, "err=" + r.error)
check("S3 healed=true", !!r.healed, "healed=" + r.healed)
check("S3 active=活文件", r.activePath === active, "ap=" + r.activePath)
check("S3 影子=存在的互补文件", r.shadowPath === shadow || r.quietPath === shadow || r.onPath === shadow,
"shadow=" + r.shadowPath + " on=" + r.onPath + " quiet=" + r.quietPath)
const rewritten = readFileSync(CFG_TXT, "utf8")
check("S3 写回含存在的影子路径", rewritten.includes(shadow), "rewritten=" + rewritten.replace(/\n/g, "|"))
check("S3 写回后二次读取仍OK", (() => { const r2 = readMcpCfg(); return !r2.error && r2.activePath === active })(),
"reread=" + JSON.stringify({ e: readMcpCfg().error }))
rmSync(work, { recursive: true, force: true })
console.log(`\nRESULT: ${pass} passed, ${fail} failed`)
process.exit(fail ? 1 : 0)