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

168 lines
10 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 { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { freeGb, diskState, porogMesta, GB_NA_ODIN_UGOL } from './disk.mjs';
import { REPO_ROOT } from './paths.mjs';
import { tolkoKod } from './schyot-zovushchih.mjs';
describe('freeGb — замер живьём', () => {
it('на живой машине отдаёт положительное число', async () => {
const gb = await freeGb(process.cwd());
expect(typeof gb).toBe('number');
expect(gb).toBeGreaterThan(0);
});
it('каталога нет вовсе — не выдумываем число, отдаём null', async () => {
// 🪤 Ноль почти всегда сбой, а не правда о мире. Ноль здесь означал бы «места нет»,
// а правда другая: мы не смогли посмотреть.
expect(await freeGb('Z:\\такого-каталога-нет-никогда')).toBeNull();
});
});
describe('diskState — можно ли работать', () => {
it('места больше порога — работать можно', () => {
expect(diskState(50, 5)).toEqual({ ok: true, free_gb: 50, min_gb: 5, reason: null });
});
it('места ровно порог — уже нельзя: порог это черта, а не «можно до неё»', () => {
const s = diskState(5, 5);
expect(s.ok).toBe(false);
expect(s.reason).toMatch(/место/i);
});
it('места меньше порога — нельзя, и причина названа человеку понятно', () => {
const s = diskState(1.5, 5);
expect(s.ok).toBe(false);
expect(s.reason).toContain('1.5');
expect(s.reason).toContain('5');
});
it('замерить не удалось — считаем, что нельзя, а не «наверное хватит»', () => {
const s = diskState(null, 5);
expect(s.ok).toBe(false);
expect(s.free_gb).toBeNull();
expect(s.reason).toMatch(/замерить не удалось/i);
});
});
describe('diskState — ПОРОГА НЕ ДАЛИ (дыра 3)', () => {
// 🔴 Раньше правило читало «настройку потеряли» как «можно»: `50 <= undefined` — ложь,
// и место пропускало что угодно. Ни одна проверка этого не ловила: `diskState` во всех
// проверках звался с порогом-числом, случая «порога нет» не было ни одного.
// Вырежи ветку про порог в disk.mjs — покраснеет весь этот раздел.
it('порога нет вовсе — НЕЛЬЗЯ, а не «места полно»', () => {
const s = diskState(50, undefined);
expect(s.ok).toBe(false);
expect(s.min_gb).toBeNull();
expect(s.reason).toMatch(/порог/i);
});
it('порог пустой (null) — тоже нельзя', () => {
expect(diskState(50, null).ok).toBe(false);
});
it('порог не число (NaN, строка) — тоже нельзя', () => {
expect(diskState(50, NaN).ok).toBe(false);
expect(diskState(50, '5').ok).toBe(false);
expect(diskState(50, Infinity).ok).toBe(false);
});
it('в отказе названа сама потерянная настройка — владельцу есть куда смотреть', () => {
expect(diskState(50, undefined).reason).toContain('disk_free_min_gb');
});
it('и замерить не удалось, и порога нет — отказ, и число не выдумано', () => {
const s = diskState(null, undefined);
expect(s.ok).toBe(false);
expect(s.free_gb).toBeNull();
expect(s.min_gb).toBeNull();
});
it('порог ноль — это настоящий порог, а не «не задан»', () => {
// 🪤 Ноль легко спутать с «не дали». Здесь он законен: владелец вправе поставить 0,
// и тогда работает правило «ровно порог — уже нельзя».
expect(diskState(0, 0).ok).toBe(false);
expect(diskState(1, 0)).toEqual({ ok: true, free_gb: 1, min_gb: 0, reason: null });
});
});
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🔴🔴 ПОРОГ СЛЕП К ЧИСЛУ РАБОТНИКОВ (найдено 05.08.2026)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
describe('🔴 порог считается от числа работников, а не вписывается числом', () => {
it('нынешняя настройка затеи: семеро при запасе 5 — это 22.5 ГБ, а не 5', () => {
expect(porogMesta({ rabotnikov: 7, zapasGb: 5 }).gb).toBe(22.5);
});
it('🔴 ГЛАВНОЕ: шесть свободных гигабайтов при семерых — ночь НЕ трогается', () => {
// Ровно та беда, ради которой всё писалось. При плоском пороге 5 здесь было `ok: true`:
// ночь законно стартовала и упиралась в диск часа в три, когда на машине никого нет.
const s = diskState(6, porogMesta({ rabotnikov: 7, zapasGb: 5 }));
expect(s.ok).toBe(false);
expect(s.min_gb).toBe(22.5);
});
it('одному работнику того же места ХВАТАЕТ — порог не «просто больше», он считается', () => {
// 🪤 Сторож, который просто задрал число, покраснел бы и здесь. Порог обязан ехать
// за числом работников в ОБЕ стороны, иначе это не счёт, а другая плоская цифра.
expect(diskState(9, porogMesta({ rabotnikov: 1, zapasGb: 5 })).ok).toBe(true);
expect(diskState(6, porogMesta({ rabotnikov: 1, zapasGb: 5 })).ok).toBe(false);
});
it('в отказе сказано, ОТКУДА взялось число — иначе владелец снизит порог рукой', () => {
const s = diskState(6, porogMesta({ rabotnikov: 7, zapasGb: 5 }));
expect(s.reason).toContain('7 работников');
expect(s.reason).toContain(String(GB_NA_ODIN_UGOL));
expect(s.reason).toMatch(/разбудить владельца/);
});
it('цена угла замерена, а не выдумана: не меньше самого тяжёлого замеренного угла (2,42 ГБ)', () => {
// 🔴 Занизить эту цену — значит вернуть ту же дыру тихо. Замер 05.08.2026 своей рукой,
// и все числа замера — нижняя граница (прибор Windows молча пропускает длинные пути).
expect(GB_NA_ODIN_UGOL).toBeGreaterThanOrEqual(2.42);
});
it('числа работников не назвали — порога НЕТ, а не «наверное один»', () => {
expect(porogMesta({ rabotnikov: undefined, zapasGb: 5 })).toBeNull();
expect(porogMesta({ rabotnikov: 0, zapasGb: 5 })).toBeNull();
expect(porogMesta({ rabotnikov: 2.5, zapasGb: 5 })).toBeNull();
expect(porogMesta({ rabotnikov: '7', zapasGb: 5 })).toBeNull();
});
it('запаса не назвали — порога тоже нет: это потерянная настройка, а не ноль', () => {
expect(porogMesta({ rabotnikov: 7, zapasGb: undefined })).toBeNull();
expect(porogMesta({ rabotnikov: 7, zapasGb: null })).toBeNull();
});
it('порога нет — и место через diskState отказывает, называя обе настройки', () => {
const s = diskState(50, porogMesta({ rabotnikov: undefined, zapasGb: 5 }));
expect(s.ok).toBe(false);
expect(s.reason).toContain('disk_free_min_gb');
expect(s.reason).toContain('max_workers_per_task');
});
});
// ━━━ 🛡️ СТОРОЖ ЗОВУЩИХ: правило мертво, если зовущий его обходит ━━━
// 🔴 Именно это и было: ветка «порога не дали» стояла в disk.mjs, была покрыта проверками
// и НЕ РАБОТАЛА НИ РАЗУ — оба живых зовущих подставляли `?? 5`, и потерянная настройка
// молча становилась пятёркой. Проверка правила такого не ловит: она зовёт правило сама.
// 🪤 Примечания ВЫРЕЗАЮТСЯ ДО ПОИСКА, и не своим вторым способом, а тем же `tolkoKod`,
// каким это делает датчик зовущих. Поймано на себе: моё же примечание «прежнее было
// `cfg.disk_free_min_gb ?? 5`» покраснело как живой код. Это ровно та мина, о которой
// датчик зовущих предупреждает вслух, только вывернутая наизнанку.
describe('🛡️ живые зовущие места подают счёт, а не плоское число', () => {
const istochnik = imya => tolkoKod(readFileSync(join(REPO_ROOT, 'tools', 'night', imya), 'utf8'));
for (const imya of ['cli.mjs', 'supervisor.mjs']) {
it(`${imya} зовёт diskState с разбором porogMesta`, () => {
const text = istochnik(imya);
expect(text).toContain('porogMesta(');
expect(text).toMatch(/rabotnikov:\s*cfg\??\.?\??\.max_workers_per_task/);
});
it(`🔴 в ${imya} не осталось плоского умолчания «disk_free_min_gb ?? число»`, () => {
expect(istochnik(imya)).not.toMatch(/disk_free_min_gb\s*\?\?\s*\d/);
});
}
});