397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
5.0 KiB
JavaScript
91 lines
5.0 KiB
JavaScript
// 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);
|
||
});
|
||
});
|