275 lines
No EOL
8.3 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
}); |