import { beforeEach, describe, expect, it, mock } from 'bun:test'; import type { ExecutionContext, IServiceContainer } from '@stock-bot/types'; import { BaseHandler } from '../src/base/BaseHandler'; // Test handler with metadata class ConfigTestHandler extends BaseHandler { static __handlerName = 'config-test'; static __operations = [ { name: 'process', method: 'processData' }, { name: 'validate', method: 'validateData' }, ]; static __schedules = [ { operation: 'processData', cronPattern: '0 * * * *', priority: 5, immediately: false, description: 'Hourly processing', payload: { type: 'scheduled' }, batch: { size: 100 }, }, ]; static __description = 'Test handler for configuration'; async processData(input: any, context: ExecutionContext) { return { processed: true, input }; } async validateData(input: any, context: ExecutionContext) { return { valid: true, input }; } } // Handler without metadata class NoMetadataHandler extends BaseHandler {} describe('BaseHandler Configuration', () => { let mockServices: IServiceContainer; beforeEach(() => { mockServices = { cache: null, globalCache: null, queueManager: null, proxy: null, browser: null, mongodb: null, postgres: null, questdb: null, } as any; }); describe('createHandlerConfig', () => { it('should create handler config from metadata', () => { const handler = new ConfigTestHandler(mockServices); const config = handler.createHandlerConfig(); expect(config.name).toBe('config-test'); expect(Object.keys(config.operations)).toEqual(['process', 'validate']); expect(config.scheduledJobs).toHaveLength(1); }); it('should create job handlers for operations', () => { const handler = new ConfigTestHandler(mockServices); const config = handler.createHandlerConfig(); expect(typeof config.operations.process).toBe('function'); expect(typeof config.operations.validate).toBe('function'); }); it('should include scheduled job details', () => { const handler = new ConfigTestHandler(mockServices); const config = handler.createHandlerConfig(); const scheduledJob = config.scheduledJobs[0]; expect(scheduledJob.type).toBe('config-test-processData'); expect(scheduledJob.operation).toBe('process'); expect(scheduledJob.cronPattern).toBe('0 * * * *'); expect(scheduledJob.priority).toBe(5); expect(scheduledJob.immediately).toBe(false); expect(scheduledJob.description).toBe('Hourly processing'); expect(scheduledJob.payload).toEqual({ type: 'scheduled' }); expect(scheduledJob.batch).toEqual({ size: 100 }); }); it('should execute operations through job handlers', async () => { const handler = new ConfigTestHandler(mockServices); const config = handler.createHandlerConfig(); // Mock the job execution const processJob = config.operations.process; const result = await processJob({ data: 'test' }, {} as any); expect(result).toEqual({ processed: true, input: { data: 'test' } }); }); it('should throw error when no metadata found', () => { const handler = new NoMetadataHandler(mockServices); expect(() => handler.createHandlerConfig()).toThrow('Handler metadata not found'); }); it('should handle schedule without matching operation', () => { class ScheduleOnlyHandler extends BaseHandler { static __handlerName = 'schedule-only'; static __operations = []; static __schedules = [ { operation: 'nonExistentMethod', cronPattern: '* * * * *', }, ]; } const handler = new ScheduleOnlyHandler(mockServices); const config = handler.createHandlerConfig(); expect(config.operations).toEqual({}); expect(config.scheduledJobs).toHaveLength(1); expect(config.scheduledJobs[0].operation).toBe('nonExistentMethod'); }); it('should handle empty schedules array', () => { class NoScheduleHandler extends BaseHandler { static __handlerName = 'no-schedule'; static __operations = [{ name: 'test', method: 'testMethod' }]; static __schedules = []; testMethod() {} } const handler = new NoScheduleHandler(mockServices); const config = handler.createHandlerConfig(); expect(config.scheduledJobs).toEqual([]); expect(config.operations).toHaveProperty('test'); }); it('should create execution context with proper metadata', async () => { const handler = new ConfigTestHandler(mockServices); const config = handler.createHandlerConfig(); // Spy on execute method const executeSpy = mock(); handler.execute = executeSpy; executeSpy.mockResolvedValue({ result: 'test' }); // Execute through job handler await config.operations.process({ input: 'data' }, {} as any); expect(executeSpy).toHaveBeenCalledWith( 'process', { input: 'data' }, expect.objectContaining({ type: 'queue', metadata: expect.objectContaining({ source: 'queue', timestamp: expect.any(Number), }), }) ); }); }); describe('extractMetadata', () => { it('should extract complete metadata', () => { const metadata = ConfigTestHandler.extractMetadata(); expect(metadata).not.toBeNull(); expect(metadata?.name).toBe('config-test'); expect(metadata?.operations).toEqual(['process', 'validate']); expect(metadata?.description).toBe('Test handler for configuration'); expect(metadata?.scheduledJobs).toHaveLength(1); }); it('should return null for handler without metadata', () => { const metadata = NoMetadataHandler.extractMetadata(); expect(metadata).toBeNull(); }); it('should handle missing optional fields', () => { class MinimalHandler extends BaseHandler { static __handlerName = 'minimal'; static __operations = []; } const metadata = MinimalHandler.extractMetadata(); expect(metadata).not.toBeNull(); expect(metadata?.name).toBe('minimal'); expect(metadata?.operations).toEqual([]); expect(metadata?.scheduledJobs).toEqual([]); expect(metadata?.description).toBeUndefined(); }); it('should map schedule operations correctly', () => { class MappedScheduleHandler extends BaseHandler { static __handlerName = 'mapped'; static __operations = [ { name: 'op1', method: 'method1' }, { name: 'op2', method: 'method2' }, ]; static __schedules = [ { operation: 'method1', cronPattern: '* * * * *' }, { operation: 'method2', cronPattern: '0 * * * *' }, ]; } const metadata = MappedScheduleHandler.extractMetadata(); expect(metadata?.scheduledJobs[0].operation).toBe('op1'); expect(metadata?.scheduledJobs[1].operation).toBe('op2'); }); }); });