import { describe, test, expect, beforeEach, vi } from 'vitest' import { OperationTracker } from '../../src/utils/operationTracker' // Mock uuid vi.mock('uuid', () => ({ v4: vi.fn(() => 'mock-uuid-123') })) describe('OperationTracker', () => { let tracker: OperationTracker beforeEach(() => { tracker = new OperationTracker() vi.clearAllMocks() }) describe('generateOperationId', () => { test('generates unique operation IDs', () => { const id1 = tracker.generateOperationId() const id2 = tracker.generateOperationId() expect(id1).toBe('mock-uuid-123') expect(id2).toBe('mock-uuid-123') // Same because mock always returns same value // In real implementation, these would be different expect(id1).toBeTruthy() expect(id2).toBeTruthy() }) test('returns string IDs', () => { const id = tracker.generateOperationId() expect(typeof id).toBe('string') }) }) describe('addOperation', () => { test('adds operation to tracking set', () => { const operationId = 'test-op-1' tracker.addOperation(operationId) expect(tracker.isOwnOperation(operationId)).toBe(true) }) test('handles multiple operations', () => { tracker.addOperation('op-1') tracker.addOperation('op-2') tracker.addOperation('op-3') expect(tracker.isOwnOperation('op-1')).toBe(true) expect(tracker.isOwnOperation('op-2')).toBe(true) expect(tracker.isOwnOperation('op-3')).toBe(true) }) test('handles duplicate operations gracefully', () => { const operationId = 'duplicate-op' tracker.addOperation(operationId) tracker.addOperation(operationId) // Add same ID again expect(tracker.isOwnOperation(operationId)).toBe(true) }) }) describe('removeOperation', () => { test('removes operation from tracking', () => { const operationId = 'temp-op' tracker.addOperation(operationId) expect(tracker.isOwnOperation(operationId)).toBe(true) tracker.removeOperation(operationId) expect(tracker.isOwnOperation(operationId)).toBe(false) }) test('handles removing non-existent operation', () => { // Should not throw error expect(() => { tracker.removeOperation('non-existent') }).not.toThrow() }) test('removes only specified operation', () => { tracker.addOperation('op-1') tracker.addOperation('op-2') tracker.addOperation('op-3') tracker.removeOperation('op-2') expect(tracker.isOwnOperation('op-1')).toBe(true) expect(tracker.isOwnOperation('op-2')).toBe(false) expect(tracker.isOwnOperation('op-3')).toBe(true) }) }) describe('isOwnOperation', () => { test('returns true for tracked operations', () => { const operationId = 'tracked-op' tracker.addOperation(operationId) expect(tracker.isOwnOperation(operationId)).toBe(true) }) test('returns false for untracked operations', () => { expect(tracker.isOwnOperation('untracked-op')).toBe(false) }) test('returns false after operation is removed', () => { const operationId = 'temp-op' tracker.addOperation(operationId) tracker.removeOperation(operationId) expect(tracker.isOwnOperation(operationId)).toBe(false) }) }) describe('clear', () => { test('removes all tracked operations', () => { tracker.addOperation('op-1') tracker.addOperation('op-2') tracker.addOperation('op-3') tracker.clear() expect(tracker.isOwnOperation('op-1')).toBe(false) expect(tracker.isOwnOperation('op-2')).toBe(false) expect(tracker.isOwnOperation('op-3')).toBe(false) }) test('works with empty tracker', () => { expect(() => tracker.clear()).not.toThrow() }) }) describe('echo suppression scenarios', () => { test('prevents processing own operations', () => { const operationId = tracker.generateOperationId() tracker.addOperation(operationId) // Simulate receiving an event with our operation ID const event = { operationId, data: 'some data' } // Should identify as own operation (skip processing) if (tracker.isOwnOperation(event.operationId)) { // Skip processing expect(true).toBe(true) // Operation should be skipped } else { // Process event expect(false).toBe(true) // Should not reach here } }) test('allows processing external operations', () => { const externalOpId = 'external-op-123' // Simulate receiving an event from another client const event = { operationId: externalOpId, data: 'external data' } // Should not identify as own operation if (!tracker.isOwnOperation(event.operationId)) { // Process event expect(true).toBe(true) // Operation should be processed } else { // Skip processing expect(false).toBe(true) // Should not reach here } }) }) describe('cleanup patterns', () => { test('supports operation cleanup after completion', () => { const operationId = tracker.generateOperationId() tracker.addOperation(operationId) // Simulate operation completion setTimeout(() => { tracker.removeOperation(operationId) }, 100) // Initially tracked expect(tracker.isOwnOperation(operationId)).toBe(true) // After cleanup (would be false after timeout) // Note: In real tests, would use fake timers or promises }) test('handles batch cleanup', () => { const operations = ['op-1', 'op-2', 'op-3', 'op-4', 'op-5'] // Add all operations operations.forEach(op => tracker.addOperation(op)) // Remove specific operations tracker.removeOperation('op-2') tracker.removeOperation('op-4') expect(tracker.isOwnOperation('op-1')).toBe(true) expect(tracker.isOwnOperation('op-2')).toBe(false) expect(tracker.isOwnOperation('op-3')).toBe(true) expect(tracker.isOwnOperation('op-4')).toBe(false) expect(tracker.isOwnOperation('op-5')).toBe(true) }) }) describe('memory management', () => { test('does not accumulate unlimited operations', () => { // Add many operations for (let i = 0; i < 1000; i++) { tracker.addOperation(`op-${i}`) } // Clear to prevent memory leaks tracker.clear() // Verify all cleared expect(tracker.isOwnOperation('op-0')).toBe(false) expect(tracker.isOwnOperation('op-999')).toBe(false) }) test('supports operation TTL pattern', () => { // This test demonstrates a pattern for auto-cleanup const operationWithTTL = (id: string, ttlMs: number) => { tracker.addOperation(id) setTimeout(() => { tracker.removeOperation(id) }, ttlMs) } const opId = 'ttl-op' operationWithTTL(opId, 5000) // 5 second TTL // Initially tracked expect(tracker.isOwnOperation(opId)).toBe(true) // Would be removed after TTL expires }) }) })