215 lines
No EOL
7.2 KiB
TypeScript
215 lines
No EOL
7.2 KiB
TypeScript
import { describe, it, expect, beforeEach, mock } from 'bun:test';
|
|
import { BaseHandler } from '../src/base/BaseHandler';
|
|
import type { IServiceContainer, ExecutionContext } from '@stock-bot/types';
|
|
|
|
// 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');
|
|
});
|
|
});
|
|
}); |