Шаги 1.4 и 1.5 плана production-готовности: - SseConnectionIndex (byOrg/byUser), один heartbeat-таймер, протокол SSE не тронут - getTaskTree/getTaskParentChain: WITH RECURSIVE CTE (было 1+2N запросов) - sync initial/delta: Promise.all по формам - embedding queue/reindex: батч-предзагрузки вместо поштучных запросов - 8 новых тестов (96/96)
91 lines
3.9 KiB
TypeScript
91 lines
3.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
||
import { SseConnectionIndex, type IndexedSSEConnection } from '../server/utils/sse-connection-index';
|
||
|
||
function conn(id: string, userId: number, organizationId: number): IndexedSSEConnection {
|
||
return { id, userId, organizationId };
|
||
}
|
||
|
||
describe('SseConnectionIndex', () => {
|
||
it('add/remove ведут учёт размера и очищают оба индекса', () => {
|
||
const index = new SseConnectionIndex<IndexedSSEConnection>();
|
||
index.add(conn('a', 1, 10));
|
||
index.add(conn('b', 1, 10));
|
||
index.add(conn('c', 2, 20));
|
||
expect(index.size).toBe(3);
|
||
|
||
index.remove('a');
|
||
expect(index.size).toBe(2);
|
||
// Соединение удалено из всех индексов — не придёт ни по org, ни по user
|
||
expect(Array.from(index.getForEventTarget(10)).map(c => c.id)).toEqual(['b']);
|
||
expect(Array.from(index.getForEventTarget(10, 1)).map(c => c.id)).toEqual(['b']);
|
||
|
||
index.remove('b');
|
||
index.remove('c');
|
||
expect(index.size).toBe(0);
|
||
// Пустые Set'ы убраны из карт
|
||
expect(Array.from(index.getForEventTarget(10))).toEqual([]);
|
||
expect(Array.from(index.getForEventTarget(20, 2))).toEqual([]);
|
||
});
|
||
|
||
it('remove несуществующего id — no-op', () => {
|
||
const index = new SseConnectionIndex<IndexedSSEConnection>();
|
||
index.add(conn('a', 1, 10));
|
||
expect(index.remove('missing')).toBeUndefined();
|
||
expect(index.size).toBe(1);
|
||
});
|
||
|
||
it('событие организации уходит только соединениям этой организации', () => {
|
||
const index = new SseConnectionIndex<IndexedSSEConnection>();
|
||
index.add(conn('a', 1, 10));
|
||
index.add(conn('b', 2, 10));
|
||
index.add(conn('c', 3, 20));
|
||
const targets = Array.from(index.getForEventTarget(10)).map(c => c.id);
|
||
expect(targets).toEqual(['a', 'b']);
|
||
});
|
||
|
||
it('событие пользователя без organizationId уходит всем его соединениям (любая org)', () => {
|
||
const index = new SseConnectionIndex<IndexedSSEConnection>();
|
||
index.add(conn('a', 1, 10));
|
||
index.add(conn('b', 1, 20));
|
||
index.add(conn('c', 2, 10));
|
||
const targets = Array.from(index.getForEventTarget(undefined, 1)).map(c => c.id);
|
||
expect(targets).toEqual(['a', 'b']);
|
||
});
|
||
|
||
it('событие пользователя + organizationId уходит только его соединениям в этой org', () => {
|
||
const index = new SseConnectionIndex<IndexedSSEConnection>();
|
||
index.add(conn('a', 1, 10));
|
||
index.add(conn('b', 1, 20));
|
||
index.add(conn('c', 2, 10));
|
||
const targets = Array.from(index.getForEventTarget(10, 1)).map(c => c.id);
|
||
expect(targets).toEqual(['a']);
|
||
});
|
||
|
||
it('событие без userId и organizationId — broadcast на все соединения', () => {
|
||
const index = new SseConnectionIndex<IndexedSSEConnection>();
|
||
index.add(conn('a', 1, 10));
|
||
index.add(conn('b', 2, 20));
|
||
const targets = Array.from(index.getForEventTarget(undefined, undefined)).map(c => c.id);
|
||
expect(targets).toEqual(['a', 'b']);
|
||
});
|
||
|
||
it('isUserConnected учитывает и пользователя, и организацию', () => {
|
||
const index = new SseConnectionIndex<IndexedSSEConnection>();
|
||
index.add(conn('a', 1, 10));
|
||
expect(index.isUserConnected(1, 10)).toBe(true);
|
||
expect(index.isUserConnected(1, 20)).toBe(false);
|
||
expect(index.isUserConnected(2, 10)).toBe(false);
|
||
index.remove('a');
|
||
expect(index.isUserConnected(1, 10)).toBe(false);
|
||
});
|
||
|
||
it('forEach обходит все соединения (глобальный heartbeat)', () => {
|
||
const index = new SseConnectionIndex<IndexedSSEConnection>();
|
||
index.add(conn('a', 1, 10));
|
||
index.add(conn('b', 2, 20));
|
||
const seen: string[] = [];
|
||
index.forEach(c => seen.push(c.id));
|
||
expect(seen).toEqual(['a', 'b']);
|
||
});
|
||
});
|