stock-bot/libs/core/handlers/test/auto-register.test.ts
2025-06-25 09:20:53 -04:00

275 lines
No EOL
8.3 KiB
TypeScript

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);
});
});
});