Files
core2026/packages/common/test/readModelOutbox.test.ts
T
Hide_D dd4645e4d0 feat: 직접 writer를 내구성 변화 저널에 연결
86개 API mutation을 분류하고 메시지 mailbox, 베팅, 국가 설정, 예약 명령의 revision/outbox 표식을 소유 transaction에 연결한다. 공개 SSE는 식별자 없는 invalidation만 노출한다.
2026-08-16 18:38:08 +00:00

104 lines
3.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
hasRealtimeReadModelChanges,
parseReadModelOutboxPayload,
readModelOutboxPayloadToMessageMailboxes,
readModelOutboxPayloadToChanges,
resolveRealtimeReadModelInvalidation,
} from '../src/index.js';
describe('read-model outbox payload', () => {
it('validates the version, domain, entity ID, and bigint revision', () => {
expect(
parseReadModelOutboxPayload({
version: 1,
changes: [
['general.content', 7, '12'],
['map.world', 0, '3'],
],
})
).toEqual({
version: 1,
changes: [
['general.content', 7, '12'],
['map.world', 0, '3'],
],
});
expect(parseReadModelOutboxPayload({ version: 2, changes: [] })).toBeNull();
expect(parseReadModelOutboxPayload({ version: 1, changes: [['unknown', 0, '1']] })).toBeNull();
expect(parseReadModelOutboxPayload({ version: 1, changes: [['map.world', -1, '1']] })).toBeNull();
expect(parseReadModelOutboxPayload({ version: 1, changes: [['map.world', 0, '-1']] })).toBeNull();
});
it('reconstructs an idempotent internal invalidation without broadening map-only changes', () => {
const payload = parseReadModelOutboxPayload({
version: 1,
changes: [
['general.content', 7, '2'],
['map.general', 7, '2'],
['map.world', 0, '9'],
['front.general', 7, '3'],
['front.nation', 4, '5'],
['records.general', 7, '8'],
['reserved.general', 7, '4'],
['lobby.general', 7, '2'],
['contacts.world', 0, '6'],
],
});
if (!payload) throw new Error('valid payload rejected');
const changes = readModelOutboxPayloadToChanges(payload);
expect(changes).toMatchObject({
generalIds: [7],
mapGeneralIds: [7],
mapChanged: true,
frontStatusActorIds: [7],
frontStatusNationIds: [4],
recordGeneralIds: [7],
reservedGeneralIds: [7],
lobbyGeneralIds: [7],
contactsChanged: true,
worldChanged: false,
});
expect(hasRealtimeReadModelChanges(changes)).toBe(true);
expect(resolveRealtimeReadModelInvalidation(changes, { generalId: 99, cityId: 2, nationId: 9 })).toMatchObject(
{
map: true,
context: false,
commands: false,
lobby: false,
}
);
});
it('keeps source-only, access-only, tournament, and betting domains off the dashboard channel', () => {
const payload = parseReadModelOutboxPayload({
version: 1,
changes: [
['access.general', 7, '1'],
['dashboard.global', 0, '1'],
['tournament', 0, '1'],
['betting', 0, '1'],
],
});
if (!payload) throw new Error('valid payload rejected');
expect(hasRealtimeReadModelChanges(readModelOutboxPayloadToChanges(payload))).toBe(false);
});
it('keeps durable mailbox revisions separate from dashboard invalidations', () => {
const payload = parseReadModelOutboxPayload({
version: 1,
changes: [
['messages.mailbox', 9999, '3'],
['messages.mailbox', 7, '2'],
['messages.mailbox', 7, '2'],
],
});
if (!payload) throw new Error('valid payload rejected');
expect(readModelOutboxPayloadToMessageMailboxes(payload)).toEqual([7, 9999]);
expect(hasRealtimeReadModelChanges(readModelOutboxPayloadToChanges(payload))).toBe(false);
});
});