stock-bot/libs/core/handler-registry/test/registry-edge-cases.test.ts
2025-06-26 16:12:27 -04:00

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([]);
});
});
});