d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 1 | import assert from 'node:assert/strict'; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 2 | import { test } from 'node:test'; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 3 | import { observeArrivals } from '../src/lib/arrivals.ts'; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 4 | import type { TimelineEvent, TimelineItem } from '../src/lib/types.ts'; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 5 | |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 6 | const MINUTE = 60 * 1000; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 7 | |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 8 | function event(id: string, timestamp_ms: number, extra: Partial<TimelineEvent> = {}): TimelineItem { |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 9 | return { kind: 'event', event: { item_id: id, timestamp_ms, own: false, ...extra } as TimelineEvent }; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 10 | } |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 11 | |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 12 | function lagOf(items: TimelineItem[] | null, id: string): number | undefined { |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 13 | const found = items?.find((item) => item.kind === 'event' && item.event.item_id === id); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 14 | return found?.kind === 'event' ? found.event.delivery_lag_ms : undefined; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 15 | } |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 16 | |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 17 | test('flags a delayed event appended after newer known events', () => { |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 18 | const now = 100 * MINUTE; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 19 | const previous = [event('a', 90 * MINUTE), event('b', 95 * MINUTE)]; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 20 | const next = observeArrivals(previous, [...previous, event('late', 80 * MINUTE)], 0, now); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 21 | assert.equal(lagOf(next, 'late'), 20 * MINUTE); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 22 | }); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 23 | |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 24 | test('ignores backfill and prompt arrivals', () => { |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 25 | const now = 100 * MINUTE; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 26 | const previous = [event('a', 99 * MINUTE)]; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 27 | const next = observeArrivals(previous, [event('old', 10 * MINUTE), ...previous, event('new', now - 1000)], 0, now); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 28 | assert.equal(lagOf(next, 'old'), undefined); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 29 | assert.equal(lagOf(next, 'new'), undefined); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 30 | }); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 31 | |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 32 | test('measures from when the room became live, not from the send time', () => { |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 33 | const now = 100 * MINUTE; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 34 | const previous = [event('a', 10 * MINUTE)]; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 35 | const caughtUp = observeArrivals(previous, [...previous, event('offline', 20 * MINUTE)], now - 5000, now); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 36 | assert.equal(lagOf(caughtUp, 'offline'), undefined); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 37 | const delayed = observeArrivals(previous, [...previous, event('stuck', 20 * MINUTE)], now - 3 * MINUTE, now); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 38 | assert.equal(lagOf(delayed, 'stuck'), 3 * MINUTE); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 39 | }); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 40 | |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 41 | test('carries a flag across later updates', () => { |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 42 | const now = 100 * MINUTE; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 43 | const previous = [event('a', 99 * MINUTE), event('late', 50 * MINUTE, { delivery_lag_ms: 50 * MINUTE })]; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 44 | const next = observeArrivals(previous, [...previous.map((item) => item.kind === 'event' ? event(item.event.item_id, item.event.timestamp_ms) : item), event('c', now)], 0, now + MINUTE); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 45 | assert.equal(lagOf(next, 'late'), 50 * MINUTE); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 46 | }); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 47 | |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 48 | test('skips own events and replaced lists', () => { |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 49 | const now = 100 * MINUTE; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 50 | const previous = [event('a', 99 * MINUTE)]; |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 51 | const own = observeArrivals(previous, [...previous, event('mine', 10 * MINUTE, { own: true })], 0, now); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 52 | assert.equal(lagOf(own, 'mine'), undefined); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 53 | assert.equal(observeArrivals(previous, [event('x', 10 * MINUTE)], 0, now), null); |
d2a34d0f4861 | atagen | 2026-10-25 07:29 UTC | 54 | }); |