380 lines
11 KiB
TypeScript
380 lines
11 KiB
TypeScript
import { beforeEach, describe, expect, it, mock } from 'bun:test';
|
|
import type { JobHandler, ScheduledJob } from '@stock-bot/types';
|
|
import { HandlerRegistry } from '../src/registry';
|
|
import type {
|
|
HandlerConfiguration,
|
|
HandlerMetadata,
|
|
OperationMetadata,
|
|
ScheduleMetadata,
|
|
} from '../src/types';
|
|
|
|
describe('HandlerRegistry Edge Cases', () => {
|
|
let registry: HandlerRegistry;
|
|
|
|
beforeEach(() => {
|
|
registry = new HandlerRegistry();
|
|
});
|
|
|
|
describe('Metadata Edge Cases', () => {
|
|
it('should handle metadata without service', () => {
|
|
const metadata: HandlerMetadata = {
|
|
name: 'NoServiceHandler',
|
|
operations: [],
|
|
};
|
|
|
|
registry.registerMetadata(metadata);
|
|
|
|
expect(registry.getMetadata('NoServiceHandler')).toEqual(metadata);
|
|
expect(registry.getHandlerService('NoServiceHandler')).toBeUndefined();
|
|
});
|
|
|
|
it('should handle metadata with optional fields', () => {
|
|
const metadata: HandlerMetadata = {
|
|
name: 'FullHandler',
|
|
service: 'test-service',
|
|
operations: [
|
|
{
|
|
name: 'op1',
|
|
method: 'method1',
|
|
description: 'Operation 1',
|
|
},
|
|
],
|
|
schedules: [
|
|
{
|
|
operation: 'op1',
|
|
cronPattern: '*/5 * * * *',
|
|
priority: 10,
|
|
immediately: true,
|
|
description: 'Every 5 minutes',
|
|
},
|
|
],
|
|
version: '1.0.0',
|
|
description: 'Full handler with all fields',
|
|
};
|
|
|
|
registry.registerMetadata(metadata);
|
|
|
|
const retrieved = registry.getMetadata('FullHandler');
|
|
expect(retrieved).toEqual(metadata);
|
|
expect(retrieved?.version).toBe('1.0.0');
|
|
expect(retrieved?.description).toBe('Full handler with all fields');
|
|
expect(retrieved?.schedules?.[0].immediately).toBe(true);
|
|
});
|
|
|
|
it('should handle empty operations array', () => {
|
|
const metadata: HandlerMetadata = {
|
|
name: 'EmptyHandler',
|
|
operations: [],
|
|
};
|
|
|
|
registry.registerMetadata(metadata);
|
|
|
|
const stats = registry.getStats();
|
|
expect(stats.handlers).toBe(1);
|
|
expect(stats.operations).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Configuration Edge Cases', () => {
|
|
it('should handle configuration without scheduled jobs', () => {
|
|
const config: HandlerConfiguration = {
|
|
name: 'SimpleHandler',
|
|
operations: {
|
|
process: mock(async () => {}) as JobHandler,
|
|
},
|
|
};
|
|
|
|
registry.registerConfiguration(config);
|
|
|
|
const scheduledJobs = registry.getScheduledJobs('SimpleHandler');
|
|
expect(scheduledJobs).toEqual([]);
|
|
});
|
|
|
|
it('should handle empty operations object', () => {
|
|
const config: HandlerConfiguration = {
|
|
name: 'EmptyOpsHandler',
|
|
operations: {},
|
|
};
|
|
|
|
registry.registerConfiguration(config);
|
|
|
|
expect(registry.getOperation('EmptyOpsHandler', 'nonexistent')).toBeUndefined();
|
|
});
|
|
|
|
it('should handle configuration with empty scheduled jobs array', () => {
|
|
const config: HandlerConfiguration = {
|
|
name: 'NoScheduleHandler',
|
|
operations: {},
|
|
scheduledJobs: [],
|
|
};
|
|
|
|
registry.registerConfiguration(config);
|
|
|
|
const scheduled = registry.getScheduledJobs('NoScheduleHandler');
|
|
expect(scheduled).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('Service Management Edge Cases', () => {
|
|
it('should update metadata when setting handler service', () => {
|
|
const metadata: HandlerMetadata = {
|
|
name: 'UpdateableHandler',
|
|
operations: [],
|
|
service: 'old-service',
|
|
};
|
|
|
|
registry.registerMetadata(metadata);
|
|
registry.setHandlerService('UpdateableHandler', 'new-service');
|
|
|
|
const updated = registry.getMetadata('UpdateableHandler');
|
|
expect(updated?.service).toBe('new-service');
|
|
expect(registry.getHandlerService('UpdateableHandler')).toBe('new-service');
|
|
});
|
|
|
|
it('should set service for non-existent handler', () => {
|
|
registry.setHandlerService('NonExistentHandler', 'some-service');
|
|
|
|
expect(registry.getHandlerService('NonExistentHandler')).toBe('some-service');
|
|
expect(registry.getMetadata('NonExistentHandler')).toBeUndefined();
|
|
});
|
|
|
|
it('should return empty array for service with no handlers', () => {
|
|
const handlers = registry.getServiceHandlers('non-existent-service');
|
|
expect(handlers).toEqual([]);
|
|
});
|
|
|
|
it('should handle multiple handlers for same service', () => {
|
|
const metadata1: HandlerMetadata = {
|
|
name: 'Handler1',
|
|
service: 'shared-service',
|
|
operations: [],
|
|
};
|
|
const metadata2: HandlerMetadata = {
|
|
name: 'Handler2',
|
|
service: 'shared-service',
|
|
operations: [],
|
|
};
|
|
const metadata3: HandlerMetadata = {
|
|
name: 'Handler3',
|
|
service: 'other-service',
|
|
operations: [],
|
|
};
|
|
|
|
registry.registerMetadata(metadata1);
|
|
registry.registerMetadata(metadata2);
|
|
registry.registerMetadata(metadata3);
|
|
|
|
const sharedHandlers = registry.getServiceHandlers('shared-service');
|
|
expect(sharedHandlers).toHaveLength(2);
|
|
expect(sharedHandlers.map(h => h.name).sort()).toEqual(['Handler1', 'Handler2']);
|
|
});
|
|
});
|
|
|
|
describe('Operation Access Edge Cases', () => {
|
|
it('should return undefined for non-existent handler operation', () => {
|
|
const op = registry.getOperation('NonExistent', 'operation');
|
|
expect(op).toBeUndefined();
|
|
});
|
|
|
|
it('should return undefined for non-existent operation name', () => {
|
|
const config: HandlerConfiguration = {
|
|
name: 'TestHandler',
|
|
operations: {
|
|
exists: mock(async () => {}) as JobHandler,
|
|
},
|
|
};
|
|
|
|
registry.registerConfiguration(config);
|
|
|
|
const op = registry.getOperation('TestHandler', 'notexists');
|
|
expect(op).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('getAllHandlersWithSchedule Edge Cases', () => {
|
|
it('should handle mix of handlers with and without schedules', () => {
|
|
const metadata1: HandlerMetadata = {
|
|
name: 'WithSchedule',
|
|
operations: [],
|
|
};
|
|
const config1: HandlerConfiguration = {
|
|
name: 'WithSchedule',
|
|
operations: {},
|
|
scheduledJobs: [
|
|
{
|
|
name: 'job1',
|
|
handler: mock(async () => {}) as JobHandler,
|
|
pattern: '* * * * *',
|
|
} as ScheduledJob,
|
|
],
|
|
};
|
|
|
|
const metadata2: HandlerMetadata = {
|
|
name: 'WithoutSchedule',
|
|
operations: [],
|
|
};
|
|
const config2: HandlerConfiguration = {
|
|
name: 'WithoutSchedule',
|
|
operations: {},
|
|
};
|
|
|
|
registry.register(metadata1, config1);
|
|
registry.register(metadata2, config2);
|
|
|
|
const allWithSchedule = registry.getAllHandlersWithSchedule();
|
|
expect(allWithSchedule.size).toBe(2);
|
|
|
|
const withSchedule = allWithSchedule.get('WithSchedule');
|
|
expect(withSchedule?.scheduledJobs).toHaveLength(1);
|
|
|
|
const withoutSchedule = allWithSchedule.get('WithoutSchedule');
|
|
expect(withoutSchedule?.scheduledJobs).toEqual([]);
|
|
});
|
|
|
|
it('should handle handler with metadata but no configuration', () => {
|
|
const metadata: HandlerMetadata = {
|
|
name: 'MetadataOnly',
|
|
operations: [],
|
|
};
|
|
|
|
registry.registerMetadata(metadata);
|
|
|
|
const allWithSchedule = registry.getAllHandlersWithSchedule();
|
|
const handler = allWithSchedule.get('MetadataOnly');
|
|
|
|
expect(handler?.metadata).toEqual(metadata);
|
|
expect(handler?.scheduledJobs).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('Import/Export Edge Cases', () => {
|
|
it('should handle empty export', () => {
|
|
const exported = registry.export();
|
|
|
|
expect(exported.handlers).toEqual([]);
|
|
expect(exported.configurations).toEqual([]);
|
|
expect(exported.services).toEqual([]);
|
|
});
|
|
|
|
it('should handle empty import', () => {
|
|
// Add some data first
|
|
registry.registerMetadata({
|
|
name: 'ExistingHandler',
|
|
operations: [],
|
|
});
|
|
|
|
// Import empty data
|
|
registry.import({
|
|
handlers: [],
|
|
configurations: [],
|
|
services: [],
|
|
});
|
|
|
|
expect(registry.getHandlerNames()).toEqual([]);
|
|
});
|
|
|
|
it('should preserve complex data through export/import cycle', () => {
|
|
const metadata: HandlerMetadata = {
|
|
name: 'ComplexHandler',
|
|
service: 'complex-service',
|
|
operations: [
|
|
{ name: 'op1', method: 'method1' },
|
|
{ name: 'op2', method: 'method2' },
|
|
],
|
|
schedules: [
|
|
{
|
|
operation: 'op1',
|
|
cronPattern: '0 * * * *',
|
|
},
|
|
],
|
|
};
|
|
|
|
const handler = mock(async () => {}) as JobHandler;
|
|
const config: HandlerConfiguration = {
|
|
name: 'ComplexHandler',
|
|
operations: {
|
|
op1: handler,
|
|
op2: handler,
|
|
},
|
|
scheduledJobs: [
|
|
{
|
|
name: 'scheduled1',
|
|
handler,
|
|
pattern: '0 * * * *',
|
|
} as ScheduledJob,
|
|
],
|
|
};
|
|
|
|
registry.register(metadata, config);
|
|
registry.setHandlerService('ComplexHandler', 'overridden-service');
|
|
|
|
const exported = registry.export();
|
|
|
|
// Create new registry and import
|
|
const newRegistry = new HandlerRegistry();
|
|
newRegistry.import(exported);
|
|
|
|
expect(newRegistry.getMetadata('ComplexHandler')).toEqual(metadata);
|
|
expect(newRegistry.getConfiguration('ComplexHandler')).toEqual(config);
|
|
expect(newRegistry.getHandlerService('ComplexHandler')).toBe('overridden-service');
|
|
});
|
|
});
|
|
|
|
describe('Statistics Edge Cases', () => {
|
|
it('should count schedules from metadata', () => {
|
|
const metadata: HandlerMetadata = {
|
|
name: 'ScheduledHandler',
|
|
operations: [{ name: 'op1', method: 'method1' }],
|
|
schedules: [
|
|
{ operation: 'op1', cronPattern: '* * * * *' },
|
|
{ operation: 'op1', cronPattern: '0 * * * *' },
|
|
],
|
|
};
|
|
|
|
registry.registerMetadata(metadata);
|
|
|
|
const stats = registry.getStats();
|
|
expect(stats.handlers).toBe(1);
|
|
expect(stats.operations).toBe(1);
|
|
expect(stats.scheduledJobs).toBe(2);
|
|
expect(stats.services).toBe(0); // No service specified
|
|
});
|
|
|
|
it('should not double count services', () => {
|
|
registry.registerMetadata({
|
|
name: 'Handler1',
|
|
service: 'service1',
|
|
operations: [],
|
|
});
|
|
|
|
registry.registerMetadata({
|
|
name: 'Handler2',
|
|
service: 'service1', // Same service
|
|
operations: [],
|
|
});
|
|
|
|
registry.registerMetadata({
|
|
name: 'Handler3',
|
|
service: 'service2',
|
|
operations: [],
|
|
});
|
|
|
|
const stats = registry.getStats();
|
|
expect(stats.services).toBe(2); // Only 2 unique services
|
|
});
|
|
});
|
|
|
|
describe('Error Scenarios', () => {
|
|
it('should handle undefined values gracefully', () => {
|
|
expect(registry.getMetadata(undefined as any)).toBeUndefined();
|
|
expect(registry.getConfiguration(undefined as any)).toBeUndefined();
|
|
expect(registry.getOperation(undefined as any, 'op')).toBeUndefined();
|
|
expect(registry.hasHandler(undefined as any)).toBe(false);
|
|
});
|
|
|
|
it('should handle null service lookup', () => {
|
|
const handlers = registry.getServiceHandlers(null as any);
|
|
expect(handlers).toEqual([]);
|
|
});
|
|
});
|
|
});
|