Files
openchamber/packages/ui/src/sync/session-ordering.test.ts
T

187 lines
7.1 KiB
TypeScript

import { beforeEach, describe, expect, test } from 'bun:test';
import type { Session } from '@opencode-ai/sdk/v2';
import {
compareSessionsByLifecycleOrder,
observeSessionActivityEvent,
orderSessionsByLifecycleScopes,
reconcileSessionActivitySnapshot,
removeSessionOrdering,
resetSessionOrdering,
useSessionOrderingStore,
raiseSessionOrderingBaselines,
} from './session-ordering';
const session = (
id: string,
updated: number,
parentID?: string,
): Session => ({
id,
parentID,
time: { created: updated - 1, updated },
} as Session);
beforeEach(() => resetSessionOrdering());
describe('session lifecycle ordering', () => {
test('promotes only meaningful event transitions', () => {
observeSessionActivityEvent('session-a', 'settled');
expect(useSessionOrderingStore.getState().rankById.has('session-a')).toBe(false);
observeSessionActivityEvent('session-a', 'active');
const activeRank = useSessionOrderingStore.getState().rankById.get('session-a');
expect(typeof activeRank).toBe('number');
observeSessionActivityEvent('session-a', 'active');
expect(useSessionOrderingStore.getState().rankById.get('session-a')).toBe(activeRank);
observeSessionActivityEvent('session-a', 'settled');
expect(useSessionOrderingStore.getState().rankById.get('session-a')).toBeGreaterThan(activeRank ?? 0);
});
test('treats an active event without a snapshot baseline as a real transition', () => {
observeSessionActivityEvent('session-a', 'active');
expect(useSessionOrderingStore.getState().rankById.has('session-a')).toBe(true);
});
test('seeds the first authoritative snapshot without synthetic promotions', () => {
reconcileSessionActivitySnapshot(['session-a'], ['session-a', 'session-b']);
expect(useSessionOrderingStore.getState().rankById.size).toBe(0);
reconcileSessionActivitySnapshot([], ['session-a', 'session-b']);
expect(useSessionOrderingStore.getState().rankById.has('session-a')).toBe(true);
expect(useSessionOrderingStore.getState().rankById.has('session-b')).toBe(false);
});
test('uses lifecycle rank only within the same parent scope', () => {
const rootOlder = session('root-older', 10);
const rootNewer = session('root-newer', 20);
const childOlder = session('child-older', 10, 'root-older');
const childNewer = session('child-newer', 20, 'root-older');
const otherParentChild = session('other-parent-child', 20, 'root-newer');
const rankById = new Map([
['child-older', 100],
['root-older', 90],
]);
expect(compareSessionsByLifecycleOrder(rootOlder, rootNewer, new Set(), rankById)).toBeLessThan(0);
expect(compareSessionsByLifecycleOrder(childOlder, childNewer, new Set(), rankById)).toBeLessThan(0);
expect(compareSessionsByLifecycleOrder(childOlder, otherParentChild, new Set(), rankById)).toBeGreaterThan(0);
expect(compareSessionsByLifecycleOrder(childOlder, rootNewer, new Set(), rankById)).toBeGreaterThan(0);
});
test('freezes timestamp fallback until a lifecycle transition', () => {
const older = session('older', 10);
const newer = session('newer', 20);
expect(compareSessionsByLifecycleOrder(older, newer, new Set(), new Map())).toBeGreaterThan(0);
const metadataOnlyUpdate = session('older', 30);
expect(compareSessionsByLifecycleOrder(metadataOnlyUpdate, newer, new Set(), new Map())).toBeGreaterThan(0);
expect(compareSessionsByLifecycleOrder(
metadataOnlyUpdate,
newer,
new Set(),
new Map([['older', 40]]),
)).toBeLessThan(0);
});
test('clears lifecycle state when a session is deleted', () => {
observeSessionActivityEvent('session-a', 'active');
removeSessionOrdering('session-a');
expect(useSessionOrderingStore.getState().rankById.has('session-a')).toBe(false);
observeSessionActivityEvent('session-a', 'settled');
expect(useSessionOrderingStore.getState().rankById.has('session-a')).toBe(false);
});
test('sorts each forest scope before flattening parent-first', () => {
const rootOlder = session('root-older', 10);
const rootNewer = session('root-newer', 20);
const childOlder = session('child-older', 5, 'root-older');
const childNewer = session('child-newer', 6, 'root-older');
const ordered = orderSessionsByLifecycleScopes(
[rootNewer, childOlder, rootOlder, childNewer],
new Set(),
new Map([
['root-older', 100],
['child-older', 90],
]),
);
expect(ordered.map((item) => item.id)).toEqual([
'root-older',
'child-older',
'child-newer',
'root-newer',
]);
});
test('orders roots, siblings, orphan parents, and cyclic parent scopes deterministically', () => {
const rootOlder = session('root-older', 10);
const rootNewer = session('root-newer', 20);
const childOlder = session('child-older', 5, 'root-older');
const childNewer = session('child-newer', 6, 'root-older');
const orphanOlder = session('orphan-older', 10, 'missing-parent');
const orphanNewer = session('orphan-newer', 20, 'missing-parent');
const cycleOlder = session('cycle-older', 10, 'cycle-newer');
const cycleNewer = session('cycle-newer', 20, 'cycle-older');
expect(orderSessionsByLifecycleScopes(
[cycleOlder, rootOlder, childOlder, orphanOlder, cycleNewer, rootNewer, childNewer, orphanNewer],
new Set(),
new Map(),
).map((item) => item.id)).toEqual([
'orphan-newer',
'root-newer',
'orphan-older',
'root-older',
'child-newer',
'child-older',
'cycle-newer',
'cycle-older',
]);
});
test('does not promote a root when only its child has lifecycle activity', () => {
const rootOlder = session('root-older', 10);
const rootNewer = session('root-newer', 20);
const activeChild = session('active-child', 5, 'root-older');
const ordered = orderSessionsByLifecycleScopes(
[rootOlder, activeChild, rootNewer],
new Set(),
new Map([['active-child', 100]]),
);
expect(ordered.map((item) => item.id)).toEqual([
'root-newer',
'root-older',
'active-child',
]);
});
test('authoritative snapshot raises frozen baselines without live ranks', () => {
const older = session('older', 10);
const newer = session('newer', 20);
// Freeze both baselines at their first-seen timestamps.
expect(compareSessionsByLifecycleOrder(older, newer, new Set(), new Map())).toBeGreaterThan(0);
// A metadata-only live update must NOT reorder (frozen baseline)...
const liveBump = session('older', 30);
expect(compareSessionsByLifecycleOrder(liveBump, newer, new Set(), new Map())).toBeGreaterThan(0);
// ...but an authoritative snapshot with the newer stamp raises the baseline.
raiseSessionOrderingBaselines([liveBump, newer]);
expect(compareSessionsByLifecycleOrder(liveBump, newer, new Set(), new Map())).toBeLessThan(0);
});
test('store-held stale live rank is raised by an authoritative snapshot', () => {
useSessionOrderingStore.setState({ rankById: new Map([['stale', 15]]) });
raiseSessionOrderingBaselines([session('stale', 40)]);
expect(useSessionOrderingStore.getState().rankById.get('stale')).toBe(40);
});
});