import { describe, it } from 'node:test'; import assert from 'node:assert'; import type { BeadIssueWithProject, BeadDependency } from '../../src/lib/types'; import { diffSnapshots } from '../../src/lib/snapshot-differ'; const MOCK_PROJECT = { key: 'proj-1', root: 'C:\\test', displayPath: 'test', name: 'Test Project', source: 'local' as const, addedAt: null, }; function createMockIssue(id: string, overrides: Partial = {}): BeadIssueWithProject { return { id, title: `Title ${id}`, description: null, status: 'open', priority: 2, issue_type: 'task', assignee: null, templateId: null, owner: 'owner', labels: [], dependencies: [], created_at: '2026-02-13T00:00:00Z', updated_at: '2026-02-13T00:00:00Z', closed_at: null, close_reason: null, closed_by_session: null, created_by: 'creator', due_at: null, estimated_minutes: null, external_ref: null, metadata: {}, project: MOCK_PROJECT, ...overrides, }; } describe('Snapshot Differ Stress Tests', () => { describe('HIGH-FREQUENCY BURSTS', () => { it('should capture only final diff when status changes multiple times (simulated burst)', () => { const prev = [createMockIssue('bb-1', { status: 'open' })]; const curr = [createMockIssue('bb-1', { status: 'closed' })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'closed'); assert.strictEqual(events[0].payload.from, 'open'); assert.strictEqual(events[0].payload.to, 'closed'); }); it('should handle rapid status oscillation (open -> in_progress -> blocked -> in_progress)', () => { const prev = [createMockIssue('bb-1', { status: 'open' })]; const curr = [createMockIssue('bb-1', { status: 'in_progress' })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'status_changed'); }); }); describe('BATCH MUTATIONS', () => { it('should handle 50+ beads all changing status simultaneously', () => { const prev: BeadIssueWithProject[] = []; const curr: BeadIssueWithProject[] = []; for (let i = 1; i <= 50; i++) { curr.push(createMockIssue(`bb-${i}`, { status: 'open' })); } const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 50); events.forEach(e => { assert.strictEqual(e.kind, 'created'); }); }); it('should handle 100 beads with mixed status transitions', () => { const prev: BeadIssueWithProject[] = []; const curr: BeadIssueWithProject[] = []; for (let i = 1; i <= 100; i++) { if (i <= 50) { prev.push(createMockIssue(`bb-${i}`, { status: 'open' })); curr.push(createMockIssue(`bb-${i}`, { status: 'in_progress' })); } else { curr.push(createMockIssue(`bb-${i}`, { status: 'open' })); } } const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 100); const statusChanged = events.filter(e => e.kind === 'status_changed'); const created = events.filter(e => e.kind === 'created'); assert.strictEqual(statusChanged.length, 50); assert.strictEqual(created.length, 50); }); }); describe('COMPLEX PERMUTATIONS', () => { it('should emit events for status + assignee + priority + labels all changing on same bead', () => { const prev = [createMockIssue('bb-1', { status: 'open', assignee: null, priority: 2, labels: ['bug'], })]; const curr = [createMockIssue('bb-1', { status: 'in_progress', assignee: 'alice', priority: 1, labels: ['bug', 'urgent'], })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 4); const kinds = events.map(e => e.kind); assert.ok(kinds.includes('status_changed')); assert.ok(kinds.includes('assignee_changed')); assert.ok(kinds.includes('priority_changed')); assert.ok(kinds.includes('labels_changed')); }); it('should handle all trackable fields changing simultaneously', () => { const prev = [createMockIssue('bb-1', { status: 'open', title: 'Old Title', description: null, priority: 3, issue_type: 'task', assignee: null, labels: [], dependencies: [], due_at: null, estimated_minutes: null, })]; const curr = [createMockIssue('bb-1', { status: 'in_progress', title: 'New Title', description: 'New description', priority: 1, issue_type: 'feature', assignee: 'bob', labels: ['enhancement'], dependencies: [{ type: 'blocks', target: 'bb-2' }], due_at: '2026-02-20T00:00:00Z', estimated_minutes: 120, })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 10); const kinds = events.map(e => e.kind); assert.ok(kinds.includes('status_changed')); assert.ok(kinds.includes('title_changed')); assert.ok(kinds.includes('description_changed')); assert.ok(kinds.includes('priority_changed')); assert.ok(kinds.includes('type_changed')); assert.ok(kinds.includes('assignee_changed')); assert.ok(kinds.includes('labels_changed')); assert.ok(kinds.includes('dependency_added')); assert.ok(kinds.includes('due_date_changed')); assert.ok(kinds.includes('estimate_changed')); }); }); describe('NULL SAFETY', () => { it('should NOT crash when labels is null in prev (DOCUMENTS BUG - will throw TypeError)', () => { const prev = [createMockIssue('bb-1', { labels: null as unknown as string[] })]; const curr = [createMockIssue('bb-1', { labels: ['bug'] })]; assert.throws(() => { diffSnapshots(prev, curr); }, /TypeError|Cannot read properties of null/); }); it('should NOT crash when labels is null in curr (DOCUMENTS BUG - will throw TypeError)', () => { const prev = [createMockIssue('bb-1', { labels: ['bug'] })]; const curr = [createMockIssue('bb-1', { labels: null as unknown as string[] })]; assert.throws(() => { diffSnapshots(prev, curr); }, /TypeError|Cannot read properties of null/); }); it('should NOT crash when dependencies is null (DOCUMENTS BUG - will throw TypeError)', () => { const prev = [createMockIssue('bb-1', { dependencies: null as unknown as BeadDependency[] })]; const curr = [createMockIssue('bb-1', { dependencies: [] })]; assert.throws(() => { diffSnapshots(prev, curr); }, /TypeError|Cannot read properties of null/); }); it('should NOT crash when dependencies is undefined in curr (DOCUMENTS BUG - will throw TypeError)', () => { const prev = [createMockIssue('bb-1', { dependencies: [] })]; const curr = [createMockIssue('bb-1', { dependencies: undefined as unknown as BeadDependency[] })]; assert.throws(() => { diffSnapshots(prev, curr); }, /TypeError|Cannot read properties of undefined/); }); }); describe('EMPTY ARRAYS', () => { it('should not emit event when labels goes from empty to empty', () => { const prev = [createMockIssue('bb-1', { labels: [] })]; const curr = [createMockIssue('bb-1', { labels: [] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); it('should emit labels_changed when labels goes from empty to non-empty', () => { const prev = [createMockIssue('bb-1', { labels: [] })]; const curr = [createMockIssue('bb-1', { labels: ['bug'] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'labels_changed'); }); it('should emit labels_changed when labels goes from non-empty to empty', () => { const prev = [createMockIssue('bb-1', { labels: ['bug'] })]; const curr = [createMockIssue('bb-1', { labels: [] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'labels_changed'); }); it('should not emit event when dependencies goes from empty to empty', () => { const prev = [createMockIssue('bb-1', { dependencies: [] })]; const curr = [createMockIssue('bb-1', { dependencies: [] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); it('should emit dependency_added when dependencies goes from empty to non-empty', () => { const prev = [createMockIssue('bb-1', { dependencies: [] })]; const curr = [createMockIssue('bb-1', { dependencies: [{ type: 'blocks', target: 'bb-2' }] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'dependency_added'); }); it('should emit dependency_removed when dependencies goes from non-empty to empty', () => { const prev = [createMockIssue('bb-1', { dependencies: [{ type: 'blocks', target: 'bb-2' }] })]; const curr = [createMockIssue('bb-1', { dependencies: [] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'dependency_removed'); }); }); describe('DELETION DETECTION', () => { it('should NOT emit any event when issue is deleted (DOCUMENTS CURRENT BEHAVIOR - no deletion event)', () => { const prev = [createMockIssue('bb-1')]; const curr: BeadIssueWithProject[] = []; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); it('should NOT emit deletion event when multiple issues are removed', () => { const prev = [ createMockIssue('bb-1'), createMockIssue('bb-2'), createMockIssue('bb-3'), ]; const curr = [createMockIssue('bb-2')]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); it('should still emit created events for new issues even when others are deleted', () => { const prev = [createMockIssue('bb-1')]; const curr = [createMockIssue('bb-2')]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'created'); assert.strictEqual(events[0].beadId, 'bb-2'); }); }); describe('LABELS ORDER INDEPENDENCE', () => { it('should NOT emit labels_changed when same labels are in different order', () => { const prev = [createMockIssue('bb-1', { labels: ['bug', 'ui', 'urgent'] })]; const curr = [createMockIssue('bb-1', { labels: ['urgent', 'bug', 'ui'] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); it('should NOT emit labels_changed for single label (trivially same order)', () => { const prev = [createMockIssue('bb-1', { labels: ['bug'] })]; const curr = [createMockIssue('bb-1', { labels: ['bug'] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); it('should handle large label sets with shuffled order', () => { const labels = Array.from({ length: 20 }, (_, i) => `label-${i}`); const shuffled = [...labels].reverse(); const prev = [createMockIssue('bb-1', { labels })]; const curr = [createMockIssue('bb-1', { labels: shuffled })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); }); describe('DUPLICATE LABELS', () => { it('should NOT emit event when labels have same duplicates in same order', () => { const prev = [createMockIssue('bb-1', { labels: ['bug', 'bug', 'ui'] })]; const curr = [createMockIssue('bb-1', { labels: ['bug', 'bug', 'ui'] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); it('should NOT emit event when labels have same duplicates in different order', () => { const prev = [createMockIssue('bb-1', { labels: ['bug', 'bug', 'ui'] })]; const curr = [createMockIssue('bb-1', { labels: ['ui', 'bug', 'bug'] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 0); }); it('should emit event when duplicate count differs', () => { const prev = [createMockIssue('bb-1', { labels: ['bug', 'bug'] })]; const curr = [createMockIssue('bb-1', { labels: ['bug'] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'labels_changed'); }); it('should emit event when new duplicate is added', () => { const prev = [createMockIssue('bb-1', { labels: ['bug'] })]; const curr = [createMockIssue('bb-1', { labels: ['bug', 'bug'] })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); assert.strictEqual(events[0].kind, 'labels_changed'); }); }); describe('EDGE CASES', () => { it('should handle null previous snapshot (first load)', () => { const curr = [createMockIssue('bb-1'), createMockIssue('bb-2')]; const events = diffSnapshots(null, curr); assert.strictEqual(events.length, 2); events.forEach(e => { assert.strictEqual(e.kind, 'created'); }); }); it('should handle empty previous and current snapshots', () => { const events = diffSnapshots([], []); assert.strictEqual(events.length, 0); }); it('should handle same issue in both snapshots with no changes', () => { const issue = createMockIssue('bb-1'); const events = diffSnapshots([issue], [issue]); assert.strictEqual(events.length, 0); }); it('should handle multiple dependency changes on same bead', () => { const prev = [createMockIssue('bb-1', { dependencies: [ { type: 'blocks', target: 'bb-2' }, { type: 'relates_to', target: 'bb-3' }, ], })]; const curr = [createMockIssue('bb-1', { dependencies: [ { type: 'blocks', target: 'bb-4' }, ], })]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 3); const kinds = events.map(e => e.kind); assert.ok(kinds.includes('dependency_added')); assert.ok(kinds.filter(k => k === 'dependency_removed').length === 2); }); it('should generate valid UUIDs for all events', () => { const prev: BeadIssueWithProject[] = []; const curr = [createMockIssue('bb-1')]; const events = diffSnapshots(prev, curr); assert.strictEqual(events.length, 1); const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; assert.match(events[0].id, uuidRegex); }); it('should include correct project info in events', () => { const prev: BeadIssueWithProject[] = []; const curr = [createMockIssue('bb-1')]; const events = diffSnapshots(prev, curr); assert.strictEqual(events[0].projectId, 'proj-1'); assert.strictEqual(events[0].projectName, 'Test Project'); }); it('should compute actor from assignee or owner or created_by', () => { const events1 = diffSnapshots([], [createMockIssue('bb-1', { assignee: 'alice', owner: 'bob', created_by: 'charlie' })]); assert.strictEqual(events1[0].actor, 'alice'); const events2 = diffSnapshots([], [createMockIssue('bb-1', { assignee: null, owner: 'bob', created_by: 'charlie' })]); assert.strictEqual(events2[0].actor, 'bob'); const events3 = diffSnapshots([], [createMockIssue('bb-1', { assignee: null, owner: null, created_by: 'charlie' })]); assert.strictEqual(events3[0].actor, 'charlie'); }); }); });