74 lines
4.9 KiB
JavaScript
74 lines
4.9 KiB
JavaScript
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');
|
||
});
|
||
});
|