Files
portal/tools/round-control.test.mjs
T

74 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest';
import { codeRoundCheck, ROUND_OUTCOMES, decideRoundOutcome, runRoundControl } from './round-control.mjs';
import { addQuestion, answerQuestion } from './question-slots.mjs';
describe('codeRoundCheck ($0 — скил вызван по журналу + нет повисших вопросов)', () => {
it('всё закрыто → ok', () => {
let s = addQuestion([], { id: 'q1', text: 't' });
s = answerQuestion(s, 'q1', 'a');
const r = codeRoundCheck({ slots: s, invokedSkills: ['brainstorming'], requiredSkills: ['brainstorming'] });
expect(r.ok).toBe(true); expect(r.reasons).toEqual([]);
});
it('повисший вопрос → not ok + причина', () => {
const s = addQuestion([], { id: 'q1', text: 't' });
const r = codeRoundCheck({ slots: s, invokedSkills: [], requiredSkills: [] });
expect(r.ok).toBe(false); expect(r.reasons.some((x) => /повис/i.test(x))).toBe(true);
});
it('требуемый скил не вызван (журнал) → not ok + причина', () => {
const r = codeRoundCheck({ slots: [], invokedSkills: [], requiredSkills: ['writing-plans'] });
expect(r.ok).toBe(false); expect(r.reasons.some((x) => /не вызван/i.test(x))).toBe(true);
});
});
describe('decideRoundOutcome (§3А — 3 прохода → владелец; двойной терминатор)', () => {
it('код+смысл сошлись → proceed', () => {
expect(decideRoundOutcome({ codeOk: true, routerFaithful: true }).outcome).toBe('proceed');
});
it('пробел и проходы не исчерпаны → soft-return', () => {
expect(decideRoundOutcome({ codeOk: false, routerFaithful: true, roundCount: 1, maxRounds: 3 }).outcome).toBe('soft-return');
});
it('пробел и проходы исчерпаны → escalate-owner', () => {
expect(decideRoundOutcome({ codeOk: true, routerFaithful: false, roundCount: 3, maxRounds: 3 }).outcome).toBe('escalate-owner');
});
it('ROUND_OUTCOMES — закрытый список', () => {
expect(ROUND_OUTCOMES).toEqual(['proceed', 'soft-return', 'escalate-owner']);
});
});
describe('decideRoundOutcome — консервативность при битом счётчике (аудит F4)', () => {
it('невалидный roundCount (отриц) при пробеле → escalate-owner (не вечный loop)', () => {
expect(decideRoundOutcome({ codeOk: false, routerFaithful: true, roundCount: -1 }).outcome).toBe('escalate-owner');
});
it('невалидный roundCount (NaN) при пробеле → escalate-owner', () => {
expect(decideRoundOutcome({ codeOk: false, routerFaithful: true, roundCount: NaN }).outcome).toBe('escalate-owner');
});
it('битый roundCount, но круг сошёлся → всё равно proceed', () => {
expect(decideRoundOutcome({ codeOk: true, routerFaithful: true, roundCount: NaN }).outcome).toBe('proceed');
});
});
describe('runRoundControl (оркестратор: код + роутер-верность смысла + решение)', () => {
const okSlots = () => { let s = addQuestion([], { id: 'q1', text: 't' }); return answerQuestion(s, 'q1', 'a'); };
it('код ok + смысл верен → proceed', async () => {
const r = await runRoundControl({ slots: okSlots(), invokedSkills: ['b'], requiredSkills: ['b'], verityCall: async () => ({ ok: true, faithful: true }) });
expect(r.outcome).toBe('proceed'); expect(r.routerFaithful).toBe(true);
});
it('код ok, смысл искажён (faithful=false) → soft-return (есть проходы)', async () => {
const r = await runRoundControl({ slots: okSlots(), invokedSkills: ['b'], requiredSkills: ['b'], roundCount: 0, verityCall: async () => ({ ok: true, faithful: false }) });
expect(r.outcome).toBe('soft-return');
});
it('повисший вопрос (код not ok) → soft-return даже при верном смысле', async () => {
const s = addQuestion([], { id: 'q1', text: 't' });
const r = await runRoundControl({ slots: s, verityCall: async () => ({ ok: true, faithful: true }) });
expect(r.outcome).toBe('soft-return'); expect(r.codeCheck.ok).toBe(false);
});
it('сбой verityCall → routerFaithful=false (безопасно, пробел)', async () => {
const r = await runRoundControl({ slots: okSlots(), invokedSkills: ['b'], requiredSkills: ['b'], verityCall: async () => { throw new Error('net'); } });
expect(r.routerFaithful).toBe(false); expect(r.outcome).not.toBe('proceed');
});
it('пробел и проходы исчерпаны → escalate-owner', async () => {
const r = await runRoundControl({ slots: okSlots(), invokedSkills: ['b'], requiredSkills: ['b'], roundCount: 3, maxRounds: 3, verityCall: async () => ({ ok: true, faithful: false }) });
expect(r.outcome).toBe('escalate-owner');
});
});