Files
brain/tools/skeleton-radar.test.mjs
T

50 lines
2.9 KiB
JavaScript

// tools/skeleton-radar.test.mjs
import { describe, it, expect } from 'vitest';
import { buildSkeletonRadar, REPORT_STATUSES } from './skeleton-radar.mjs';
describe('buildSkeletonRadar (§5.7 скелет-радар)', () => {
const headers = ['## Auth', '## Billing', '## Webhooks'];
it('каждый заголовок → пункт; непрорепорченный → not-reported', () => {
const r = buildSkeletonRadar({ headers, reports: { '## Auth': 'touched' } });
expect(r.items).toHaveLength(3);
expect(r.items.find((i) => i.header === '## Auth').status).toBe('touched');
expect(r.items.find((i) => i.header === '## Billing').status).toBe('not-reported');
expect(r.unreported.map((i) => i.header).sort()).toEqual(['## Billing', '## Webhooks']);
});
it('REPORT_STATUSES заморожен и содержит 4 статуса', () => {
expect(Object.isFrozen(REPORT_STATUSES)).toBe(true);
expect(REPORT_STATUSES).toEqual(['touched', 'not-relevant', 'not-read', 'not-reported']);
});
it('невалидный статус в reports → not-reported (fail-safe)', () => {
const r = buildSkeletonRadar({ headers: ['## A'], reports: { '## A': 'выдумка' } });
expect(r.items[0].status).toBe('not-reported');
});
it('битые входы → пусто, не крашит', () => {
expect(buildSkeletonRadar({ headers: null }).items).toEqual([]);
expect(buildSkeletonRadar({}).unreported).toEqual([]);
});
// F-F3: пустой скелет виден — summary.headers=0 (зеркало F-F1 graph-radar)
it('F-F3: summary {headers, unreported}; пустой скелет = headers:0', () => {
const r = buildSkeletonRadar({ headers, reports: { '## Auth': 'touched' } });
expect(r.summary).toEqual({ headers: 3, unreported: 2 });
const empty = buildSkeletonRadar({ headers: [] });
expect(empty.summary).toEqual({ headers: 0, unreported: 0 });
});
// F-F4: items заморожены — машинная мутация статуса бросает TypeError (L2-пол)
it('F-F4: элементы items заморожены, мутация status бросает TypeError', () => {
const r = buildSkeletonRadar({ headers: ['## A'], reports: {} });
expect(r.items.every((i) => Object.isFrozen(i))).toBe(true);
expect(() => { r.items[0].status = 'touched'; }).toThrow(TypeError);
});
// F-F5 (характеризация, без RED — SE9): дубликаты заголовков сохраняются оба,
// делят один ключ отчёта (направление fail-safe — пункты не теряются)
it('F-F5: дубликаты заголовков → оба пункта, один статус на ключ', () => {
const r = buildSkeletonRadar({ headers: ['## A', '## A'], reports: { '## A': 'touched' } });
expect(r.items).toHaveLength(2);
expect(r.items.every((i) => i.status === 'touched')).toBe(true);
});
});