Files
core2026/app/game-engine/test/webPushEvents.test.ts
T
Hide_D 7bc3213b03 feat: 계정 Web Push 전달 기반과 사건 판정을 추가한다
게임 상태 변경과 같은 트랜잭션에 내구성 outbox를 기록하고 Gateway가 계정 설정과 기기 구독으로 fan-out하도록 한다. 전역 기본값은 비활성으로 유지하며 migration과 회귀 테스트를 함께 추가한다.
2026-08-23 13:16:07 +00:00

117 lines
4.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { buildTurnWebPushEvents, type WebPushTurnBaseline } from '../src/turn/webPushEvents.js';
const general = (
userId: string | null,
options: { nationId?: number; crew?: number; deathCrew?: number; autorunLimit?: number | null } = {}
) => ({
userId,
nationId: options.nationId ?? 1,
crew: options.crew ?? 100,
deathCrew: options.deathCrew ?? 0,
autorunLimit: options.autorunLimit ?? null,
});
const baseline = (input: Partial<WebPushTurnBaseline> = {}): WebPushTurnBaseline => ({
serverId: 'season-1',
year: 200,
month: 1,
turnTick: '100',
generals: new Map(),
hasReservedTurns: new Map(),
...input,
});
describe('turn web push event projection', () => {
it('emits annihilation only when battle casualty totals also increase', () => {
const before = baseline({ generals: new Map([[1, general('user-1', { crew: 500, deathCrew: 10 })]]) });
const battleAfter = baseline({
generals: new Map([[1, general('user-1', { crew: 0, deathCrew: 510 })]]),
turnTick: '101',
});
const disbandAfter = baseline({
generals: new Map([[1, general('user-1', { crew: 0, deathCrew: 10 })]]),
turnTick: '101',
});
expect(buildTurnWebPushEvents({ before, after: battleAfter, changes: { deletedNationSnapshots: [] } })).toEqual(
[expect.objectContaining({ eventType: 'TROOP_ANNIHILATED', userIds: ['user-1'] })]
);
expect(
buildTurnWebPushEvents({ before, after: disbandAfter, changes: { deletedNationSnapshots: [] } })
).toEqual([]);
});
it('emits reserved-turn completion only for a dirty queue that consumed its last command', () => {
const before = baseline({
generals: new Map([[1, general('user-1')]]),
hasReservedTurns: new Map([[1, true]]),
});
const after = baseline({
generals: new Map([[1, general('user-1')]]),
hasReservedTurns: new Map([[1, false]]),
turnTick: '102',
});
const events = buildTurnWebPushEvents({
before,
after,
changes: { deletedNationSnapshots: [] },
reservedTurnChanges: { generalIds: [1] },
});
expect(events).toEqual([expect.objectContaining({ eventType: 'RESERVED_TURNS_ENDED', userIds: ['user-1'] })]);
});
it('emits calendar and autonomous-expiry events at the exclusive limit month', () => {
const before = baseline({
year: 200,
month: 1,
generals: new Map([[1, general('user-1', { autorunLimit: 2401 })]]),
});
const after = baseline({
year: 200,
month: 2,
generals: new Map([[1, general('user-1', { autorunLimit: 2401 })]]),
turnTick: '103',
});
expect(
buildTurnWebPushEvents({ before, after, changes: { deletedNationSnapshots: [] } }).map(
(event) => event.eventType
)
).toEqual(['TARGET_DATE_REACHED', 'AUTONOMOUS_ACTION_ENDED']);
});
it('targets the users who belonged to a destroyed nation before its removal', () => {
const before = baseline({
generals: new Map([
[1, general('user-1', { nationId: 7 })],
[2, general(null, { nationId: 7 })],
[3, general('user-3', { nationId: 7 })],
]),
});
const after = baseline({
generals: new Map([
[1, general('user-1', { nationId: 0 })],
[3, general('user-3', { nationId: 0 })],
]),
turnTick: '104',
});
const events = buildTurnWebPushEvents({
before,
after,
changes: {
deletedNationSnapshots: [
{
nation: { id: 7 },
generalIds: [1, 2, 3],
removedAt: new Date('0200-01-01T00:00:00.000Z'),
} as never,
],
},
});
expect(events).toEqual([
expect.objectContaining({ eventType: 'NATION_DESTROYED', userIds: ['user-1', 'user-3'] }),
]);
});
});