103 lines
3.6 KiB
TypeScript
103 lines
3.6 KiB
TypeScript
/**
|
||
* v0.8.2 P0-2: 根 MEMORY.md 保护闸门(工具无关的路径形参数匹配)
|
||
*
|
||
* 锁定契约:
|
||
* - delete_file / file_move(source_path / destination_path)等旧名单遗漏的工具
|
||
* 对根 MEMORY.md 的操作被拦截(读/写/删/移动/改名任一方向)
|
||
* - 子目录 MEMORY.md 不受保护(H-5 语义保持)
|
||
* - 相对路径以 workspacePath 为基解析
|
||
* - MCP 工具(任意带路径形参数的工具)同样纳入
|
||
* - 非根 MEMORY.md 的路径不误伤
|
||
*/
|
||
|
||
import { describe, it, expect, vi } from 'vitest';
|
||
import { tmpdir } from 'os';
|
||
import { join } from 'path';
|
||
|
||
vi.mock('electron-log', () => ({
|
||
default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
||
}));
|
||
|
||
import { AgentLoopEngine } from '../engine';
|
||
|
||
const WORKSPACE = join(tmpdir(), 'metona-memory-gate-test');
|
||
const ROOT_MEMORY = join(WORKSPACE, 'MEMORY.md');
|
||
|
||
function makeEngine(): AgentLoopEngine {
|
||
const engine = new AgentLoopEngine({}, {
|
||
providerId: 'fake',
|
||
supportedModels: [],
|
||
supportsToolCalling: true,
|
||
supportsThinking: false,
|
||
send: vi.fn(),
|
||
sendStream: vi.fn(),
|
||
} as never);
|
||
engine.setWorkspacePath(WORKSPACE);
|
||
return engine;
|
||
}
|
||
|
||
/** 白盒调用(私有方法契约测试) */
|
||
function gate(engine: AgentLoopEngine, args: Record<string, unknown>): boolean {
|
||
return (
|
||
engine as unknown as {
|
||
isTargetingRootMemoryMd: (tc: { args: Record<string, unknown> }) => boolean;
|
||
}
|
||
).isTargetingRootMemoryMd({ args });
|
||
}
|
||
|
||
describe('根 MEMORY.md 保护闸门(P0-2)', () => {
|
||
it('delete_file / file_move(旧名单遗漏工具)→ 拦截', () => {
|
||
const engine = makeEngine();
|
||
expect(gate(engine, { file_path: ROOT_MEMORY })).toBe(true);
|
||
expect(gate(engine, { source_path: ROOT_MEMORY, destination_path: join(WORKSPACE, 'x') })).toBe(
|
||
true,
|
||
);
|
||
expect(
|
||
gate(engine, { source_path: join(WORKSPACE, 'note.md'), destination_path: ROOT_MEMORY }),
|
||
).toBe(true);
|
||
});
|
||
|
||
it('路径参数别名矩阵:path / filePath / destination / dir_path 均命中', () => {
|
||
const engine = makeEngine();
|
||
expect(gate(engine, { path: ROOT_MEMORY })).toBe(true);
|
||
expect(gate(engine, { filePath: ROOT_MEMORY })).toBe(true);
|
||
expect(gate(engine, { destination: ROOT_MEMORY })).toBe(true);
|
||
expect(gate(engine, { dir_path: ROOT_MEMORY })).toBe(true);
|
||
});
|
||
|
||
it('相对路径以工作空间为基解析 → 拦截', () => {
|
||
const engine = makeEngine();
|
||
expect(gate(engine, { file_path: 'MEMORY.md' })).toBe(true);
|
||
expect(gate(engine, { file_path: './MEMORY.md' })).toBe(true);
|
||
});
|
||
|
||
it('子目录 MEMORY.md 不受保护(H-5 语义)', () => {
|
||
const engine = makeEngine();
|
||
expect(gate(engine, { file_path: join(WORKSPACE, 'notes', 'MEMORY.md') })).toBe(false);
|
||
});
|
||
|
||
it('MCP 工具的路径形参数同样纳入(任意工具生效)', () => {
|
||
const engine = makeEngine();
|
||
expect(gate(engine, { target_path: ROOT_MEMORY, options: { recursive: true } })).toBe(true);
|
||
});
|
||
|
||
it('其他文件路径不误伤', () => {
|
||
const engine = makeEngine();
|
||
expect(gate(engine, { file_path: join(WORKSPACE, 'src', 'main.ts') })).toBe(false);
|
||
expect(gate(engine, { file_path: ROOT_MEMORY + '.bak' })).toBe(false);
|
||
expect(gate(engine, { command: 'echo hello' })).toBe(false);
|
||
});
|
||
|
||
it('未设置工作空间时闸门放行(无根可保护)', () => {
|
||
const engine = new AgentLoopEngine({}, {
|
||
providerId: 'fake',
|
||
supportedModels: [],
|
||
supportsToolCalling: true,
|
||
supportsThinking: false,
|
||
send: vi.fn(),
|
||
sendStream: vi.fn(),
|
||
} as never);
|
||
expect(gate(engine, { file_path: ROOT_MEMORY })).toBe(false);
|
||
});
|
||
});
|