// tools/floor-signer.test.mjs — Пакет 3 (Блок 2): подписант GREEN + occurrence. import { describe, it, expect } from 'vitest'; import { signGreen, verifyGreen, acceptGreen } from './floor-signer.mjs'; const KEY = 'signer-secret-key-xyz'; describe('floor-signer: signGreen (подпись GREEN в домене M5_GREEN)', () => { it('подписывает payload и возвращает запись с 64-hex sig', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, KEY); expect(rec.criterion_id).toBe('c1'); expect(rec.code_fingerprint).toBe('fp1'); expect(rec.occurrence).toBe(1); expect(rec.sig).toMatch(/^[0-9a-f]{64}$/); }); it('без ключа подписанта возвращает null (fail-closed)', () => { expect(signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, null)).toBe(null); expect(signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, '')).toBe(null); }); it('подпись пригодна только в домене M5_GREEN (verifyGreen true)', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, KEY); expect(verifyGreen(rec, KEY)).toBe(true); }); }); describe('floor-signer: verifyGreen (целостность + привязка к criterion_id)', () => { it('подделка criterion_id после подписи аннулирует проверку', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, KEY); const forged = { ...rec, criterion_id: 'c2' }; expect(verifyGreen(forged, KEY)).toBe(false); }); it('подделка occurrence после подписи аннулирует проверку', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, KEY); const forged = { ...rec, occurrence: 2 }; expect(verifyGreen(forged, KEY)).toBe(false); }); it('чужой ключ не проходит проверку', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, KEY); expect(verifyGreen(rec, 'other-key')).toBe(false); }); it('неподписанная запись (нет sig) не проходит (fail-closed)', () => { expect(verifyGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, KEY)).toBe(false); }); }); describe('floor-signer: acceptGreen (occurrence монотонный, анти-пере-зачёт)', () => { it('свежий GREEN с occurrence > lastOccurrence принимается', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 5 }, KEY); const r = acceptGreen(rec, KEY, { lastOccurrence: 4 }); expect(r.accepted).toBe(true); }); it('тот же GREEN с разным criterion_id без нового occurrence отклоняется (Пакет 3 критерий)', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 5 }, KEY); const swapped = { ...rec, criterion_id: 'c2' }; const r = acceptGreen(swapped, KEY, { lastOccurrence: 4 }); expect(r.accepted).toBe(false); expect(r.reason).toBe('bad-signature'); }); it('пере-зачёт того же occurrence отклоняется (occurrence <= lastOccurrence)', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 5 }, KEY); const r = acceptGreen(rec, KEY, { lastOccurrence: 5 }); expect(r.accepted).toBe(false); expect(r.reason).toBe('stale-occurrence'); }); it('occurrence ниже последнего отклоняется', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 3 }, KEY); const r = acceptGreen(rec, KEY, { lastOccurrence: 5 }); expect(r.accepted).toBe(false); expect(r.reason).toBe('stale-occurrence'); }); it('нецелый / неположительный occurrence отклоняется', () => { const bad = [0, -1, 1.5, 'x', null]; for (const occ of bad) { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: occ }, KEY); const r = acceptGreen(rec, KEY, { lastOccurrence: 0 }); expect(r.accepted).toBe(false); expect(r.reason).toBe('bad-occurrence'); } }); it('подделка GREEN без ключа подписанта не принимается', () => { const forged = { criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 5, sig: 'a'.repeat(64) }; const r = acceptGreen(forged, KEY, { lastOccurrence: 0 }); expect(r.accepted).toBe(false); expect(r.reason).toBe('bad-signature'); }); it('lastOccurrence по умолчанию 0 (первый GREEN occurrence=1 принимается)', () => { const rec = signGreen({ criterion_id: 'c1', code_fingerprint: 'fp1', occurrence: 1 }, KEY); const r = acceptGreen(rec, KEY); expect(r.accepted).toBe(true); }); });