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); }); });