tests
This commit is contained in:
parent
54f37f9521
commit
3a7254708e
19 changed files with 1560 additions and 1237 deletions
|
|
@ -1,356 +1,446 @@
|
|||
import { describe, expect, it, beforeEach, mock } from 'bun:test';
|
||||
import { BaseHandler, ScheduledHandler } from '../src/base/BaseHandler';
|
||||
import type { IServiceContainer, ExecutionContext } from '@stock-bot/types';
|
||||
|
||||
describe('BaseHandler', () => {
|
||||
let mockServices: IServiceContainer;
|
||||
let mockContext: ExecutionContext;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockQueue = {
|
||||
add: mock(async () => ({ id: 'job-456' })),
|
||||
getName: mock(() => 'test-queue'),
|
||||
};
|
||||
|
||||
mockServices = {
|
||||
cache: {
|
||||
get: mock(async () => null),
|
||||
set: mock(async () => {}),
|
||||
del: mock(async () => {}),
|
||||
clear: mock(async () => {}),
|
||||
has: mock(async () => false),
|
||||
keys: mock(async () => []),
|
||||
ttl: mock(async () => -1),
|
||||
type: 'memory',
|
||||
} as any,
|
||||
globalCache: {
|
||||
get: mock(async () => null),
|
||||
set: mock(async () => {}),
|
||||
del: mock(async () => {}),
|
||||
clear: mock(async () => {}),
|
||||
has: mock(async () => false),
|
||||
keys: mock(async () => []),
|
||||
ttl: mock(async () => -1),
|
||||
type: 'memory',
|
||||
} as any,
|
||||
queueManager: {
|
||||
getQueue: mock(() => mockQueue),
|
||||
createQueue: mock(() => mockQueue),
|
||||
hasQueue: mock(() => true),
|
||||
sendToQueue: mock(async () => 'job-123'),
|
||||
} as any,
|
||||
proxy: {
|
||||
getProxy: mock(() => ({ host: 'proxy.example.com', port: 8080 })),
|
||||
} as any,
|
||||
browser: {
|
||||
scrape: mock(async () => ({ data: 'scraped' })),
|
||||
} as any,
|
||||
mongodb: {
|
||||
db: mock(() => ({
|
||||
collection: mock(() => ({
|
||||
find: mock(() => ({ toArray: mock(async () => []) })),
|
||||
insertOne: mock(async () => ({ insertedId: 'id-123' })),
|
||||
})),
|
||||
})),
|
||||
} as any,
|
||||
postgres: {
|
||||
query: mock(async () => ({ rows: [] })),
|
||||
} as any,
|
||||
questdb: null,
|
||||
logger: {
|
||||
info: mock(() => {}),
|
||||
error: mock(() => {}),
|
||||
warn: mock(() => {}),
|
||||
debug: mock(() => {}),
|
||||
} as any,
|
||||
queue: mockQueue as any,
|
||||
};
|
||||
|
||||
mockContext = {
|
||||
logger: mockServices.logger,
|
||||
traceId: 'trace-123',
|
||||
handlerName: 'TestHandler',
|
||||
operationName: 'testOperation',
|
||||
metadata: {},
|
||||
startTime: new Date(),
|
||||
container: {
|
||||
resolve: mock((name: string) => mockServices[name as keyof IServiceContainer]),
|
||||
resolveAsync: mock(async (name: string) => mockServices[name as keyof IServiceContainer]),
|
||||
},
|
||||
} as any;
|
||||
|
||||
// Reset all mocks
|
||||
Object.values(mockServices).forEach(service => {
|
||||
if (service && typeof service === 'object') {
|
||||
Object.values(service).forEach(method => {
|
||||
if (typeof method === 'function' && 'mockClear' in method) {
|
||||
(method as any).mockClear();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
class TestHandler extends BaseHandler {
|
||||
constructor() {
|
||||
super(mockServices, 'TestHandler');
|
||||
}
|
||||
|
||||
async testOperation(data: any) {
|
||||
return { processed: data };
|
||||
}
|
||||
}
|
||||
|
||||
describe('service access', () => {
|
||||
it('should provide access to cache service', async () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
await handler.cache.set('key', 'value');
|
||||
|
||||
expect(mockServices.cache.set).toHaveBeenCalledWith('key', 'value');
|
||||
});
|
||||
|
||||
it('should have logger initialized', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
expect(handler.logger).toBeDefined();
|
||||
// Logger is created by getLogger, not from mockServices
|
||||
});
|
||||
|
||||
it('should provide access to queue service', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
expect(handler.queue).toBeDefined();
|
||||
expect(handler.queue.getName()).toBe('test-queue');
|
||||
});
|
||||
|
||||
it('should provide access to mongodb', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
expect(handler.mongodb).toBe(mockServices.mongodb);
|
||||
});
|
||||
|
||||
it('should provide access to postgres', async () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const result = await handler.postgres.query('SELECT 1');
|
||||
|
||||
expect(result.rows).toEqual([]);
|
||||
expect(mockServices.postgres.query).toHaveBeenCalledWith('SELECT 1');
|
||||
});
|
||||
|
||||
it('should provide access to browser', async () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const result = await handler.browser.scrape('https://example.com');
|
||||
|
||||
expect(result).toEqual({ data: 'scraped' });
|
||||
expect(mockServices.browser.scrape).toHaveBeenCalledWith('https://example.com');
|
||||
});
|
||||
|
||||
it('should provide access to proxy manager', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const proxy = handler.proxy.getProxy();
|
||||
|
||||
expect(proxy).toEqual({ host: 'proxy.example.com', port: 8080 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('caching utilities', () => {
|
||||
it('should generate handler-specific cache keys', async () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const key = await handler.cacheKey(mockContext, 'user', '123');
|
||||
expect(key).toMatch(/TestHandler:user:123$/);
|
||||
});
|
||||
|
||||
it('should cache handler results', async () => {
|
||||
const handler = new TestHandler();
|
||||
const operation = mock(async () => ({ result: 'data' }));
|
||||
|
||||
// First call - executes operation
|
||||
const result1 = await handler.cacheHandler(
|
||||
mockContext,
|
||||
'test-cache',
|
||||
operation,
|
||||
{ ttl: 300 }
|
||||
);
|
||||
|
||||
expect(result1).toEqual({ result: 'data' });
|
||||
expect(operation).toHaveBeenCalledTimes(1);
|
||||
expect(mockServices.cache.set).toHaveBeenCalled();
|
||||
|
||||
// Mock cache hit
|
||||
mockServices.cache.get = mock(async () => ({ result: 'data' }));
|
||||
|
||||
// Second call - returns cached result
|
||||
const result2 = await handler.cacheHandler(
|
||||
mockContext,
|
||||
'test-cache',
|
||||
operation,
|
||||
{ ttl: 300 }
|
||||
);
|
||||
|
||||
expect(result2).toEqual({ result: 'data' });
|
||||
expect(operation).toHaveBeenCalledTimes(1); // Not called again
|
||||
});
|
||||
});
|
||||
|
||||
describe('scheduling', () => {
|
||||
it('should schedule operations', async () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const jobId = await handler.scheduleOperation(
|
||||
mockContext,
|
||||
'processData',
|
||||
{ data: 'test' },
|
||||
{ delay: 5000 }
|
||||
);
|
||||
|
||||
expect(jobId).toBe('job-123');
|
||||
expect(mockServices.queueManager.sendToQueue).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('HTTP client', () => {
|
||||
it('should provide axios instance', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const http = handler.http(mockContext);
|
||||
expect(http).toBeDefined();
|
||||
expect(http.get).toBeDefined();
|
||||
expect(http.post).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handler metadata', () => {
|
||||
it('should extract handler metadata', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const metadata = handler.getHandlerMetadata();
|
||||
expect(metadata).toBeDefined();
|
||||
expect(metadata.name).toBe('TestHandler');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lifecycle hooks', () => {
|
||||
class LifecycleHandler extends BaseHandler {
|
||||
onInitCalled = false;
|
||||
onStartCalled = false;
|
||||
onStopCalled = false;
|
||||
onDisposeCalled = false;
|
||||
|
||||
constructor() {
|
||||
super(mockServices, 'LifecycleHandler');
|
||||
}
|
||||
|
||||
async onInit() {
|
||||
this.onInitCalled = true;
|
||||
}
|
||||
|
||||
async onStart() {
|
||||
this.onStartCalled = true;
|
||||
}
|
||||
|
||||
async onStop() {
|
||||
this.onStopCalled = true;
|
||||
}
|
||||
|
||||
async onDispose() {
|
||||
this.onDisposeCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
it('should call lifecycle hooks', async () => {
|
||||
const handler = new LifecycleHandler();
|
||||
|
||||
await handler.onInit();
|
||||
expect(handler.onInitCalled).toBe(true);
|
||||
|
||||
await handler.onStart();
|
||||
expect(handler.onStartCalled).toBe(true);
|
||||
|
||||
await handler.onStop();
|
||||
expect(handler.onStopCalled).toBe(true);
|
||||
|
||||
await handler.onDispose();
|
||||
expect(handler.onDisposeCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ScheduledHandler', () => {
|
||||
const mockServices: IServiceContainer = {
|
||||
cache: { type: 'memory' } as any,
|
||||
globalCache: { type: 'memory' } as any,
|
||||
queueManager: { getQueue: () => null } as any,
|
||||
proxy: null,
|
||||
browser: null,
|
||||
mongodb: null,
|
||||
postgres: null,
|
||||
questdb: null,
|
||||
logger: null as any,
|
||||
queue: null as any,
|
||||
};
|
||||
|
||||
class TestScheduledHandler extends ScheduledHandler {
|
||||
constructor() {
|
||||
super(mockServices, 'TestScheduledHandler');
|
||||
}
|
||||
|
||||
getScheduledJobs() {
|
||||
return [
|
||||
{
|
||||
name: 'dailyJob',
|
||||
schedule: '0 0 * * *',
|
||||
handler: 'processDailyData',
|
||||
},
|
||||
{
|
||||
name: 'hourlyJob',
|
||||
schedule: '0 * * * *',
|
||||
handler: 'processHourlyData',
|
||||
options: {
|
||||
timezone: 'UTC',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async processDailyData() {
|
||||
return { processed: 'daily' };
|
||||
}
|
||||
|
||||
async processHourlyData() {
|
||||
return { processed: 'hourly' };
|
||||
}
|
||||
}
|
||||
|
||||
it('should define scheduled jobs', () => {
|
||||
const handler = new TestScheduledHandler();
|
||||
|
||||
const jobs = handler.getScheduledJobs();
|
||||
|
||||
expect(jobs).toHaveLength(2);
|
||||
expect(jobs[0]).toEqual({
|
||||
name: 'dailyJob',
|
||||
schedule: '0 0 * * *',
|
||||
handler: 'processDailyData',
|
||||
});
|
||||
expect(jobs[1]).toEqual({
|
||||
name: 'hourlyJob',
|
||||
schedule: '0 * * * *',
|
||||
handler: 'processHourlyData',
|
||||
options: {
|
||||
timezone: 'UTC',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should be a BaseHandler', () => {
|
||||
const handler = new TestScheduledHandler();
|
||||
|
||||
expect(handler).toBeInstanceOf(BaseHandler);
|
||||
expect(handler).toBeInstanceOf(ScheduledHandler);
|
||||
});
|
||||
import { describe, expect, it, beforeEach, mock, type Mock } from 'bun:test';
|
||||
import { BaseHandler, ScheduledHandler } from '../src/base/BaseHandler';
|
||||
import { Handler, Operation } from '../src/decorators/decorators';
|
||||
import type { IServiceContainer, ExecutionContext, ServiceTypes } from '@stock-bot/types';
|
||||
import type { CacheProvider } from '@stock-bot/cache';
|
||||
import type { Logger } from '@stock-bot/logger';
|
||||
import type { QueueManager, Queue } from '@stock-bot/queue';
|
||||
import type { SimpleBrowser } from '@stock-bot/browser';
|
||||
import type { SimpleProxyManager } from '@stock-bot/proxy';
|
||||
import type { MongoClient, Db, Collection } from 'mongodb';
|
||||
import type { Pool, QueryResult } from 'pg';
|
||||
|
||||
type MockQueue = {
|
||||
add: Mock<(name: string, data: any) => Promise<{ id: string }>>;
|
||||
getName: Mock<() => string>;
|
||||
};
|
||||
|
||||
type MockQueueManager = {
|
||||
getQueue: Mock<(name: string) => MockQueue | null>;
|
||||
createQueue: Mock<(name: string) => MockQueue>;
|
||||
hasQueue: Mock<(name: string) => boolean>;
|
||||
sendToQueue: Mock<(service: string, handler: string, data: any) => Promise<string>>;
|
||||
};
|
||||
|
||||
type MockCache = {
|
||||
get: Mock<(key: string) => Promise<any>>;
|
||||
set: Mock<(key: string, value: any, ttl?: number) => Promise<void>>;
|
||||
del: Mock<(key: string) => Promise<void>>;
|
||||
clear: Mock<() => Promise<void>>;
|
||||
has: Mock<(key: string) => Promise<boolean>>;
|
||||
keys: Mock<(pattern?: string) => Promise<string[]>>;
|
||||
ttl: Mock<(key: string) => Promise<number>>;
|
||||
type: 'memory';
|
||||
};
|
||||
|
||||
type MockLogger = {
|
||||
info: Mock<(message: string, meta?: any) => void>;
|
||||
error: Mock<(message: string, meta?: any) => void>;
|
||||
warn: Mock<(message: string, meta?: any) => void>;
|
||||
debug: Mock<(message: string, meta?: any) => void>;
|
||||
};
|
||||
|
||||
type MockBrowser = {
|
||||
scrape: Mock<(url: string) => Promise<{ data: string }>>;
|
||||
};
|
||||
|
||||
type MockProxy = {
|
||||
getProxy: Mock<() => { host: string; port: number }>;
|
||||
};
|
||||
|
||||
type MockPostgres = {
|
||||
query: Mock<(text: string, values?: any[]) => Promise<QueryResult>>;
|
||||
};
|
||||
|
||||
type MockMongoDB = {
|
||||
db: Mock<(name?: string) => {
|
||||
collection: Mock<(name: string) => {
|
||||
find: Mock<(filter: any) => { toArray: Mock<() => Promise<any[]>> }>;
|
||||
insertOne: Mock<(doc: any) => Promise<{ insertedId: string }>>;
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
|
||||
describe('BaseHandler', () => {
|
||||
let mockServices: IServiceContainer;
|
||||
let mockContext: ExecutionContext;
|
||||
let mockQueue: MockQueue;
|
||||
let mockQueueManager: MockQueueManager;
|
||||
let mockCache: MockCache;
|
||||
let mockLogger: MockLogger;
|
||||
|
||||
beforeEach(() => {
|
||||
mockQueue = {
|
||||
add: mock(async () => ({ id: 'job-456' })),
|
||||
getName: mock(() => 'test-queue'),
|
||||
};
|
||||
|
||||
mockQueueManager = {
|
||||
getQueue: mock(() => mockQueue),
|
||||
createQueue: mock(() => mockQueue),
|
||||
hasQueue: mock(() => true),
|
||||
sendToQueue: mock(async () => 'job-123'),
|
||||
};
|
||||
|
||||
mockCache = {
|
||||
get: mock(async () => null),
|
||||
set: mock(async () => {}),
|
||||
del: mock(async () => {}),
|
||||
clear: mock(async () => {}),
|
||||
has: mock(async () => false),
|
||||
keys: mock(async () => []),
|
||||
ttl: mock(async () => -1),
|
||||
type: 'memory',
|
||||
};
|
||||
|
||||
mockLogger = {
|
||||
info: mock(() => {}),
|
||||
error: mock(() => {}),
|
||||
warn: mock(() => {}),
|
||||
debug: mock(() => {}),
|
||||
};
|
||||
|
||||
const mockBrowser: MockBrowser = {
|
||||
scrape: mock(async () => ({ data: 'scraped' })),
|
||||
};
|
||||
|
||||
const mockProxy: MockProxy = {
|
||||
getProxy: mock(() => ({ host: 'proxy.example.com', port: 8080 })),
|
||||
};
|
||||
|
||||
const mockPostgres: MockPostgres = {
|
||||
query: mock(async () => ({ rows: [], rowCount: 0 } as QueryResult)),
|
||||
};
|
||||
|
||||
const mockMongoDB: MockMongoDB = {
|
||||
db: mock(() => ({
|
||||
collection: mock(() => ({
|
||||
find: mock(() => ({ toArray: mock(async () => []) })),
|
||||
insertOne: mock(async () => ({ insertedId: 'id-123' })),
|
||||
})),
|
||||
})),
|
||||
};
|
||||
|
||||
mockServices = {
|
||||
cache: mockCache as unknown as ServiceTypes['cache'],
|
||||
globalCache: { ...mockCache } as unknown as ServiceTypes['globalCache'],
|
||||
queueManager: mockQueueManager as unknown as ServiceTypes['queueManager'],
|
||||
proxy: mockProxy as unknown as ServiceTypes['proxy'],
|
||||
browser: mockBrowser as unknown as ServiceTypes['browser'],
|
||||
mongodb: mockMongoDB as unknown as ServiceTypes['mongodb'],
|
||||
postgres: mockPostgres as unknown as ServiceTypes['postgres'],
|
||||
questdb: null,
|
||||
logger: mockLogger as unknown as ServiceTypes['logger'],
|
||||
queue: mockQueue as unknown as ServiceTypes['queue'],
|
||||
};
|
||||
|
||||
mockContext = {
|
||||
logger: mockLogger as unknown as Logger,
|
||||
traceId: 'trace-123',
|
||||
handlerName: 'TestHandler',
|
||||
operationName: 'testOperation',
|
||||
metadata: {},
|
||||
startTime: new Date(),
|
||||
container: {
|
||||
resolve: mock((name: string) => mockServices[name as keyof IServiceContainer]),
|
||||
resolveAsync: mock(async (name: string) => mockServices[name as keyof IServiceContainer]),
|
||||
},
|
||||
};
|
||||
|
||||
// Reset all mocks
|
||||
Object.values(mockServices).forEach(service => {
|
||||
if (service && typeof service === 'object') {
|
||||
Object.values(service).forEach(method => {
|
||||
if (typeof method === 'function' && 'mockClear' in method) {
|
||||
(method as unknown as Mock<any>).mockClear();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
class TestHandler extends BaseHandler {
|
||||
constructor() {
|
||||
super(mockServices, 'TestHandler');
|
||||
}
|
||||
|
||||
async testOperation(data: unknown): Promise<{ processed: unknown }> {
|
||||
return { processed: data };
|
||||
}
|
||||
}
|
||||
|
||||
describe('service access', () => {
|
||||
it('should provide access to cache service', async () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
await handler.cache.set('key', 'value');
|
||||
|
||||
expect(mockCache.set).toHaveBeenCalledWith('key', 'value');
|
||||
});
|
||||
|
||||
it('should have logger initialized', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
expect(handler.logger).toBeDefined();
|
||||
// Logger is created by getLogger, not from mockServices
|
||||
});
|
||||
|
||||
it('should provide access to queue service', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
expect(handler.queue).toBeDefined();
|
||||
expect(mockQueue.getName()).toBe('test-queue');
|
||||
});
|
||||
|
||||
it('should provide access to mongodb', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
expect(handler.mongodb).toBe(mockServices.mongodb);
|
||||
});
|
||||
|
||||
it('should provide access to postgres', async () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const result = await handler.postgres.query('SELECT 1');
|
||||
|
||||
expect(result.rows).toEqual([]);
|
||||
expect(mockServices.postgres.query).toHaveBeenCalledWith('SELECT 1');
|
||||
});
|
||||
|
||||
it('should provide access to browser', async () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const result = await handler.browser.scrape('https://example.com');
|
||||
|
||||
expect(result).toEqual({ data: 'scraped' });
|
||||
expect((mockServices.browser as unknown as MockBrowser).scrape).toHaveBeenCalledWith('https://example.com');
|
||||
});
|
||||
|
||||
it('should provide access to proxy manager', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const proxy = handler.proxy.getProxy();
|
||||
|
||||
expect(proxy).toEqual({ host: 'proxy.example.com', port: 8080 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('caching utilities', () => {
|
||||
it('should set and get cache values with handler namespace', async () => {
|
||||
const handler = new TestHandler();
|
||||
mockCache.set.mockClear();
|
||||
mockCache.get.mockClear();
|
||||
|
||||
// Test cacheSet
|
||||
await handler['cacheSet']('testKey', 'testValue', 3600);
|
||||
expect(mockCache.set).toHaveBeenCalledWith('TestHandler:testKey', 'testValue', 3600);
|
||||
|
||||
// Test cacheGet
|
||||
mockCache.get.mockImplementation(async () => 'cachedValue');
|
||||
const result = await handler['cacheGet']('testKey');
|
||||
expect(mockCache.get).toHaveBeenCalledWith('TestHandler:testKey');
|
||||
expect(result).toBe('cachedValue');
|
||||
});
|
||||
|
||||
it('should delete cache values with handler namespace', async () => {
|
||||
const handler = new TestHandler();
|
||||
mockCache.del.mockClear();
|
||||
|
||||
await handler['cacheDel']('testKey');
|
||||
expect(mockCache.del).toHaveBeenCalledWith('TestHandler:testKey');
|
||||
});
|
||||
|
||||
it('should handle null cache gracefully', async () => {
|
||||
mockServices.cache = null;
|
||||
const handler = new TestHandler();
|
||||
|
||||
// Should not throw when cache is null
|
||||
await expect(handler['cacheSet']('key', 'value')).resolves.toBeUndefined();
|
||||
await expect(handler['cacheGet']('key')).resolves.toBeNull();
|
||||
await expect(handler['cacheDel']('key')).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('scheduling', () => {
|
||||
it('should schedule operations', async () => {
|
||||
const handler = new TestHandler();
|
||||
mockQueueManager.hasQueue.mockClear();
|
||||
mockQueue.add.mockClear();
|
||||
|
||||
await handler.scheduleOperation(
|
||||
'processData',
|
||||
{ data: 'test' },
|
||||
{ delay: 5000 }
|
||||
);
|
||||
|
||||
expect(mockQueueManager.getQueue).toHaveBeenCalledWith('TestHandler');
|
||||
expect(mockQueue.add).toHaveBeenCalledWith(
|
||||
'processData',
|
||||
{
|
||||
handler: 'TestHandler',
|
||||
operation: 'processData',
|
||||
payload: { data: 'test' },
|
||||
},
|
||||
{ delay: 5000 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HTTP client', () => {
|
||||
it('should provide http methods', () => {
|
||||
const handler = new TestHandler();
|
||||
|
||||
const http = handler['http'];
|
||||
expect(http).toBeDefined();
|
||||
expect(http.get).toBeDefined();
|
||||
expect(http.post).toBeDefined();
|
||||
expect(http.put).toBeDefined();
|
||||
expect(http.delete).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handler metadata', () => {
|
||||
it('should extract handler metadata', () => {
|
||||
// For metadata extraction, we need a decorated handler
|
||||
@Handler('MetadataTestHandler')
|
||||
class MetadataTestHandler extends BaseHandler {
|
||||
@Operation('testOp')
|
||||
async handleTestOp() {
|
||||
return { result: 'success' };
|
||||
}
|
||||
}
|
||||
|
||||
const metadata = MetadataTestHandler.extractMetadata();
|
||||
expect(metadata).toBeDefined();
|
||||
expect(metadata!.name).toBe('MetadataTestHandler');
|
||||
expect(metadata!.operations).toContain('testOp');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lifecycle hooks', () => {
|
||||
class LifecycleHandler extends BaseHandler {
|
||||
onInitCalled = false;
|
||||
onStartCalled = false;
|
||||
onStopCalled = false;
|
||||
onDisposeCalled = false;
|
||||
|
||||
constructor() {
|
||||
super(mockServices, 'LifecycleHandler');
|
||||
}
|
||||
|
||||
async onInit(): Promise<void> {
|
||||
this.onInitCalled = true;
|
||||
}
|
||||
|
||||
async onStart(): Promise<void> {
|
||||
this.onStartCalled = true;
|
||||
}
|
||||
|
||||
async onStop(): Promise<void> {
|
||||
this.onStopCalled = true;
|
||||
}
|
||||
|
||||
async onDispose(): Promise<void> {
|
||||
this.onDisposeCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
it('should call lifecycle hooks', async () => {
|
||||
const handler = new LifecycleHandler();
|
||||
|
||||
await handler.onInit();
|
||||
expect(handler.onInitCalled).toBe(true);
|
||||
|
||||
await handler.onStart();
|
||||
expect(handler.onStartCalled).toBe(true);
|
||||
|
||||
await handler.onStop();
|
||||
expect(handler.onStopCalled).toBe(true);
|
||||
|
||||
await handler.onDispose();
|
||||
expect(handler.onDisposeCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ScheduledHandler', () => {
|
||||
const mockQueue: MockQueue = {
|
||||
add: mock(async () => ({ id: 'job-456' })),
|
||||
getName: mock(() => 'test-queue'),
|
||||
};
|
||||
|
||||
const mockServices: IServiceContainer = {
|
||||
cache: { type: 'memory' } as unknown as ServiceTypes['cache'],
|
||||
globalCache: { type: 'memory' } as unknown as ServiceTypes['globalCache'],
|
||||
queueManager: {
|
||||
getQueue: () => mockQueue
|
||||
} as unknown as ServiceTypes['queueManager'],
|
||||
proxy: null as unknown as ServiceTypes['proxy'],
|
||||
browser: null as unknown as ServiceTypes['browser'],
|
||||
mongodb: null as unknown as ServiceTypes['mongodb'],
|
||||
postgres: null as unknown as ServiceTypes['postgres'],
|
||||
questdb: null,
|
||||
logger: null as unknown as ServiceTypes['logger'],
|
||||
queue: mockQueue as unknown as ServiceTypes['queue'],
|
||||
};
|
||||
|
||||
class TestScheduledHandler extends ScheduledHandler {
|
||||
constructor() {
|
||||
super(mockServices, 'TestScheduledHandler');
|
||||
}
|
||||
|
||||
getScheduledJobs() {
|
||||
return [
|
||||
{
|
||||
name: 'dailyJob',
|
||||
schedule: '0 0 * * *',
|
||||
handler: 'processDailyData',
|
||||
},
|
||||
{
|
||||
name: 'hourlyJob',
|
||||
schedule: '0 * * * *',
|
||||
handler: 'processHourlyData',
|
||||
options: {
|
||||
timezone: 'UTC',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async processDailyData(): Promise<{ processed: string }> {
|
||||
return { processed: 'daily' };
|
||||
}
|
||||
|
||||
async processHourlyData(): Promise<{ processed: string }> {
|
||||
return { processed: 'hourly' };
|
||||
}
|
||||
}
|
||||
|
||||
it('should define scheduled jobs', () => {
|
||||
const handler = new TestScheduledHandler();
|
||||
|
||||
const jobs = handler.getScheduledJobs();
|
||||
|
||||
expect(jobs).toHaveLength(2);
|
||||
expect(jobs[0]).toEqual({
|
||||
name: 'dailyJob',
|
||||
schedule: '0 0 * * *',
|
||||
handler: 'processDailyData',
|
||||
});
|
||||
expect(jobs[1]).toEqual({
|
||||
name: 'hourlyJob',
|
||||
schedule: '0 * * * *',
|
||||
handler: 'processHourlyData',
|
||||
options: {
|
||||
timezone: 'UTC',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should be a BaseHandler', () => {
|
||||
const handler = new TestScheduledHandler();
|
||||
|
||||
expect(handler).toBeInstanceOf(BaseHandler);
|
||||
expect(handler).toBeInstanceOf(ScheduledHandler);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue