stock-bot/libs/core/queue/test/queue.test.ts
2025-06-25 11:38:23 -04:00

203 lines
6 KiB
TypeScript

import { beforeEach, describe, expect, it, mock } from 'bun:test';
import { createServiceCache, ServiceCache } from '../src/service-cache';
import {
generateCachePrefix,
getFullQueueName,
normalizeServiceName,
parseQueueName,
} from '../src/service-utils';
import type { BatchJobData } from '../src/types';
describe('Service Utilities', () => {
describe('normalizeServiceName', () => {
it('should normalize service names', () => {
expect(normalizeServiceName('MyService')).toBe('my-service');
expect(normalizeServiceName('webApi')).toBe('web-api');
expect(normalizeServiceName('dataIngestion')).toBe('data-ingestion');
expect(normalizeServiceName('data-pipeline')).toBe('data-pipeline');
expect(normalizeServiceName('UPPERCASE')).toBe('uppercase');
});
it('should handle empty string', () => {
expect(normalizeServiceName('')).toBe('');
});
it('should handle special characters', () => {
// The function only handles camelCase, not special characters
expect(normalizeServiceName('my@service#123')).toBe('my@service#123');
expect(normalizeServiceName('serviceWithCamelCase')).toBe('service-with-camel-case');
});
});
describe('generateCachePrefix', () => {
it('should generate cache prefix', () => {
expect(generateCachePrefix('service')).toBe('cache:service');
expect(generateCachePrefix('webApi')).toBe('cache:web-api');
});
it('should handle empty parts', () => {
expect(generateCachePrefix('')).toBe('cache:');
});
});
describe('getFullQueueName', () => {
it('should generate full queue name', () => {
expect(getFullQueueName('service', 'handler')).toBe('{service_handler}');
expect(getFullQueueName('webApi', 'handler')).toBe('{web-api_handler}');
});
it('should normalize service name', () => {
expect(getFullQueueName('MyService', 'handler')).toBe('{my-service_handler}');
});
});
describe('parseQueueName', () => {
it('should parse queue name', () => {
expect(parseQueueName('{service_handler}')).toEqual({
service: 'service',
handler: 'handler',
});
expect(parseQueueName('{web-api_data-processor}')).toEqual({
service: 'web-api',
handler: 'data-processor',
});
});
it('should handle invalid formats', () => {
expect(parseQueueName('service:handler')).toBeNull();
expect(parseQueueName('service')).toBeNull();
expect(parseQueueName('')).toBeNull();
});
it('should handle edge cases', () => {
expect(parseQueueName('{}_handler')).toBeNull();
expect(parseQueueName('{service_}')).toBeNull();
expect(parseQueueName('not-a-valid-format')).toBeNull();
});
});
});
describe('ServiceCache', () => {
it('should create service cache', () => {
const mockRedisConfig = {
host: 'localhost',
port: 6379,
};
// Since ServiceCache constructor internally creates a real cache,
// we can't easily test it without mocking the createCache function
// For now, just test that the function exists and returns something
const serviceCache = createServiceCache('myservice', mockRedisConfig);
expect(serviceCache).toBeDefined();
expect(serviceCache).toBeInstanceOf(ServiceCache);
});
it('should handle cache prefix correctly', () => {
const mockRedisConfig = {
host: 'localhost',
port: 6379,
};
const serviceCache = createServiceCache('webApi', mockRedisConfig);
expect(serviceCache).toBeDefined();
// The prefix is set internally as cache:web-api
expect(serviceCache.getKey('test')).toBe('cache:web-api:test');
});
it('should support global cache option', () => {
const mockRedisConfig = {
host: 'localhost',
port: 6379,
};
const globalCache = createServiceCache('myservice', mockRedisConfig, { global: true });
expect(globalCache).toBeDefined();
// Global cache uses a different prefix
expect(globalCache.getKey('test')).toBe('stock-bot:shared:test');
});
});
describe('Batch Processing', () => {
it('should handle batch job data types', () => {
const batchJob: BatchJobData = {
items: [1, 2, 3],
options: {
batchSize: 10,
concurrency: 2,
},
};
expect(batchJob.items).toHaveLength(3);
expect(batchJob.options.batchSize).toBe(10);
expect(batchJob.options.concurrency).toBe(2);
});
it('should process batch results', () => {
const results = {
totalItems: 10,
successful: 8,
failed: 2,
errors: [
{ item: 5, error: 'Failed to process' },
{ item: 7, error: 'Invalid data' },
],
duration: 1000,
};
expect(results.successful + results.failed).toBe(results.totalItems);
expect(results.errors).toHaveLength(results.failed);
});
});
describe('Rate Limiting', () => {
it('should validate rate limit config', () => {
const config = {
rules: [
{
name: 'default',
maxJobs: 100,
window: 60000,
},
{
name: 'api',
maxJobs: 10,
window: 1000,
},
],
};
expect(config.rules).toHaveLength(2);
expect(config.rules[0].name).toBe('default');
expect(config.rules[1].maxJobs).toBe(10);
});
});
describe('Queue Types', () => {
it('should validate job data structure', () => {
const jobData = {
handler: 'TestHandler',
operation: 'process',
payload: { data: 'test' },
};
expect(jobData.handler).toBe('TestHandler');
expect(jobData.operation).toBe('process');
expect(jobData.payload).toBeDefined();
});
it('should validate queue stats structure', () => {
const stats = {
waiting: 10,
active: 2,
completed: 100,
failed: 5,
delayed: 3,
paused: false,
workers: 4,
};
expect(stats.waiting + stats.active + stats.completed + stats.failed + stats.delayed).toBe(120);
expect(stats.paused).toBe(false);
expect(stats.workers).toBe(4);
});
});