import { SandboxContext, ContextData } from '../../src/sandbox/sandbox-context'; describe('SandboxContext', () => { let sandboxContext: SandboxContext; beforeEach(() => { sandboxContext = new SandboxContext(); jest.clearAllMocks(); }); describe('run', () => { it('should execute callback within context', async () => { const contextData: ContextData = { eventId: 'test-event-123', eventType: 'customer_created' }; const callback = jest.fn().mockResolvedValue('test-result'); const result = await sandboxContext.run(contextData, callback); expect(result).toBe('test-result'); expect(callback).toHaveBeenCalledTimes(1); }); it('should create context with eventId and startTime', async () => { const contextData: ContextData = { eventId: 'test-event-456', eventType: 'customer_created' }; let capturedContext: any; await sandboxContext.run(contextData, async () => { capturedContext = sandboxContext.getCurrentContext(); return 'test'; }); expect(capturedContext).toBeDefined(); expect(capturedContext.eventId).toBe('test-event-456'); expect(capturedContext.startTime).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); }); it('should handle missing eventId gracefully', async () => { const contextData: ContextData = { eventId: '', eventType: 'customer_created' }; let capturedContext: any; await sandboxContext.run(contextData, async () => { capturedContext = sandboxContext.getCurrentContext(); return 'test'; }); expect(capturedContext.eventId).toBe('unknown'); }); it('should handle undefined eventId', async () => { const contextData: ContextData = { eventId: undefined as any, eventType: 'customer_created' }; let capturedContext: any; await sandboxContext.run(contextData, async () => { capturedContext = sandboxContext.getCurrentContext(); return 'test'; }); expect(capturedContext.eventId).toBe('unknown'); }); it('should maintain context isolation between concurrent executions', async () => { const contextData1: ContextData = { eventId: 'event-1', eventType: 'customer_created' }; const contextData2: ContextData = { eventId: 'event-2', eventType: 'customer_created' }; const results: string[] = []; const promise1 = sandboxContext.run(contextData1, async () => { await new Promise(resolve => setTimeout(resolve, 10)); const context = sandboxContext.getCurrentContext(); results.push(`Context 1: ${context?.eventId}`); return 'result1'; }); const promise2 = sandboxContext.run(contextData2, async () => { await new Promise(resolve => setTimeout(resolve, 5)); const context = sandboxContext.getCurrentContext(); results.push(`Context 2: ${context?.eventId}`); return 'result2'; }); const [result1, result2] = await Promise.all([promise1, promise2]); expect(result1).toBe('result1'); expect(result2).toBe('result2'); expect(results).toContain('Context 1: event-1'); expect(results).toContain('Context 2: event-2'); }); it('should propagate callback errors', async () => { const contextData: ContextData = { eventId: 'error-event', eventType: 'customer_created' }; const error = new Error('Test error'); const callback = jest.fn().mockRejectedValue(error); await expect(sandboxContext.run(contextData, callback)).rejects.toThrow('Test error'); expect(callback).toHaveBeenCalledTimes(1); }); it('should handle synchronous callbacks', async () => { const contextData: ContextData = { eventId: 'sync-event', eventType: 'customer_created' }; const callback = jest.fn().mockReturnValue('sync-result'); const result = await sandboxContext.run(contextData, callback); expect(result).toBe('sync-result'); expect(callback).toHaveBeenCalledTimes(1); }); }); describe('getCurrentContext', () => { it('should return undefined when not in context', () => { const context = sandboxContext.getCurrentContext(); expect(context).toBeUndefined(); }); it('should return current context when within run', async () => { const contextData: ContextData = { eventId: 'current-context-test', eventType: 'customer_created' }; let contextFromCallback: any; await sandboxContext.run(contextData, async () => { contextFromCallback = sandboxContext.getCurrentContext(); return 'test'; }); expect(contextFromCallback).toBeDefined(); expect(contextFromCallback.eventId).toBe('current-context-test'); expect(contextFromCallback.startTime).toBeDefined(); }); it('should return correct context in nested async operations', async () => { const contextData: ContextData = { eventId: 'nested-async-test', eventType: 'customer_created' }; let nestedContext: any; await sandboxContext.run(contextData, async () => { await new Promise(resolve => { setTimeout(() => { nestedContext = sandboxContext.getCurrentContext(); resolve(undefined); }, 10); }); return 'test'; }); expect(nestedContext).toBeDefined(); expect(nestedContext.eventId).toBe('nested-async-test'); }); }); describe('context data validation', () => { it('should handle null contextData gracefully', async () => { const callback = jest.fn().mockResolvedValue('result'); // The current implementation expects ContextData, so we provide empty object const result = await sandboxContext.run({ eventId: '', eventType: 'customer_created' }, callback); expect(result).toBe('result'); expect(callback).toHaveBeenCalledTimes(1); }); it('should handle empty contextData object', async () => { const contextData = {} as ContextData; let capturedContext: any; await sandboxContext.run(contextData, async () => { capturedContext = sandboxContext.getCurrentContext(); return 'test'; }); expect(capturedContext.eventId).toBe('unknown'); }); it('should preserve eventId when provided', async () => { const contextData: ContextData = { eventId: 'preserve-test-123', eventType: 'customer_created' }; let capturedContext: any; await sandboxContext.run(contextData, async () => { capturedContext = sandboxContext.getCurrentContext(); return 'test'; }); expect(capturedContext.eventId).toBe('preserve-test-123'); }); }); describe('startTime generation', () => { it('should generate valid ISO timestamp for startTime', async () => { const contextData: ContextData = { eventId: 'timestamp-test', eventType: 'customer_created' }; let capturedContext: any; await sandboxContext.run(contextData, async () => { capturedContext = sandboxContext.getCurrentContext(); return 'test'; }); const startTime = capturedContext.startTime; expect(startTime).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); // Verify it's a valid date const date = new Date(startTime); expect(date.getTime()).not.toBeNaN(); }); it('should generate different startTimes for different executions', async () => { const contextData: ContextData = { eventId: 'different-times', eventType: 'customer_created' }; const startTimes: string[] = []; for (let i = 0; i < 3; i++) { // Add delay before each execution to ensure different timestamps if (i > 0) { await new Promise(resolve => setTimeout(resolve, 2)); } await sandboxContext.run(contextData, async () => { const context = sandboxContext.getCurrentContext(); startTimes.push(context!.startTime); return 'test'; }); } // All start times should be different (or at least 2 should be different) // Since timestamp precision might cause some to be the same, we check for at least 2 unique values expect(new Set(startTimes).size).toBeGreaterThanOrEqual(2); }); }); });