import { describe, expect, test } from 'bun:test'; import { createHash } from 'node:crypto'; import { topicEvents } from '../server/utils/events'; import { createTopicSnapshotStream } from '../server/utils/topic-snapshot-stream'; import { appendTextDelta, serializeTopicSnapshot } from '../utils/topic-sync'; const snapshot = () => ({ id: 'topic', name: 'Example', messages: [{ id: 'message', activeChildId: null as string | null, updatedAt: new Date('2026-09-27T00:00:00Z'), generation: { status: 'pending', tokens: null as unknown }, parts: [{ id: 'part', type: 'text', content: 'hello', finished: false }], }], }); const fingerprint = (value: unknown) => createHash('sha256').update(serializeTopicSnapshot(value)).digest('hex'); const readEvent = async (reader: ReadableStreamDefaultReader) => { const { value } = await reader.read(); return JSON.parse(value!.slice(6)); }; describe('topic snapshot synchronization', () => { test('matches database Dates and hydrated JSON regardless of object key order', () => { const topic = snapshot(); const hydrated = JSON.parse(JSON.stringify(topic)); expect(fingerprint({ messages: hydrated.messages, name: hydrated.name, id: hydrated.id })).toBe(fingerprint(topic)); }); test('detects older-message, part, generation, variant, and topic changes without relying on count', () => { const original = fingerprint(snapshot()); for (const change of [ (topic: ReturnType) => topic.name = 'Renamed', (topic: ReturnType) => topic.messages[0]!.activeChildId = 'variant', (topic: ReturnType) => topic.messages[0]!.parts[0]!.content = 'edited', (topic: ReturnType) => topic.messages[0]!.parts[0]!.finished = true, (topic: ReturnType) => topic.messages[0]!.generation.tokens = { output: 123 }, ]) { const topic = snapshot(); change(topic); expect(fingerprint(topic)).not.toBe(original); } }); test('matching state receives a small ready event, then live changes', async () => { const topic = snapshot(); const subscription = createTopicSnapshotStream('match', fingerprint(topic)); const result = await subscription.open(async () => topic); expect(result.ok).toBe(true); if (!result.ok) return; const reader = result.data.getReader(); try { expect(await readEvent(reader)).toEqual({ type: 'synced', payload: { id: 'match' } }); await topicEvents.emit('match', { type: 'topic_updated', payload: { name: 'New' } }); expect((await readEvent(reader)).payload).toEqual({ name: 'New' }); } finally { await reader.cancel(); } }); test('stale or missing fingerprints receive a full replacement snapshot', async () => { for (const offered of [undefined, fingerprint(snapshot())]) { const topic = snapshot(); topic.messages[0]!.parts[0]!.content = 'changed'; const subscription = createTopicSnapshotStream('stale', offered); const result = await subscription.open(async () => topic); expect(result.ok).toBe(true); if (!result.ok) continue; const reader = result.data.getReader(); try { const event = await readEvent(reader); expect(event.type).toBe('initial_state'); expect(event.payload.messages[0].parts[0].content).toBe('changed'); } finally { await reader.cancel(); } } }); test('captures events during the DB query and includes text not yet persisted', async () => { const id = 'query-race'; const initial = snapshot(); await topicEvents.emit(id, { type: 'text-start', payload: { messageId: 'message', part: initial.messages[0]!.parts[0] }, }); // This delta predates subscription and has not reached the database yet. await topicEvents.emit(id, { type: 'text-delta', payload: { messageId: 'message', partId: 'part', content: ' world', offset: 5 }, }); const pending = Promise.withResolvers>(); const subscription = createTopicSnapshotStream(id, fingerprint(initial)); const opening = subscription.open(() => pending.promise); await topicEvents.emit(id, { type: 'text-delta', payload: { messageId: 'message', partId: 'part', content: '!', offset: 11 }, }); pending.resolve(snapshot()); const result = await opening; expect(result.ok).toBe(true); if (!result.ok) return; const reader = result.data.getReader(); try { const state = await readEvent(reader); expect(state.type).toBe('initial_state'); const text = state.payload.messages[0].parts[0].content; expect(text).toBe('hello world!'); const replay = await readEvent(reader); expect(replay.replay).toBe(true); expect(appendTextDelta(text, replay.payload.content, replay.payload.offset)).toBe(text); } finally { await reader.cancel(); topicEvents.clearLiveText(id, 'message'); } }); test('cancellation during snapshot loading does not establish a stream', async () => { const pending = Promise.withResolvers>(); const subscription = createTopicSnapshotStream('cancelled', undefined); const opening = subscription.open(() => pending.promise); subscription.close(); pending.resolve(snapshot()); expect((await opening).ok).toBe(false); }); test('text offsets handle partial overlaps, duplicates, and gaps', () => { expect(appendTextDelta('hello w', ' world', 5)).toBe('hello world'); expect(appendTextDelta('hello world', ' world', 5)).toBe('hello world'); expect(appendTextDelta('hello', '!', 5)).toBe('hello!'); expect(appendTextDelta('hello', '!', 10)).toBe('hello'); }); });