Files
core2026/packages/common/test/changeJournal.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

94 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
ChangeJournal,
createReadModelOutboxPayload,
normalizeReadModelRevisionKeys,
READ_MODEL_DOMAINS,
type ReadModelDomain,
} from '../src/realtime/changeJournal.js';
describe('ChangeJournal', () => {
it('keeps viewer-filtering semantics as distinct durable domains', () => {
expect(READ_MODEL_DOMAINS).toEqual(
expect.arrayContaining([
'map.general',
'lobby.general',
'contacts.world',
'reserved.general',
'messages.mailbox',
'dashboard.global',
])
);
});
it('dedupes keys and returns them in a stable lock order', () => {
const journal = new ChangeJournal()
.mark('nation.content', 4)
.mark('general.content', 9)
.mark('nation.content', 4)
.mark('general.content', 2)
.mark('map.world');
expect(journal.size).toBe(4);
expect(journal.snapshot()).toEqual([
{ domain: 'general.content', entityId: 2 },
{ domain: 'general.content', entityId: 9 },
{ domain: 'map.world', entityId: 0 },
{ domain: 'nation.content', entityId: 4 },
]);
});
it('merges another journal without sharing mutable state', () => {
const source = new ChangeJournal().mark('records.general', 7);
const destination = new ChangeJournal().mark('records.global').merge(source);
source.clear();
expect(source.isEmpty).toBe(true);
expect(destination.snapshot()).toEqual([
{ domain: 'records.general', entityId: 7 },
{ domain: 'records.global', entityId: 0 },
]);
});
it('rejects unknown domains and unstable entity IDs at runtime', () => {
expect(() => new ChangeJournal().mark('unknown' as ReadModelDomain, 1)).toThrow(TypeError);
expect(() => new ChangeJournal().mark('general.content', -1)).toThrow(RangeError);
expect(() => new ChangeJournal().mark('general.content', Number.MAX_SAFE_INTEGER + 1)).toThrow(RangeError);
});
});
describe('durable read-model invalidation contract', () => {
it('normalizes an arbitrary iterable independently of the collector', () => {
expect(
normalizeReadModelRevisionKeys([
{ domain: 'front.nation', entityId: 3 },
{ domain: 'front.global', entityId: 0 },
{ domain: 'front.nation', entityId: 3 },
])
).toEqual([
{ domain: 'front.global', entityId: 0 },
{ domain: 'front.nation', entityId: 3 },
]);
});
it('serializes bigint revisions to a compact JSON-safe payload', () => {
const payload = createReadModelOutboxPayload({
revisions: [
{ domain: 'world.content', entityId: 0, revision: 12n },
{ domain: 'general.content', entityId: 7, revision: 4n },
],
});
expect(payload).toEqual({
version: 1,
changes: [
['general.content', 7, '4'],
['world.content', 0, '12'],
],
});
expect(() => JSON.stringify(payload)).not.toThrow();
});
});