import { describe, expect, it, beforeEach, mock } from 'bun:test'; import { autoRegisterHandlers, createAutoHandlerRegistry, findHandlerFiles, extractHandlerClasses, } from '../src/registry/auto-register'; import type { HandlerRegistry } from '@stock-bot/handler-registry'; import { Handler, Operation } from '../src/decorators/decorators'; describe('Auto Registration', () => { const mockRegistry: HandlerRegistry = { registerHandler: mock(() => {}), getHandler: mock(() => null), hasHandler: mock(() => false), getAllHandlers: mock(() => []), getHandlersByService: mock(() => []), getHandlerOperations: mock(() => []), hasOperation: mock(() => false), executeOperation: mock(async () => ({ result: 'mocked' })), clear: mock(() => {}), }; const mockLogger = { info: mock(() => {}), error: mock(() => {}), warn: mock(() => {}), debug: mock(() => {}), }; beforeEach(() => { // Reset all mocks Object.values(mockRegistry).forEach(method => { if (typeof method === 'function' && 'mockClear' in method) { (method as any).mockClear(); } }); Object.values(mockLogger).forEach(method => { if (typeof method === 'function' && 'mockClear' in method) { (method as any).mockClear(); } }); }); describe('findHandlerFiles', () => { it('should find handler files matching default pattern', async () => { // This test would need actual file system or mocking // For now, we'll test the function exists and returns an array const files = await findHandlerFiles(); expect(Array.isArray(files)).toBe(true); }); it('should find handler files with custom pattern', async () => { const files = await findHandlerFiles('**/*.handler.ts'); expect(Array.isArray(files)).toBe(true); }); it('should find handler files in specific directory', async () => { const files = await findHandlerFiles('*.handler.ts', './src/handlers'); expect(Array.isArray(files)).toBe(true); }); }); describe('extractHandlerClasses', () => { it('should extract handler classes from module', () => { @Handler('TestHandler1') class Handler1 { @Operation('op1') async operation1() {} } @Handler('TestHandler2') class Handler2 { @Operation('op2') async operation2() {} } class NotAHandler { async someMethod() {} } const testModule = { Handler1, Handler2, NotAHandler, someFunction: () => {}, someValue: 123, }; const handlers = extractHandlerClasses(testModule); expect(handlers).toHaveLength(2); expect(handlers.map(h => h.name)).toContain('Handler1'); expect(handlers.map(h => h.name)).toContain('Handler2'); }); it('should handle modules with no handlers', () => { const testModule = { SomeClass: class {}, someFunction: () => {}, someValue: 'test', }; const handlers = extractHandlerClasses(testModule); expect(handlers).toHaveLength(0); }); it('should handle empty modules', () => { const handlers = extractHandlerClasses({}); expect(handlers).toHaveLength(0); }); it('should extract handlers with metadata', () => { @Handler('MetadataHandler') class HandlerWithMetadata { @Operation('process') async process() {} } const testModule = { HandlerWithMetadata }; const handlers = extractHandlerClasses(testModule); expect(handlers).toHaveLength(1); const handlerClass = handlers[0]; const metadata = Reflect.getMetadata('handler', handlerClass); expect(metadata).toEqual({ name: 'MetadataHandler', disabled: false, }); }); }); describe('autoRegisterHandlers', () => { it('should auto-register handlers', async () => { // Since this function reads from file system, we'll test its behavior // by mocking the registry calls const options = { pattern: '**/*.handler.ts', directory: './test-handlers', serviceName: 'test-service', }; // This would normally scan files and register handlers // For unit testing, we'll verify the function executes without errors await expect( autoRegisterHandlers(mockRegistry, options, mockLogger) ).resolves.not.toThrow(); }); it('should use default options when not provided', async () => { await expect( autoRegisterHandlers(mockRegistry) ).resolves.not.toThrow(); }); it('should handle registration errors gracefully', async () => { mockRegistry.registerHandler = mock(() => { throw new Error('Registration failed'); }); // Should not throw, but log errors await expect( autoRegisterHandlers(mockRegistry, {}, mockLogger) ).resolves.not.toThrow(); }); }); describe('createAutoHandlerRegistry', () => { it('should create a registry function for a service', () => { const registerFunction = createAutoHandlerRegistry('my-service'); expect(typeof registerFunction).toBe('function'); // Test the created function const result = registerFunction(mockRegistry, mockLogger); expect(result).toBeInstanceOf(Promise); }); it('should pass through custom options', () => { const customOptions = { pattern: '**/*.custom-handler.ts', directory: './custom-handlers', }; const registerFunction = createAutoHandlerRegistry('my-service', customOptions); // The function should be created with merged options expect(typeof registerFunction).toBe('function'); }); it('should use service name in registration', async () => { @Handler('ServiceHandler') class TestServiceHandler { @Operation('serviceOp') async operation() {} } // Mock file discovery to return our test handler const mockFindFiles = mock(async () => ['test.handler.ts']); const mockExtract = mock(() => [TestServiceHandler]); const registerFunction = createAutoHandlerRegistry('test-service'); // Execute registration await registerFunction(mockRegistry, mockLogger); // Verify service name would be used in actual implementation expect(typeof registerFunction).toBe('function'); }); }); describe('integration scenarios', () => { it('should handle complex handler hierarchies', () => { @Handler('BaseHandler') class BaseTestHandler { @Operation('baseOp') async baseOperation() {} } @Handler('DerivedHandler') class DerivedTestHandler extends BaseTestHandler { @Operation('derivedOp') async derivedOperation() {} } const testModule = { BaseTestHandler, DerivedTestHandler, }; const handlers = extractHandlerClasses(testModule); expect(handlers).toHaveLength(2); // Both should be valid handler classes handlers.forEach(handlerClass => { const metadata = Reflect.getMetadata('handler', handlerClass); expect(metadata).toBeDefined(); expect(metadata.disabled).toBe(false); }); }); it('should skip disabled handlers if needed', () => { @Handler('EnabledHandler') class EnabledHandler { @Operation('op1') async operation() {} } @Handler('DisabledHandler') class DisabledHandler { @Operation('op2') async operation() {} } // Mark as disabled Reflect.defineMetadata('handler', { name: 'DisabledHandler', disabled: true }, DisabledHandler); const testModule = { EnabledHandler, DisabledHandler, }; const handlers = extractHandlerClasses(testModule); // Should extract both, filtering happens during registration expect(handlers).toHaveLength(2); const disabledMetadata = Reflect.getMetadata('handler', DisabledHandler); expect(disabledMetadata.disabled).toBe(true); }); }); });