/** * 「一直同意」的跨进程持久化测试。 * * 这个功能的判据只有一条最要紧:**下一个进程还认不认**。 * 钩子一封(一次工具调用)一个进程,所以「记在内存里」等于没记。 */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, writeFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createFileGrantStore, grantsFilePath } from '../lib/grants-file.mjs'; const tmpFile = async () => join(await mkdtemp(join(tmpdir(), 'zc-grants-')), 'g.json'); test('★ 授权跨进程存活(新 store 读同一个文件仍认账)', async () => { const f = await tmpFile(); const s1 = createFileGrantStore(f); assert.equal(s1.isGranted('sess-1', 'Bash'), false); assert.equal(s1.grant('sess-1', 'Bash', '一直同意'), true); // 模拟下一个钩子进程 const s2 = createFileGrantStore(f); assert.equal(s2.isGranted('sess-1', 'Bash'), true); await rm(join(f, '..'), { recursive: true, force: true }); }); test('「同意」是单次,不落盘', async () => { const f = await tmpFile(); const s = createFileGrantStore(f); assert.equal(s.grant('sess-1', 'Bash', '同意'), false); assert.equal(createFileGrantStore(f).isGranted('sess-1', 'Bash'), false); await rm(join(f, '..'), { recursive: true, force: true }); }); test('★ 反向对照:同一个文件里,一直同意与单次同意必须分道扬镳', async () => { // 只翻转决策文本,落盘结果必须不同 —— 否则「什么都记下来」也会让上一条通过。 const f = await tmpFile(); const s = createFileGrantStore(f); assert.equal(s.grant('sess-a', 'Bash', '一直同意'), true); assert.equal(s.grant('sess-b', 'Bash', '同意'), false); const back = createFileGrantStore(f); assert.equal(back.isGranted('sess-a', 'Bash'), true); assert.equal(back.isGranted('sess-b', 'Bash'), false); await rm(join(f, '..'), { recursive: true, force: true }); }); test('授权按会话隔离,不跨会话泄漏', async () => { const f = await tmpFile(); const s = createFileGrantStore(f); s.grant('sess-1', 'Bash', '一直同意'); const back = createFileGrantStore(f); assert.equal(back.isGranted('sess-1', 'Bash'), true); assert.equal(back.isGranted('sess-2', 'Bash'), false); await rm(join(f, '..'), { recursive: true, force: true }); }); test('授权按工具隔离(bash 的免批不放行 write)', async () => { const f = await tmpFile(); const s = createFileGrantStore(f); s.grant('s', 'Bash', '一直同意'); const back = createFileGrantStore(f); assert.equal(back.isGranted('s', 'Bash'), true); assert.equal(back.isGranted('s', 'Write'), false); await rm(join(f, '..'), { recursive: true, force: true }); }); test('撤销会话后不再免批', async () => { const f = await tmpFile(); const s = createFileGrantStore(f); s.grant('s', 'Bash', '一直同意'); assert.equal(s.revokeSession('s'), true); assert.equal(createFileGrantStore(f).isGranted('s', 'Bash'), false); await rm(join(f, '..'), { recursive: true, force: true }); }); test('文件不存在或内容损坏都当空表,不抛错', async () => { const f = await tmpFile(); assert.equal(createFileGrantStore(f).isGranted('s', 'Bash'), false); await writeFile(f, '{ 这不是 JSON', 'utf8'); assert.equal(createFileGrantStore(f).isGranted('s', 'Bash'), false); await writeFile(f, '{"sessions":{"s":"not-an-array"}}', 'utf8'); assert.equal(createFileGrantStore(f).isGranted('s', 'Bash'), false); await rm(join(f, '..'), { recursive: true, force: true }); }); test('文件位置按 显式 > AGENTMAIL_CONFIG_DIR > ZCODE_PLUGIN_DATA > 家目录 解析', () => { const p = grantsFilePath({ AGENTMAIL_ZCODE_GRANTS_FILE: '/x/g.json', AGENTMAIL_CONFIG_DIR: '/c', ZCODE_PLUGIN_DATA: '/d' }); assert.equal(p, '/x/g.json'); assert.equal(grantsFilePath({ AGENTMAIL_CONFIG_DIR: '/c', ZCODE_PLUGIN_DATA: '/d' }), '/c/permission-grants.json'); assert.equal(grantsFilePath({ ZCODE_PLUGIN_DATA: '/d' }), '/d/permission-grants.json'); assert.match(grantsFilePath({}), /permission-grants\.json$/); });