import assert from 'node:assert/strict'; import test from 'node:test'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join, resolve } from 'node:path'; import { homedir } from 'node:os'; import { piMailFallback } from '../src/paths.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(HERE, '..'); const HOME = homedir(); /** * 这一组判据补的是一个**不对称**(pi 评审 2026-09-15): * 当初把 `piMailFallback` 造在共用的 `lib/workspace.js` 里时, * **四个桥的 `npm test` 全绿,只有 `check-shared-libs.sh` 抓得到** * —— 因为 `piMailFallback` 一条测试都没有,没有任何东西读它。 * * 为什么这个测试文件是**独立**的(不放进 `test/workspace.test.mjs`): * 那个文件在四个桥里**逐字节相同**(md5 一致,属于共用测试), * 往里加 pi 专有的断言会把共用测试也弄分叉 —— 与 `lib/` 是同一条规矩。 */ test('★ piMailFallback 落在 ~/.pi/mail-sessions/(不是 .dsh)', () => { // 目录名必须是 `.pi`:落进 `~/.dsh/` 会让人以为是 DSH 在干活。 const p = piMailFallback('abc-123'); assert.equal(p, join(HOME, '.pi', 'mail-sessions', 'abc-123')); // 反向断言:绝不能带 `.dsh`(这正是当初要分叉出来的原因,写死它防回归) assert.ok(!p.includes('.dsh'), `兜底目录不该落在 dsh 名下:${p}`); }); test('空 key 收敛成 default(而不是 undefined/literal 空串)', () => { // 缺失 session_id 时不能拼出 `.../undefined` 或 `.../`: // 那会让所有缺 key 的会话挤进同一个目录,且目录名像个 bug。 assert.equal(piMailFallback(undefined), join(HOME, '.pi', 'mail-sessions', 'default')); assert.equal(piMailFallback(''), join(HOME, '.pi', 'mail-sessions', 'default')); assert.equal(piMailFallback(null), join(HOME, '.pi', 'mail-sessions', 'default')); }); test('数字/非字符串 key 也收敛成字符串(不产生 NaN 路径)', () => { assert.equal(piMailFallback(42), join(HOME, '.pi', 'mail-sessions', '42')); }); test('★ 结构判据:平台兜底值住在 src/,不在共用的 lib/ 里', () => { // 这是这次分叉的**结构性**回归判据 —— 光测行为测不出"它住在哪"。 const pathsSrc = readFileSync(join(ROOT, 'src', 'paths.mjs'), 'utf8'); assert.match(pathsSrc, /export function piMailFallback/, '兜底函数应当在 src/paths.mjs'); const sharedWorkspace = readFileSync(join(ROOT, 'lib', 'workspace.js'), 'utf8'); assert.ok(!sharedWorkspace.includes('piMailFallback'), '共用 lib/workspace.js 里不该再有平台专有的 piMailFallback(它就是分叉的原因)'); assert.ok(!sharedWorkspace.includes("'.pi'"), '共用模块里不该出现 pi 的目录名常量'); // `.dsh` 仍留在共用模块里是**对的**:那是 dsh 自己的兜底值, // 也是共用模块保真的"唯一一份"。规矩保的是"只有一份",不是"不许有平台数据"。 assert.ok(sharedWorkspace.includes('mailSessionFallback'), '共用模块里 dsh 的兜底函数应当保留(它不是 pi 的,不该跟着搬)'); }); test('★ 父子同源:pool 与 worker 从同一个模块取兜底值', () => { // 为什么这条要紧:worker 的沙箱 rw 清单由**父进程**(pool.mjs)算。 // 父进程猜一个目录、worker 落在另一个 ⇒ 症状是"界内也写不了"。 // 所以两个文件必须 import 同一个函数,而不是各自拼一份。 for (const f of ['pool.mjs', 'worker.mjs']) { const src = readFileSync(join(ROOT, 'src', f), 'utf8'); assert.match(src, /import \{ piMailFallback \} from '\.\/paths\.mjs'/, `${f} 应当从 ./paths.mjs import piMailFallback`); // 反向:不能再从共用的 lib/workspace.js import 它 const workspaceImport = src.split('\n').find((l) => l.startsWith('import') && l.includes('workspace.js')); assert.ok(workspaceImport !== undefined, `${f} 仍应从 lib/workspace.js import 别的函数`); assert.ok(!workspaceImport.includes('piMailFallback'), `${f} 不该再从共用模块 import piMailFallback:${workspaceImport}`); } });