added cli-covarage tool and fixed more tests
This commit is contained in:
parent
b63e58784c
commit
b845a8eade
57 changed files with 11917 additions and 295 deletions
|
|
@ -118,6 +118,19 @@ export class HandlerRegistry {
|
|||
return this.handlerServices.get(handlerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all handlers for a specific service
|
||||
*/
|
||||
getServiceHandlers(serviceName: string): HandlerMetadata[] {
|
||||
const handlers: HandlerMetadata[] = [];
|
||||
for (const [handlerName, metadata] of this.handlers) {
|
||||
if (this.handlerServices.get(handlerName) === serviceName || metadata.service === serviceName) {
|
||||
handlers.push(metadata);
|
||||
}
|
||||
}
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scheduled jobs for a handler
|
||||
*/
|
||||
|
|
|
|||
77
libs/core/handler-registry/test/index.test.ts
Normal file
77
libs/core/handler-registry/test/index.test.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { describe, it, expect } from 'bun:test';
|
||||
import * as handlerRegistryExports from '../src';
|
||||
import { HandlerRegistry } from '../src';
|
||||
|
||||
describe('Handler Registry Package Exports', () => {
|
||||
it('should export HandlerRegistry class', () => {
|
||||
expect(handlerRegistryExports.HandlerRegistry).toBeDefined();
|
||||
expect(handlerRegistryExports.HandlerRegistry).toBe(HandlerRegistry);
|
||||
});
|
||||
|
||||
it('should export correct types', () => {
|
||||
// Type tests - compile-time checks
|
||||
type TestHandlerMetadata = handlerRegistryExports.HandlerMetadata;
|
||||
type TestOperationMetadata = handlerRegistryExports.OperationMetadata;
|
||||
type TestScheduleMetadata = handlerRegistryExports.ScheduleMetadata;
|
||||
type TestHandlerConfiguration = handlerRegistryExports.HandlerConfiguration;
|
||||
type TestRegistryStats = handlerRegistryExports.RegistryStats;
|
||||
type TestHandlerDiscoveryResult = handlerRegistryExports.HandlerDiscoveryResult;
|
||||
|
||||
// Runtime type usage tests
|
||||
const testHandler: TestHandlerMetadata = {
|
||||
name: 'TestHandler',
|
||||
serviceName: 'test-service',
|
||||
operations: [],
|
||||
};
|
||||
|
||||
const testOperation: TestOperationMetadata = {
|
||||
operationName: 'testOperation',
|
||||
handlerName: 'TestHandler',
|
||||
operationPath: 'test.operation',
|
||||
serviceName: 'test-service',
|
||||
};
|
||||
|
||||
const testSchedule: TestScheduleMetadata = {
|
||||
handlerName: 'TestHandler',
|
||||
scheduleName: 'test-schedule',
|
||||
expression: '*/5 * * * *',
|
||||
serviceName: 'test-service',
|
||||
};
|
||||
|
||||
const testConfig: TestHandlerConfiguration = {
|
||||
handlerName: 'TestHandler',
|
||||
batchSize: 10,
|
||||
timeout: 5000,
|
||||
retries: 3,
|
||||
};
|
||||
|
||||
const testStats: TestRegistryStats = {
|
||||
totalHandlers: 5,
|
||||
totalOperations: 10,
|
||||
totalSchedules: 3,
|
||||
handlersByService: {
|
||||
'service1': 2,
|
||||
'service2': 3,
|
||||
},
|
||||
};
|
||||
|
||||
const testDiscoveryResult: TestHandlerDiscoveryResult = {
|
||||
handlers: [testHandler],
|
||||
operations: [testOperation],
|
||||
schedules: [testSchedule],
|
||||
configurations: [testConfig],
|
||||
};
|
||||
|
||||
expect(testHandler).toBeDefined();
|
||||
expect(testOperation).toBeDefined();
|
||||
expect(testSchedule).toBeDefined();
|
||||
expect(testConfig).toBeDefined();
|
||||
expect(testStats).toBeDefined();
|
||||
expect(testDiscoveryResult).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create HandlerRegistry instance', () => {
|
||||
const registry = new HandlerRegistry();
|
||||
expect(registry).toBeInstanceOf(HandlerRegistry);
|
||||
});
|
||||
});
|
||||
382
libs/core/handler-registry/test/registry-edge-cases.test.ts
Normal file
382
libs/core/handler-registry/test/registry-edge-cases.test.ts
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
import { beforeEach, describe, expect, it, mock } from 'bun:test';
|
||||
import { HandlerRegistry } from '../src/registry';
|
||||
import type {
|
||||
HandlerConfiguration,
|
||||
HandlerMetadata,
|
||||
OperationMetadata,
|
||||
ScheduleMetadata,
|
||||
} from '../src/types';
|
||||
import type { JobHandler, ScheduledJob } from '@stock-bot/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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue