191 lines
11 KiB
JavaScript
191 lines
11 KiB
JavaScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { readCommand, consumeCommand, unknownCommands } from './items.mjs';
|
|
|
|
let dir;
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(join(tmpdir(), 'night-cmd-'));
|
|
});
|
|
afterEach(() => { rmSync(dir, { recursive: true, force: true }); });
|
|
|
|
// Положить команду в каталог. Ровно так это делает панель, бот или рука владельца.
|
|
function polozhit(name, body = { at: 1 }) {
|
|
mkdirSync(dir, { recursive: true });
|
|
writeFileSync(join(dir, name), JSON.stringify(body), 'utf8');
|
|
}
|
|
|
|
describe('readCommand — что владелец велел этому работнику', () => {
|
|
it('каталога команд нет вовсе — не падаем, отвечаем «команды нет»', () => {
|
|
// 🪤 Каталог `.night/commands` заводит рука владельца, и в первую ночь его может
|
|
// не быть. Брошенная тут ошибка убила бы надзирателя молча — тот самый класс беды.
|
|
expect(readCommand(join(dir, 'нет-такого'), 'w-1')).toBeNull();
|
|
});
|
|
|
|
it('каталог есть, но пуст — работаем дальше', () => {
|
|
mkdirSync(dir, { recursive: true });
|
|
expect(readCommand(dir, 'w-1')).toBeNull();
|
|
});
|
|
|
|
it('положили личную «паузу» — работник её видит, и видно, что она личная', () => {
|
|
polozhit('w-1.pause.json');
|
|
const cmd = readCommand(dir, 'w-1');
|
|
expect(cmd.cmd).toBe('pause');
|
|
expect(cmd.common).toBe(false);
|
|
expect(cmd.file).toBe('w-1.pause.json');
|
|
});
|
|
|
|
it('чужую личную команду работник НЕ видит', () => {
|
|
polozhit('w-2.stop.json');
|
|
expect(readCommand(dir, 'w-1')).toBeNull();
|
|
});
|
|
|
|
it('«стоп всё» видит любой работник, не только адресат', () => {
|
|
polozhit('all.stopall.json');
|
|
const cmd = readCommand(dir, 'w-9');
|
|
expect(cmd.cmd).toBe('stopall');
|
|
expect(cmd.common).toBe(true);
|
|
});
|
|
|
|
it('«стоп всё» главнее личной паузы — отдаётся первой', () => {
|
|
// 🔴 Порядок не украшение: получи работник сперва паузу, он доделывал бы шаг
|
|
// до двадцати минут, пока владелец ждёт остановки всего прогона.
|
|
polozhit('all.stopall.json');
|
|
polozhit('w-1.pause.json');
|
|
expect(readCommand(dir, 'w-1').cmd).toBe('stopall');
|
|
});
|
|
|
|
it('слово не из закрытого списка не читается вовсе — мы не выдумываем, что оно значит', () => {
|
|
polozhit('w-1.udali-vsyo.json');
|
|
expect(readCommand(dir, 'w-1')).toBeNull();
|
|
});
|
|
|
|
it('файл команды порван — команда всё равно прочитана, тело просто пустое', () => {
|
|
// 🪤 Порванный файл — это отсутствие сведений, а не повод уронить надзирателя.
|
|
// Слово «стоп» стоит в самом имени файла, и его достаточно.
|
|
mkdirSync(dir, { recursive: true });
|
|
writeFileSync(join(dir, 'w-1.stop.json'), '{ это не json', 'utf8');
|
|
expect(readCommand(dir, 'w-1').cmd).toBe('stop');
|
|
});
|
|
|
|
it('тело команды доезжает целиком — вместе с тем, кто и когда её отдал', () => {
|
|
polozhit('w-1.stop.json', { at: 777, by: 'владелец' });
|
|
const cmd = readCommand(dir, 'w-1');
|
|
expect(cmd.at).toBe(777);
|
|
expect(cmd.by).toBe('владелец');
|
|
});
|
|
|
|
it('🔴 тело файла НЕ перебивает слово из его имени — иначе закрытый список обходится', () => {
|
|
// 🔴 Положи владелец (или кто угодно) в файл «паузы» тело {"cmd":"stopall"} — и работник
|
|
// был бы погашен, хотя имя файла говорит «пауза». Слово команды берётся ТОЛЬКО из имени
|
|
// файла: имя — это и есть закрытый список. Тело несёт сведения, а не приказ.
|
|
polozhit('w-1.pause.json', { at: 1, cmd: 'stopall', common: true, file: 'подделка' });
|
|
const cmd = readCommand(dir, 'w-1');
|
|
expect(cmd.cmd).toBe('pause');
|
|
expect(cmd.common).toBe(false);
|
|
expect(cmd.file).toBe('w-1.pause.json');
|
|
});
|
|
});
|
|
|
|
describe('unknownCommands — владелец узнаёт, что его команду НЕ поняли', () => {
|
|
it('непонятое слово НАЗЫВАЕТСЯ — с именем файла и самим словом, и файл остаётся лежать', () => {
|
|
// 🔴🔴 Решение диспетчера. Прежде не происходило ничего: владелец думал, что «пауза»
|
|
// нажата, а её никто не понял, и файл лежал до утра молча.
|
|
polozhit('w-1.udali-vsyo.json');
|
|
const spisok = unknownCommands(dir, 'w-1');
|
|
expect(spisok.length).toBe(1);
|
|
expect(spisok[0].word).toBe('udali-vsyo');
|
|
expect(spisok[0].file).toBe('w-1.udali-vsyo.json');
|
|
// Не съедено молча: файл владельца на месте, он и есть доказательство.
|
|
expect(existsSync(join(dir, 'w-1.udali-vsyo.json'))).toBe(true);
|
|
});
|
|
|
|
it('общее непонятое слово тоже называется — «всем» касается и этого работника', () => {
|
|
polozhit('all.vsyo-brosit.json');
|
|
const spisok = unknownCommands(dir, 'w-9');
|
|
expect(spisok.length).toBe(1);
|
|
expect(spisok[0].common).toBe(true);
|
|
});
|
|
|
|
it('🔴 понятные команды и чужие файлы в этот список НЕ попадают', () => {
|
|
// 🔴 Страховка от прибора, который называет непонятым ВСЁ подряд: владелец получал бы
|
|
// строку «не понял» на каждую исправную команду и перестал бы читать сводку вовсе.
|
|
polozhit('w-1.pause.json'); // понятная — молчим
|
|
polozhit('all.stopall.json'); // понятная общая — молчим
|
|
polozhit('w-2.udali-vsyo.json'); // чужая личная — о ней скажет надзиратель w-2
|
|
polozhit('zametka.txt'); // вообще не команда
|
|
expect(unknownCommands(dir, 'w-1')).toEqual([]);
|
|
});
|
|
|
|
it('каталога команд нет вовсе — пустой список, и не падаем', () => {
|
|
expect(unknownCommands(join(dir, 'нет-такого'), 'w-1')).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('consumeCommand — личную убираем сразу, общую только когда исполнили ВСЕ', () => {
|
|
it('прочитанная ЛИЧНАЯ команда убирается, чтобы не сработать дважды', () => {
|
|
polozhit('w-1.stop.json');
|
|
const r = consumeCommand(dir, 'w-1', 'stop');
|
|
expect(r.personal_removed).toBe(true);
|
|
expect(readCommand(dir, 'w-1')).toBeNull();
|
|
});
|
|
|
|
it('личной команды не было — так и сказано, и ничего лишнего не тронуто', () => {
|
|
polozhit('all.pause.json');
|
|
const r = consumeCommand(dir, 'w-1', 'stop');
|
|
expect(r.personal_removed).toBe(false);
|
|
expect(existsSync(join(dir, 'all.pause.json'))).toBe(true);
|
|
});
|
|
|
|
it('«стоп всё» НЕ съедается первым прочитавшим — её видят все семеро', () => {
|
|
// 🪤 Ровно эта беда и была: первый исполнивший стирал файл, шестеро работали дальше.
|
|
polozhit('all.stopall.json');
|
|
const semero = ['w-1', 'w-2', 'w-3', 'w-4', 'w-5', 'w-6', 'w-7'];
|
|
for (const w of semero.slice(0, 6)) {
|
|
expect(readCommand(dir, w).cmd).toBe('stopall');
|
|
consumeCommand(dir, w, 'stopall', { concerns: semero });
|
|
}
|
|
// Седьмой всё ещё видит команду — она его тоже касается.
|
|
expect(readCommand(dir, 'w-7').cmd).toBe('stopall');
|
|
const r = consumeCommand(dir, 'w-7', 'stopall', { concerns: semero });
|
|
// Исполнили все — только теперь файла нет.
|
|
expect(r.common_removed).toBe(true);
|
|
expect(readCommand(dir, 'w-7')).toBeNull();
|
|
expect(existsSync(join(dir, 'all.stopall.json'))).toBe(false);
|
|
});
|
|
|
|
it('исполнившие копятся в САМОМ ФАЙЛЕ, а не в памяти одного надзирателя', () => {
|
|
// 🔴 Надзиратели — разные процессы. Держи мы список исполнивших в памяти, каждый
|
|
// видел бы свой собственный, и общая команда не убралась бы никогда.
|
|
polozhit('all.stopall.json');
|
|
consumeCommand(dir, 'w-1', 'stopall', { concerns: ['w-1', 'w-2'] });
|
|
const body = JSON.parse(readFileSync(join(dir, 'all.stopall.json'), 'utf8'));
|
|
expect(body.done).toEqual(['w-1']);
|
|
});
|
|
|
|
it('исполнивший общую команду второй раз её не получает — иначе гасился бы по ней вечно', () => {
|
|
polozhit('all.stopall.json');
|
|
consumeCommand(dir, 'w-1', 'stopall', { concerns: ['w-1', 'w-2'] });
|
|
expect(readCommand(dir, 'w-1')).toBeNull();
|
|
expect(readCommand(dir, 'w-2').cmd).toBe('stopall');
|
|
});
|
|
|
|
it('состав не назван — общий файл НЕ удаляется: лучше промолчать, чем снять команду с тех, до кого она не дошла', () => {
|
|
// 🔴 Это дословное правило договора о стыках. Убери его — и первый же надзиратель,
|
|
// не сумевший прочитать `run.json`, снял бы «стоп всё» со всего прогона.
|
|
polozhit('all.stopall.json');
|
|
const r = consumeCommand(dir, 'w-1', 'stopall', { concerns: [] });
|
|
expect(r.common_removed).toBe(false);
|
|
expect(existsSync(join(dir, 'all.stopall.json'))).toBe(true);
|
|
});
|
|
|
|
it('исполнение ЛИЧНОЙ команды не съедает общую, и наоборот', () => {
|
|
polozhit('all.pause.json');
|
|
polozhit('w-1.stop.json');
|
|
consumeCommand(dir, 'w-1', 'stop', { concerns: ['w-1', 'w-2'] });
|
|
expect(readCommand(dir, 'w-2').cmd).toBe('pause');
|
|
expect(readCommand(dir, 'w-1').cmd).toBe('pause');
|
|
});
|
|
});
|