Files
brain/tools/mentor-journal-store.test.mjs
T

41 lines
2.5 KiB
JavaScript

// tools/mentor-journal-store.test.mjs
import { describe, it, expect } from 'vitest';
import { loadMentorJournal, persistMentorJournal, loadMentorVerdict, persistMentorVerdict } from './mentor-journal-store.mjs';
function memFs(files = {}) {
return {
files,
readFileSync(p) { if (!(p in files)) { const e = new Error('ENOENT'); e.code = 'ENOENT'; throw e; } return files[p]; },
writeFileSync(p, d) { files[p] = String(d); },
renameSync(a, b) { files[b] = files[a]; delete files[a]; },
};
}
describe('mentor-journal-store (F-C2-4 персист цепи + вердикта)', () => {
it('нет файлов → {entries:[], headSig:null} / null-вердикт', () => {
const fs = memFs();
expect(loadMentorJournal({ sessionId: 's1', runtimeDir: '/r', fsImpl: fs })).toEqual({ entries: [], headSig: null });
expect(loadMentorVerdict({ sessionId: 's1', runtimeDir: '/r', fsImpl: fs })).toBe(null);
});
it('persist → load круговая (журнал: jsonl+head; вердикт: json)', () => {
const fs = memFs();
const journal = { entries: [{ seq: 1, ts: 5, payload: { task_id: 't', round: 1, side: 'mentor', utterance: 'u', justification: 'j' }, prev_hash: '0'.repeat(64), chain_hash: 'h1' }], headSig: 'SIG' };
persistMentorJournal({ journal, sessionId: 's1', runtimeDir: '/r', fsImpl: fs });
expect(loadMentorJournal({ sessionId: 's1', runtimeDir: '/r', fsImpl: fs })).toEqual({ entries: journal.entries, headSig: 'SIG' });
const v = { ok: true, wired: true, planHash: 'PH', verdict: { recommendation: 'r' } };
persistMentorVerdict({ record: v, sessionId: 's1', runtimeDir: '/r', fsImpl: fs });
expect(loadMentorVerdict({ sessionId: 's1', runtimeDir: '/r', fsImpl: fs })).toEqual(v);
});
it('atomic: после persist временных файлов не остаётся', () => {
const fs = memFs();
persistMentorVerdict({ record: { ok: false }, sessionId: 's1', runtimeDir: '/r', fsImpl: fs });
persistMentorJournal({ journal: { entries: [], headSig: null }, sessionId: 's1', runtimeDir: '/r', fsImpl: fs });
expect(Object.keys(fs.files).some((k) => k.includes('.tmp'))).toBe(false);
});
it('битый sessionId → throw (path-injection guard N3)', () => {
const fs = memFs();
expect(() => loadMentorJournal({ sessionId: '../x', runtimeDir: '/r', fsImpl: fs })).toThrow();
expect(() => persistMentorVerdict({ record: {}, sessionId: 'a/b', runtimeDir: '/r', fsImpl: fs })).toThrow();
});
});