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

101 lines
4.8 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, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, readdirSync, writeFileSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { writeJsonAtomic, readJson, readJsonOrBroken, appendLine, readLines } from './state.mjs';
let dir;
beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'night-state-')); });
afterEach(() => { rmSync(dir, { recursive: true, force: true }); });
describe('writeJsonAtomic', () => {
it('пишет файл и читает обратно тем же значением', () => {
const p = join(dir, 'a', 'b', 'state.json');
writeJsonAtomic(p, { status: 'working', n: 1 });
expect(readJson(p)).toEqual({ status: 'working', n: 1 });
});
it('не оставляет временных файлов после успешной записи', () => {
const p = join(dir, 'state.json');
writeJsonAtomic(p, { a: 1 });
expect(readdirSync(dir)).toEqual(['state.json']);
});
it('несколько записей в один каталог подряд не мешают друг другу', () => {
// 🪤 Надзиратель в одном круге пишет в каталог работника несколько файлов состояния
// подряд — от одного процесса и с одним временем на часах. Собери имя временного файла
// только из времени и номера процесса — два таких имени совпадут, и одна запись
// переименует чужую недописанную половину. Убери случайный хвост — покраснеет здесь.
const names = ['a.json', 'b.json', 'c.json', 'd.json'];
for (const n of names) writeJsonAtomic(join(dir, n), { имя: n });
for (const n of names) expect(readJson(join(dir, n))).toEqual({ имя: n });
expect(readdirSync(dir).sort()).toEqual([...names].sort()); // ни одного хвоста .tmp
});
it('старое значение уцелело, если новое не сериализуется', () => {
const p = join(dir, 'state.json');
writeJsonAtomic(p, { good: true });
const loop = {}; loop.self = loop;
expect(() => writeJsonAtomic(p, loop)).toThrow();
expect(readJson(p)).toEqual({ good: true });
});
});
describe('readJson', () => {
it('нет файла — отдаёт null, а не падает', () => {
expect(readJson(join(dir, 'нет.json'))).toBeNull();
});
it('файл порван на середине — отдаёт null, а не мусор', () => {
const p = join(dir, 'broken.json');
writeFileSync(p, '{"status":"wor');
expect(readJson(p)).toBeNull();
});
});
describe('readJsonOrBroken', () => {
// 🆕 Общий прибор затеи (решение диспетчера 01.08). Три ответа вместо двух у readJson:
// файла нет · прочитан · есть, но порван — кусок 3 и кусок 6 отличают эти три случая
// друг от друга, а readJson такого различия не даёт вовсе.
it('нет файла — found: false, broken: false, value: null', () => {
const p = join(dir, 'нет.json');
expect(readJsonOrBroken(p)).toEqual({ found: false, broken: false, value: null });
});
it('файл цел — found: true, broken: false, value с содержимым', () => {
const p = join(dir, 'state.json');
writeJsonAtomic(p, { item: 3, saved_at: '2026-08-01T00:00:00.000Z' });
expect(readJsonOrBroken(p)).toEqual({
found: true,
broken: false,
value: { item: 3, saved_at: '2026-08-01T00:00:00.000Z' },
});
});
it('файл есть, но порван на середине — found: true, broken: true, value: null', () => {
const p = join(dir, 'broken.json');
writeFileSync(p, '{"item":3,"saved_at":"202');
expect(readJsonOrBroken(p)).toEqual({ found: true, broken: true, value: null });
});
});
describe('appendLine / readLines', () => {
it('дописывает строки и читает их по порядку', () => {
const p = join(dir, 'events.jsonl');
appendLine(p, { e: 'start' });
appendLine(p, { e: 'commit' });
expect(readLines(p)).toEqual([{ e: 'start' }, { e: 'commit' }]);
});
it('порванная последняя строка пропускается, целые остаются', () => {
const p = join(dir, 'events.jsonl');
appendLine(p, { e: 'start' });
writeFileSync(p, readFileSync(p, 'utf8') + '{"e":"comm');
expect(readLines(p)).toEqual([{ e: 'start' }]);
});
it('нет файла — пустой список', () => {
expect(readLines(join(dir, 'нет.jsonl'))).toEqual([]);
});
});