tests
This commit is contained in:
parent
54f37f9521
commit
3a7254708e
19 changed files with 1560 additions and 1237 deletions
|
|
@ -2,24 +2,16 @@ 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 type { IServiceContainer } from '@stock-bot/types';
|
||||
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 mockServices: IServiceContainer = {
|
||||
getService: mock(() => null),
|
||||
hasService: mock(() => false),
|
||||
registerService: mock(() => {}),
|
||||
} as any;
|
||||
|
||||
const mockLogger = {
|
||||
info: mock(() => {}),
|
||||
|
|
@ -30,246 +22,78 @@ describe('Auto Registration', () => {
|
|||
|
||||
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,
|
||||
});
|
||||
});
|
||||
mockLogger.info = mock(() => {});
|
||||
mockLogger.error = mock(() => {});
|
||||
mockLogger.warn = mock(() => {});
|
||||
mockLogger.debug = mock(() => {});
|
||||
});
|
||||
|
||||
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',
|
||||
};
|
||||
// Since this function reads from file system, we'll create a temporary directory
|
||||
const result = await autoRegisterHandlers('./non-existent-dir', mockServices, {
|
||||
pattern: '.handler.',
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
// 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();
|
||||
expect(result).toHaveProperty('registered');
|
||||
expect(result).toHaveProperty('failed');
|
||||
expect(Array.isArray(result.registered)).toBe(true);
|
||||
expect(Array.isArray(result.failed)).toBe(true);
|
||||
});
|
||||
|
||||
it('should use default options when not provided', async () => {
|
||||
await expect(
|
||||
autoRegisterHandlers(mockRegistry)
|
||||
).resolves.not.toThrow();
|
||||
const result = await autoRegisterHandlers('./non-existent-dir', mockServices);
|
||||
|
||||
expect(result).toHaveProperty('registered');
|
||||
expect(result).toHaveProperty('failed');
|
||||
});
|
||||
|
||||
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();
|
||||
it('should handle directory not found gracefully', async () => {
|
||||
// This should not throw but return empty results
|
||||
const result = await autoRegisterHandlers('./non-existent-directory', mockServices);
|
||||
|
||||
expect(result.registered).toEqual([]);
|
||||
expect(result.failed).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAutoHandlerRegistry', () => {
|
||||
it('should create a registry function for a service', () => {
|
||||
const registerFunction = createAutoHandlerRegistry('my-service');
|
||||
it('should create a registry with registerDirectory method', () => {
|
||||
const registry = createAutoHandlerRegistry(mockServices);
|
||||
|
||||
expect(typeof registerFunction).toBe('function');
|
||||
|
||||
// Test the created function
|
||||
const result = registerFunction(mockRegistry, mockLogger);
|
||||
expect(result).toBeInstanceOf(Promise);
|
||||
expect(registry).toHaveProperty('registerDirectory');
|
||||
expect(registry).toHaveProperty('registerDirectories');
|
||||
expect(typeof registry.registerDirectory).toBe('function');
|
||||
expect(typeof registry.registerDirectories).toBe('function');
|
||||
});
|
||||
|
||||
it('should pass through custom options', () => {
|
||||
const customOptions = {
|
||||
pattern: '**/*.custom-handler.ts',
|
||||
directory: './custom-handlers',
|
||||
};
|
||||
|
||||
const registerFunction = createAutoHandlerRegistry('my-service', customOptions);
|
||||
it('should register from a directory', async () => {
|
||||
const registry = createAutoHandlerRegistry(mockServices);
|
||||
|
||||
// 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);
|
||||
const result = await registry.registerDirectory('./non-existent-dir', {
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty('registered');
|
||||
expect(result).toHaveProperty('failed');
|
||||
});
|
||||
|
||||
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);
|
||||
it('should register from multiple directories', async () => {
|
||||
const registry = createAutoHandlerRegistry(mockServices);
|
||||
|
||||
// Should extract both, filtering happens during registration
|
||||
expect(handlers).toHaveLength(2);
|
||||
const result = await registry.registerDirectories([
|
||||
'./dir1',
|
||||
'./dir2',
|
||||
], {
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
const disabledMetadata = Reflect.getMetadata('handler', DisabledHandler);
|
||||
expect(disabledMetadata.disabled).toBe(true);
|
||||
expect(result).toHaveProperty('registered');
|
||||
expect(result).toHaveProperty('failed');
|
||||
expect(Array.isArray(result.registered)).toBe(true);
|
||||
expect(Array.isArray(result.failed)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue