397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
46 lines
3.2 KiB
JavaScript
46 lines
3.2 KiB
JavaScript
// tools/mentor-journal.test.mjs
|
|
import { describe, it, expect } from 'vitest';
|
|
import { appendNegotiation, verifyNegotiation, roundCount } from './mentor-journal.mjs';
|
|
|
|
const KEY = 'test-key';
|
|
|
|
describe('mentor-journal (§6.3 tamper-evident)', () => {
|
|
it('append + verify цепь цела', () => {
|
|
let s = { entries: [], headSig: null };
|
|
s = appendNegotiation(s.entries, { taskId: 'task:a', round: 1, side: 'mentor', utterance: 'расширь план', justification: 'риск X' }, { key: KEY, nowMs: 1 });
|
|
s = appendNegotiation(s.entries, { taskId: 'task:a', round: 1, side: 'claude', utterance: 'не согласен', justification: 'Y покрывает' }, { key: KEY, nowMs: 2 });
|
|
expect(verifyNegotiation(s.entries, s.headSig, { key: KEY }).ok).toBe(true);
|
|
});
|
|
it('подмена записи задним числом → verify false', () => {
|
|
let s = { entries: [], headSig: null };
|
|
s = appendNegotiation(s.entries, { taskId: 'task:a', round: 1, side: 'mentor', utterance: 'A', justification: 'j' }, { key: KEY, nowMs: 1 });
|
|
s = appendNegotiation(s.entries, { taskId: 'task:a', round: 2, side: 'mentor', utterance: 'B', justification: 'j' }, { key: KEY, nowMs: 2 });
|
|
s.entries[0].payload.utterance = 'ПОДМЕНА';
|
|
expect(verifyNegotiation(s.entries, s.headSig, { key: KEY }).ok).toBe(false);
|
|
});
|
|
it('обоснование обязательно (ДР-6) — пустое → throw', () => {
|
|
expect(() => appendNegotiation([], { taskId: 'task:a', round: 1, side: 'mentor', utterance: 'X', justification: '' }, { key: KEY, nowMs: 1 }))
|
|
.toThrow(/обоснование/);
|
|
});
|
|
it('roundCount считает максимальный круг для task-id', () => {
|
|
let s = { entries: [], headSig: null };
|
|
s = appendNegotiation(s.entries, { taskId: 'task:a', round: 1, side: 'mentor', utterance: 'A', justification: 'j' }, { key: KEY, nowMs: 1 });
|
|
s = appendNegotiation(s.entries, { taskId: 'task:a', round: 2, side: 'claude', utterance: 'B', justification: 'j' }, { key: KEY, nowMs: 2 });
|
|
s = appendNegotiation(s.entries, { taskId: 'task:b', round: 1, side: 'mentor', utterance: 'C', justification: 'j' }, { key: KEY, nowMs: 3 });
|
|
expect(roundCount(s.entries, 'task:a')).toBe(2);
|
|
expect(roundCount(s.entries, 'task:b')).toBe(1);
|
|
});
|
|
it('F-C2 (sharp-edges): пустой taskId → throw (запись вне per-task учёта запрещена, R2-SE-d)', () => {
|
|
expect(() => appendNegotiation([], { taskId: '', round: 1, side: 'mentor', utterance: 'X', justification: 'j' }, { key: KEY, nowMs: 1 }))
|
|
.toThrow(/taskId/);
|
|
expect(() => appendNegotiation([], { taskId: ' ', round: 1, side: 'claude', utterance: 'X', justification: 'j' }, { key: KEY, nowMs: 1 }))
|
|
.toThrow(/taskId/);
|
|
});
|
|
it('F-C7 (sharp-edges): side вне closed-list mentor|claude → throw', () => {
|
|
expect(() => appendNegotiation([], { taskId: 'task:a', round: 1, side: 'судья', utterance: 'X', justification: 'j' }, { key: KEY, nowMs: 1 }))
|
|
.toThrow(/side/);
|
|
expect(() => appendNegotiation([], { taskId: 'task:a', round: 1, side: '', utterance: 'X', justification: 'j' }, { key: KEY, nowMs: 1 }))
|
|
.toThrow(/side/);
|
|
});
|
|
});
|