/
kochkatech
/
CorpGame
Обзор
Документация
Войти
/
kochkatech
/
CorpGame
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
dev
backend/src/common/team-lock.spec.ts
86 строк
4 KB
kochkareal
feat(devops): свод к одному процессу Node на SQLite, портативная сборка без Docker; аудит безопасности
06 авг 2026, 15:06
06 авг 2026, 15:06
3266bcd
Код
Авторство
О чём код?
import { withTeamLock } from './team-lock'; /** * Юнит-тест дисциплины блокировки (без БД): команды перечитываются под локом и передаются * в callback, id дедуплицируются, а два конкурентных вызова на одну команду не переплетают * свои критические секции. Последнее — собственно контракт, ради которого файл существует. * * Раньше здесь проверялся порядок вызовов `$queryRaw` с `FOR UPDATE`; этот синтаксис * Postgres-специфичен и после перехода на SQLite удалён, так что проверяем наблюдаемое * поведение, а не то, какие SQL-запросы улетели. */ describe('withTeamLock', () => { const tick = () => new Promise((r) => setImmediate(r)); function makeMock(rows: { id: string; resources: Record<string, number> }[]) { const readCalls: string[][] = []; const tx = { team: { findMany: ({ where }: { where: { id: { in: string[] } } }) => { readCalls.push(where.id.in); return Promise.resolve(rows.filter((r) => where.id.in.includes(r.id))); }, }, }; const prisma = { $transaction: (cb: (t: typeof tx) => Promise<unknown>) => cb(tx), }; return { prisma, readCalls }; } it('дедуплицирует и сортирует id, перечитывая команды под локом', async () => { const { prisma, readCalls } = makeMock([{ id: 'a', resources: { wood: 3 } }]); let seen: Map<string, { id: string; resources: Record<string, number> }> | null = null; const result = await withTeamLock(prisma as never, ['a', 'a'], async (_tx, teams) => { seen = teams as never; return 'ok'; }); expect(readCalls).toEqual([['a']]); // дубль id — одно чтение, один лок expect(result).toBe('ok'); expect(seen!.get('a')?.resources).toEqual({ wood: 3 }); }); it('сортирует id перед захватом (детерминированный порядок — защита от deadlock)', async () => { const { prisma, readCalls } = makeMock([ { id: 'a', resources: {} }, { id: 'b', resources: {} }, ]); // Передаём в обратном порядке — помощник обязан отсортировать. await withTeamLock(prisma as never, ['b', 'a'], async () => undefined); expect(readCalls).toEqual([['a', 'b']]); }); it('не переплетает критические секции двух конкурентных вызовов на одну команду', async () => { const { prisma } = makeMock([{ id: 'a', resources: {} }]); const log: string[] = []; const first = withTeamLock(prisma as never, ['a'], async () => { log.push('first:enter'); await tick(); log.push('first:exit'); }); const second = withTeamLock(prisma as never, ['a'], async () => { log.push('second:enter'); await tick(); log.push('second:exit'); }); await Promise.all([first, second]); expect(log).toEqual(['first:enter', 'first:exit', 'second:enter', 'second:exit']); }); it('освобождает лок, если callback бросил исключение', async () => { const { prisma } = makeMock([{ id: 'a', resources: {} }]); await expect( withTeamLock(prisma as never, ['a'], async () => { throw new Error('кража сорвалась'); }), ).rejects.toThrow('кража сорвалась'); // Если бы лок не отпустили, следующий вызов завис бы навсегда. await expect(withTeamLock(prisma as never, ['a'], async () => 'ok')).resolves.toBe('ok'); }); });