토너먼트 state의 stage 전이만 공용 실시간 채널에 발행하고 공개 invalidation으로 변환한다. 메인 dashboard store가 단계 값을 소유해 상단 문구와 메뉴 강조를 단일/다중 탭에서 함께 갱신한다. Redis 통합 테스트와 production Chromium 회귀 검증을 추가한다.
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { buildGameEventChannel, type RealtimeEvent } from '@sammo-ts/common';
|
|
import { parseRealtimeEvent } from '../src/realtime/eventHub.js';
|
|
import { formatSseFrame } from '../src/realtime/sse.js';
|
|
|
|
describe('formatSseFrame', () => {
|
|
it('renders basic SSE payloads', () => {
|
|
const output = formatSseFrame({
|
|
event: 'ping',
|
|
id: '1',
|
|
retry: 1500,
|
|
data: 'ok',
|
|
});
|
|
|
|
expect(output).toBe(['event: ping', 'id: 1', 'retry: 1500', 'data: ok', ''].join('\n') + '\n');
|
|
});
|
|
|
|
it('splits multiline data', () => {
|
|
const output = formatSseFrame({
|
|
event: 'notice',
|
|
data: 'first\nsecond',
|
|
});
|
|
|
|
expect(output).toBe(['event: notice', 'data: first', 'data: second', ''].join('\n') + '\n');
|
|
});
|
|
});
|
|
|
|
describe('parseRealtimeEvent', () => {
|
|
it('accepts valid realtime events', () => {
|
|
const payload: RealtimeEvent = {
|
|
type: 'turnCompleted',
|
|
at: '2026-01-01T00:00:00.000Z',
|
|
lastTurnTime: '2026-01-01T00:00:00.000Z',
|
|
};
|
|
const parsed = parseRealtimeEvent(JSON.stringify(payload));
|
|
|
|
expect(parsed?.type).toBe('turnCompleted');
|
|
});
|
|
|
|
it('rejects invalid payloads', () => {
|
|
expect(parseRealtimeEvent('not-json')).toBeNull();
|
|
expect(parseRealtimeEvent(JSON.stringify({}))).toBeNull();
|
|
});
|
|
|
|
it('accepts the minimal tournament state wake-up', () => {
|
|
expect(parseRealtimeEvent(JSON.stringify({ type: 'tournamentChanged' }))).toEqual({
|
|
type: 'tournamentChanged',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('buildGameEventChannel', () => {
|
|
it('namespaces channels by profile', () => {
|
|
expect(buildGameEventChannel('che:default')).toBe('sammo:che:default:realtime:events');
|
|
});
|
|
});
|