397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
3.3 KiB
JavaScript
59 lines
3.3 KiB
JavaScript
// tools/escape-grant-floor-escape.test.mjs
|
|
import { describe, it, expect } from 'vitest';
|
|
import { join } from 'node:path';
|
|
import { loadFloorEscapes } from './escape-grant.mjs';
|
|
import { signFloorEscapeRecord } from './askuser-answer-parser.mjs';
|
|
|
|
function memFs(seed = {}) {
|
|
const norm = (p) => String(p).replace(/\\/g, '/');
|
|
const s = new Map(Object.entries(seed).map(([k, v]) => [norm(k), v]));
|
|
return { s,
|
|
existsSync: (p) => s.has(norm(p)),
|
|
readFileSync: (p) => { const n = norm(p); if (!s.has(n)) { const e = new Error('ENOENT'); e.code = 'ENOENT'; throw e; } return s.get(n); } };
|
|
}
|
|
const DIR = '/rt'; const KEY = 'test-receipt-key'; const NOW = 1000;
|
|
const file = (recs) => ({ [join(DIR, 'askuser-decisions-s1.jsonl')]: recs.map((r) => JSON.stringify(r)).join('\n') + '\n' });
|
|
const signed = (action) => signFloorEscapeRecord({ type: 'floor_escape', action, ts: NOW }, KEY);
|
|
const unsigned = (action) => ({ type: 'floor_escape', action, ts: NOW });
|
|
|
|
describe('loadFloorEscapes — key-gated подпись', () => {
|
|
it('ключ есть → подписанный принят, неподписанный/битый отброшены', () => {
|
|
const fs = memFs(file([signed('bash:real'), unsigned('bash:forged'), { ...signed('bash:tampered'), action: 'bash:evil' }]));
|
|
const g = loadFloorEscapes('s1', NOW, { keyImpl: () => KEY, fsImpl: fs, runtimeDir: DIR });
|
|
expect(g.map((x) => x.action)).toEqual(['bash:real']);
|
|
});
|
|
it('ключ null → все приняты (backward-compat, content-floor backstop)', () => {
|
|
const fs = memFs(file([unsigned('bash:a'), signed('bash:b')]));
|
|
const g = loadFloorEscapes('s1', NOW, { keyImpl: () => null, fsImpl: fs, runtimeDir: DIR });
|
|
expect(g.map((x) => x.action).sort()).toEqual(['bash:a', 'bash:b']);
|
|
});
|
|
it("ключ '' (falsy) → трактуется как нет ключа → все приняты", () => {
|
|
const fs = memFs(file([unsigned('bash:a')]));
|
|
const g = loadFloorEscapes('s1', NOW, { keyImpl: () => '', fsImpl: fs, runtimeDir: DIR });
|
|
expect(g.map((x) => x.action)).toEqual(['bash:a']);
|
|
});
|
|
it('окно 5 мин и форма {action,ts} сохранены', () => {
|
|
const old = signFloorEscapeRecord({ type: 'floor_escape', action: 'bash:old', ts: NOW - 6 * 60 * 1000 }, KEY);
|
|
const fs = memFs(file([signed('bash:fresh'), old]));
|
|
const g = loadFloorEscapes('s1', NOW, { keyImpl: () => KEY, fsImpl: fs, runtimeDir: DIR });
|
|
expect(g).toEqual([{ action: 'bash:fresh', ts: NOW }]);
|
|
});
|
|
});
|
|
|
|
describe('loadFloorEscapes — быстрый путь (keychain только при наличии пропусков)', () => {
|
|
it('нет floor_escape-записей (только чужие) → [] без резолва ключа', () => {
|
|
let called = 0;
|
|
const fs = memFs(file([{ type: 'approve_git_operation', command: 'git push', ts: NOW }]));
|
|
const g = loadFloorEscapes('s1', NOW, { keyImpl: () => { called++; return KEY; }, fsImpl: fs, runtimeDir: DIR });
|
|
expect(g).toEqual([]);
|
|
expect(called).toBe(0);
|
|
});
|
|
it('файл отсутствует → [] без резолва ключа', () => {
|
|
let called = 0;
|
|
const fs = memFs({});
|
|
const g = loadFloorEscapes('s1', NOW, { keyImpl: () => { called++; return KEY; }, fsImpl: fs, runtimeDir: DIR });
|
|
expect(g).toEqual([]);
|
|
expect(called).toBe(0);
|
|
});
|
|
});
|