fix: 토너먼트 단계의 메인 실시간 갱신을 연결
토너먼트 state의 stage 전이만 공용 실시간 채널에 발행하고 공개 invalidation으로 변환한다. 메인 dashboard store가 단계 값을 소유해 상단 문구와 메뉴 강조를 단일/다중 탭에서 함께 갱신한다. Redis 통합 테스트와 production Chromium 회귀 검증을 추가한다.
This commit is contained in:
@@ -5,14 +5,14 @@ import { writeTournamentProjection } from '../src/tournament/sourceRevision.js';
|
||||
describe('tournament source revision', () => {
|
||||
it('passes every payload and one profile revision key to a single atomic script', async () => {
|
||||
const calls: Array<{ keys: string[]; arguments: string[] }> = [];
|
||||
const published: string[] = [];
|
||||
const published: Array<{ channel: string; message: string }> = [];
|
||||
const redis = {
|
||||
eval: async (_script: string, options: { keys: string[]; arguments: string[] }) => {
|
||||
calls.push(options);
|
||||
return '7';
|
||||
return '7:1';
|
||||
},
|
||||
publish: async (_channel: string, message: string) => {
|
||||
published.push(message);
|
||||
publish: async (channel: string, message: string) => {
|
||||
published.push({ channel, message });
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
@@ -20,7 +20,12 @@ describe('tournament source revision', () => {
|
||||
await expect(
|
||||
writeTournamentProjection(
|
||||
redis,
|
||||
{ sourceRevisionKey: 'revision', sourceRevisionChannel: 'changed' },
|
||||
{
|
||||
stateKey: 'state',
|
||||
sourceRevisionKey: 'revision',
|
||||
sourceRevisionChannel: 'changed',
|
||||
realtimeEventChannel: 'realtime',
|
||||
},
|
||||
[
|
||||
{ key: 'state', value: { stage: 1 } },
|
||||
{ key: 'matches', value: [] },
|
||||
@@ -34,18 +39,35 @@ describe('tournament source revision', () => {
|
||||
arguments: [JSON.stringify({ stage: 1 }), '[]'],
|
||||
},
|
||||
]);
|
||||
expect(published).toEqual([JSON.stringify({ sourceRevision: '7' })]);
|
||||
expect(published).toEqual([
|
||||
{ channel: 'changed', message: JSON.stringify({ sourceRevision: '7' }) },
|
||||
{ channel: 'realtime', message: JSON.stringify({ type: 'tournamentChanged' }) },
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects empty or duplicate writes before evaluating Redis', async () => {
|
||||
const redis = { eval: async () => '1' };
|
||||
await expect(
|
||||
writeTournamentProjection(redis, { sourceRevisionKey: 'revision', sourceRevisionChannel: 'changed' }, [])
|
||||
writeTournamentProjection(
|
||||
redis,
|
||||
{
|
||||
stateKey: 'state',
|
||||
sourceRevisionKey: 'revision',
|
||||
sourceRevisionChannel: 'changed',
|
||||
realtimeEventChannel: 'realtime',
|
||||
},
|
||||
[]
|
||||
)
|
||||
).rejects.toThrow('at least one');
|
||||
await expect(
|
||||
writeTournamentProjection(
|
||||
redis,
|
||||
{ sourceRevisionKey: 'revision', sourceRevisionChannel: 'changed' },
|
||||
{
|
||||
stateKey: 'state',
|
||||
sourceRevisionKey: 'revision',
|
||||
sourceRevisionChannel: 'changed',
|
||||
realtimeEventChannel: 'realtime',
|
||||
},
|
||||
[
|
||||
{ key: 'state', value: 1 },
|
||||
{ key: 'state', value: 2 },
|
||||
@@ -53,4 +75,79 @@ describe('tournament source revision', () => {
|
||||
)
|
||||
).rejects.toThrow('unique');
|
||||
});
|
||||
|
||||
it('does not wake the main dashboard for participant-only writes', async () => {
|
||||
const published: string[] = [];
|
||||
const redis = {
|
||||
eval: async () => '8',
|
||||
publish: async (channel: string) => {
|
||||
published.push(channel);
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
|
||||
await writeTournamentProjection(
|
||||
redis,
|
||||
{
|
||||
stateKey: 'state',
|
||||
sourceRevisionKey: 'revision',
|
||||
sourceRevisionChannel: 'changed',
|
||||
realtimeEventChannel: 'realtime',
|
||||
},
|
||||
[{ key: 'participants', value: [{ id: 7 }] }]
|
||||
);
|
||||
|
||||
expect(published).toEqual(['changed']);
|
||||
});
|
||||
|
||||
it('does not wake the main dashboard when tournament state keeps the same stage', async () => {
|
||||
const published: string[] = [];
|
||||
const redis = {
|
||||
eval: async () => '10:0',
|
||||
publish: async (channel: string) => {
|
||||
published.push(channel);
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
writeTournamentProjection(
|
||||
redis,
|
||||
{
|
||||
stateKey: 'state',
|
||||
sourceRevisionKey: 'revision',
|
||||
sourceRevisionChannel: 'changed',
|
||||
realtimeEventChannel: 'realtime',
|
||||
},
|
||||
[{ key: 'state', value: { stage: 1, phase: 2 } }]
|
||||
)
|
||||
).resolves.toBe('10');
|
||||
expect(published).toEqual(['changed']);
|
||||
});
|
||||
|
||||
it('keeps both post-commit wake-up channels independently best effort', async () => {
|
||||
const published: string[] = [];
|
||||
const redis = {
|
||||
eval: async () => '9',
|
||||
publish: async (channel: string) => {
|
||||
published.push(channel);
|
||||
if (channel === 'changed') throw new Error('source subscriber unavailable');
|
||||
return 1;
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
writeTournamentProjection(
|
||||
redis,
|
||||
{
|
||||
stateKey: 'state',
|
||||
sourceRevisionKey: 'revision',
|
||||
sourceRevisionChannel: 'changed',
|
||||
realtimeEventChannel: 'realtime',
|
||||
},
|
||||
[{ key: 'state', value: { stage: 1 } }]
|
||||
)
|
||||
).resolves.toBe('9');
|
||||
expect(published).toEqual(['changed', 'realtime']);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user