Files
portal/tools/night/summary.test.mjs
T

127 lines
8.4 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { buildSummary, STATUS_WORD } from './summary.mjs';
import { FINAL_STATUSES } from './supervisor.mjs';
const base = { run_id: 'r-1', disk_free_gb: 42.5, workers: [], lines: [] };
describe('buildSummary', () => {
it('прогона не было вовсе — сводка всё равно есть и так и говорит', () => {
const s = buildSummary({ ...base, run_id: null });
expect(s.subject).toMatch(/сводка/i);
expect(s.text).toMatch(/прогона не было/i);
});
it('за прогон ничего не происходило — сводка всё равно есть', () => {
const s = buildSummary(base);
expect(s.text).toMatch(/дел не было/i);
});
it('всегда есть строка про свободное место', () => {
expect(buildSummary(base).text).toContain('42.5');
});
it('погашенный по восьми часам назван С МЕСТОМ (проверка 5)', () => {
const s = buildSummary({ ...base, workers: [{ worker_id: 'w-1', status: 'extinguished', reason: 'упёрся в восемь часов', at_item: '4.11' }] });
expect(s.text).toContain('w-1');
expect(s.text).toContain('4.11');
});
it('состояние работника напечатано ПО-РУССКИ: «погашен», а не extinguished', () => {
// 🔴 Владелец читает письмо глазами. Английское слово в письме — то же самое,
// что его отсутствие: он не поймёт, погас работник или сломался.
const s = buildSummary({ ...base, workers: [
{ worker_id: 'w-1', status: 'extinguished', at_item: '4.11' },
{ worker_id: 'w-2', status: 'stalled', at_item: '2.3' },
{ worker_id: 'w-3', status: 'paused', at_item: '1.1' },
] });
expect(s.text).toContain(STATUS_WORD.extinguished);
expect(s.text).toContain(STATUS_WORD.stalled);
expect(s.text).toContain(STATUS_WORD.paused);
expect(s.text).not.toContain('extinguished');
expect(s.text).not.toContain('stalled');
expect(s.text).not.toContain('paused');
});
// 🔴 СТОРОЖ КЛАССА, а не одного забытого слова. Он не перечисляет состояния руками —
// он берёт ИХ СОБСТВЕННЫЙ список у надзирателя (`FINAL_STATUSES`) и требует русское слово
// к каждому. Заведут завтра четвёртое состояние и опять забудут слово — эта проверка
// покраснеет сама, без единой правки. Проверка «одного слова» так не умеет: она стережёт
// ровно ту дыру, которую уже нашли, и слепа к следующей такой же.
it('КЛАСС: у КАЖДОГО итогового состояния работника есть русское слово', () => {
for (const status of FINAL_STATUSES) {
const slovo = STATUS_WORD[status];
expect(slovo, `состояние «${status}» завели, а русского слова к нему не дали — владелец прочитает английское`).toBeTruthy();
// 🪤 Мало «слово есть»: слово `born_dead` само себе тоже «есть». Латиница в ответе —
// признак, что состояние переписали английским, а не перевели.
expect(slovo, `слово состояния «${status}» написано латиницей — для владельца это пустой звук`).not.toMatch(/[A-Za-z]/);
}
});
it('мертворождённый назван по-русски, а не словом born_dead', () => {
// 🔴 Замерено живьём 03.08.2026: в таблице стояло «· w-4: born_dead — Работника не удалось…».
const s = buildSummary({ ...base, workers: [{ worker_id: 'w-4', status: 'born_dead', reason: 'Работника не удалось запустить.' }] });
expect(s.text).toContain(STATUS_WORD.born_dead);
expect(s.text).not.toContain('born_dead');
});
it('слово состояния незнакомое — печатается оно само, а не пустота', () => {
// Незнакомое слово тоже сведение: молча съесть его — соврать владельцу.
const s = buildSummary({ ...base, workers: [{ worker_id: 'w-1', status: 'что-то-новое' }] });
expect(s.text).toContain('что-то-новое');
expect(s.text).not.toMatch(/undefined/);
});
it('погашенный по «служба не ответила» назван этой причиной, а не часами (проверка 13)', () => {
const s = buildSummary({ ...base, workers: [{ worker_id: 'w-2', status: 'extinguished', reason: 'Погашен: служба не ответила.', at_item: '2.3' }] });
expect(s.text).toMatch(/служба не ответила/i);
expect(s.text).toContain('2.3');
});
it('по КАЖДОМУ работнику своя строка про деньги, права и видимое клиенту', () => {
const s = buildSummary({ ...base, workers: [
{ worker_id: 'w-1', touched: { money: true, rights: false, client_visible: false } },
{ worker_id: 'w-2', touched: { money: false, rights: false, client_visible: true } },
] });
const money = s.text.split('\n').filter(l => l.includes('деньги'));
expect(money.length).toBe(2);
});
it('строка «что трогали» говорит «по коммитам похоже», а не утверждает наверняка', () => {
const s = buildSummary({ ...base, workers: [{ worker_id: 'w-1', touched: { money: true, rights: false, client_visible: false } }] });
expect(s.text).toMatch(/по коммитам похоже/i);
expect(s.text).toMatch(/следов не видно/i);
expect(s.text).toMatch(/приблизительный/i); // признак честно назван приблизительным
});
it('надзиратель ничего про работника не записал — сводка говорит «не записано», а не «нет»', () => {
// 🪤 Прежде строка навсегда говорила бы «нет, нет, нет», потому что заполнять её было некому.
const s = buildSummary({ ...base, workers: [{ worker_id: 'w-1' }] });
expect(s.text).toMatch(/не записано/i);
});
it('готовые строки из ленты попадают в сводку без переписывания', () => {
const s = buildSummary({ ...base, lines: ['Убран рабочий угол работника w-1 (прогон r-1): задача не принята 7 дней.'] });
expect(s.text).toContain('Убран рабочий угол работника w-1');
});
it('сервер перезагружался — строка об этом есть', () => {
const s = buildSummary({ ...base, server_rebooted_at: Date.parse('2026-07-30T02:00:00+03:00') });
expect(s.text).toMatch(/перезагружался/i);
});
it('замер отдал ноль — это названо подозрительным, а не подано как правда', () => {
// 🪤 «Ноль почти всегда сбой» (замер 20.07.2026), но настоящий ноль означает кончившийся
// диск. Оба смысла названы вслух: тихо напечатать «0 ГБ» — соврать в обе стороны.
const s = buildSummary({ ...base, disk_free_gb: 0 });
expect(s.text).toMatch(/сбой замера/i);
expect(s.text).toMatch(/немедленно/i);
});
it('место замерить не удалось — так и сказано, а не «0 ГБ»', () => {
// Ноль почти всегда сбой, а не правда о мире: замер 20.07.2026.
const s = buildSummary({ ...base, disk_free_gb: null });
expect(s.text).toMatch(/замерить не удалось/i);
expect(s.text).not.toMatch(/0 ГБ/);
});
});