This commit is contained in:
Boki 2025-06-25 11:38:23 -04:00
parent 3a7254708e
commit b63e58784c
41 changed files with 5762 additions and 4477 deletions

View file

@ -1,14 +1,14 @@
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 { beforeEach, describe, expect, it, mock, type Mock } from 'bun:test';
import type { Collection, Db, MongoClient } from 'mongodb';
import type { Pool, QueryResult } from 'pg';
import type { SimpleBrowser } from '@stock-bot/browser';
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';
import type { Queue, QueueManager } from '@stock-bot/queue';
import type { ExecutionContext, IServiceContainer, ServiceTypes } from '@stock-bot/types';
import { BaseHandler, ScheduledHandler } from '../src/base/BaseHandler';
import { Handler, Operation } from '../src/decorators/decorators';
type MockQueue = {
add: Mock<(name: string, data: any) => Promise<{ id: string }>>;
@ -53,12 +53,16 @@ type MockPostgres = {
};
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 }>>;
}>;
}>;
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', () => {
@ -109,7 +113,7 @@ describe('BaseHandler', () => {
};
const mockPostgres: MockPostgres = {
query: mock(async () => ({ rows: [], rowCount: 0 } as QueryResult)),
query: mock(async () => ({ rows: [], rowCount: 0 }) as QueryResult),
};
const mockMongoDB: MockMongoDB = {
@ -163,7 +167,7 @@ describe('BaseHandler', () => {
constructor() {
super(mockServices, 'TestHandler');
}
async testOperation(data: unknown): Promise<{ processed: unknown }> {
return { processed: data };
}
@ -172,55 +176,57 @@ describe('BaseHandler', () => {
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');
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 });
});
});
@ -230,11 +236,11 @@ describe('BaseHandler', () => {
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');
@ -245,7 +251,7 @@ describe('BaseHandler', () => {
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');
});
@ -253,7 +259,7 @@ describe('BaseHandler', () => {
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();
@ -266,13 +272,9 @@ describe('BaseHandler', () => {
const handler = new TestHandler();
mockQueueManager.hasQueue.mockClear();
mockQueue.add.mockClear();
await handler.scheduleOperation(
'processData',
{ data: 'test' },
{ delay: 5000 }
);
await handler.scheduleOperation('processData', { data: 'test' }, { delay: 5000 });
expect(mockQueueManager.getQueue).toHaveBeenCalledWith('TestHandler');
expect(mockQueue.add).toHaveBeenCalledWith(
'processData',
@ -289,7 +291,7 @@ describe('BaseHandler', () => {
describe('HTTP client', () => {
it('should provide http methods', () => {
const handler = new TestHandler();
const http = handler['http'];
expect(http).toBeDefined();
expect(http.get).toBeDefined();
@ -309,7 +311,7 @@ describe('BaseHandler', () => {
return { result: 'success' };
}
}
const metadata = MetadataTestHandler.extractMetadata();
expect(metadata).toBeDefined();
expect(metadata!.name).toBe('MetadataTestHandler');
@ -323,40 +325,40 @@ describe('BaseHandler', () => {
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);
});
@ -372,8 +374,8 @@ describe('ScheduledHandler', () => {
const mockServices: IServiceContainer = {
cache: { type: 'memory' } as unknown as ServiceTypes['cache'],
globalCache: { type: 'memory' } as unknown as ServiceTypes['globalCache'],
queueManager: {
getQueue: () => mockQueue
queueManager: {
getQueue: () => mockQueue,
} as unknown as ServiceTypes['queueManager'],
proxy: null as unknown as ServiceTypes['proxy'],
browser: null as unknown as ServiceTypes['browser'],
@ -388,7 +390,7 @@ describe('ScheduledHandler', () => {
constructor() {
super(mockServices, 'TestScheduledHandler');
}
getScheduledJobs() {
return [
{
@ -397,7 +399,7 @@ describe('ScheduledHandler', () => {
handler: 'processDailyData',
},
{
name: 'hourlyJob',
name: 'hourlyJob',
schedule: '0 * * * *',
handler: 'processHourlyData',
options: {
@ -406,21 +408,21 @@ describe('ScheduledHandler', () => {
},
];
}
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',
@ -436,11 +438,11 @@ describe('ScheduledHandler', () => {
},
});
});
it('should be a BaseHandler', () => {
const handler = new TestScheduledHandler();
expect(handler).toBeInstanceOf(BaseHandler);
expect(handler).toBeInstanceOf(ScheduledHandler);
});
});
});