import { beforeEach, describe, expect, it, mock } from 'bun:test'; import { normalizeServiceName, generateCachePrefix, getFullQueueName, parseQueueName, } from '../src/service-utils'; import { ServiceCache, createServiceCache } from '../src/service-cache'; 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); }); });