This commit is contained in:
Boki 2025-06-25 10:47:00 -04:00
parent 54f37f9521
commit 3a7254708e
19 changed files with 1560 additions and 1237 deletions

View file

@ -1,46 +1,93 @@
import { describe, expect, it } from 'bun:test';
import { describe, expect, it, mock } from 'bun:test';
import { createContainer, asValue } from 'awilix';
import type { AwilixContainer } from 'awilix';
import { CacheFactory } from '../src/factories';
import type { CacheProvider } from '@stock-bot/cache';
import type { ServiceDefinitions } from '../src/container/types';
describe('DI Factories', () => {
describe('CacheFactory', () => {
const mockCache: CacheProvider = {
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',
};
const createMockContainer = (cache: CacheProvider | null = mockCache): AwilixContainer<ServiceDefinitions> => {
const container = createContainer<ServiceDefinitions>();
container.register({
cache: asValue(cache),
});
return container;
};
it('should be exported', () => {
expect(CacheFactory).toBeDefined();
});
it('should create cache with configuration', () => {
const cacheConfig = {
redisConfig: {
host: 'localhost',
port: 6379,
db: 1,
},
keyPrefix: 'test:',
};
const cache = CacheFactory.create(cacheConfig);
expect(cache).toBeDefined();
it('should create namespaced cache', () => {
const namespacedCache = CacheFactory.createNamespacedCache(mockCache, 'test-namespace');
expect(namespacedCache).toBeDefined();
expect(namespacedCache).toBeInstanceOf(Object);
// NamespacedCache wraps the base cache but doesn't expose type property
});
it('should create null cache without config', () => {
const cache = CacheFactory.create();
expect(cache).toBeDefined();
expect(cache.type).toBe('null');
it('should create cache for service', () => {
const container = createMockContainer();
const serviceCache = CacheFactory.createCacheForService(container, 'test-service');
expect(serviceCache).toBeDefined();
expect(serviceCache).not.toBe(mockCache); // Should be a new namespaced instance
});
it('should create cache with logger', () => {
const mockLogger = {
info: () => {},
error: () => {},
warn: () => {},
debug: () => {},
};
it('should return null when no base cache available', () => {
const container = createMockContainer(null);
const serviceCache = CacheFactory.createCacheForService(container, 'test-service');
expect(serviceCache).toBeNull();
});
const cacheConfig = {
logger: mockLogger,
};
it('should create cache for handler with prefix', () => {
const container = createMockContainer();
const handlerCache = CacheFactory.createCacheForHandler(container, 'TestHandler');
expect(handlerCache).toBeDefined();
// The namespace should include 'handler:' prefix
});
const cache = CacheFactory.create(cacheConfig);
expect(cache).toBeDefined();
it('should create cache with custom prefix', () => {
const container = createMockContainer();
const prefixedCache = CacheFactory.createCacheWithPrefix(container, 'custom-prefix');
expect(prefixedCache).toBeDefined();
});
it('should clean duplicate cache: prefix', () => {
const container = createMockContainer();
// Should handle prefix that already includes 'cache:'
const prefixedCache = CacheFactory.createCacheWithPrefix(container, 'cache:custom-prefix');
expect(prefixedCache).toBeDefined();
// Internally it should strip the duplicate 'cache:' prefix
});
it('should handle null cache in all factory methods', () => {
const container = createMockContainer(null);
expect(CacheFactory.createCacheForService(container, 'service')).toBeNull();
expect(CacheFactory.createCacheForHandler(container, 'handler')).toBeNull();
expect(CacheFactory.createCacheWithPrefix(container, 'prefix')).toBeNull();
});
});
})
});

View file

@ -3,26 +3,29 @@ import { createContainer, asClass, asFunction, asValue } from 'awilix';
import {
registerCacheServices,
registerDatabaseServices,
registerServiceDependencies,
registerApplicationServices,
} from '../src/registrations';
describe('DI Registrations', () => {
describe('registerCacheServices', () => {
it('should register null cache when no redis config', () => {
it('should register null cache when redis disabled', () => {
const container = createContainer();
const config = {
service: {
name: 'test-service',
type: 'WORKER' as const,
},
// No redis config
};
redis: {
enabled: false,
host: 'localhost',
port: 6379,
},
} as any;
registerCacheServices(container, config);
const cache = container.resolve('cache');
expect(cache).toBeDefined();
expect(cache.type).toBe('null'); // NullCache type
expect(cache).toBeNull();
});
it('should register redis cache when redis config exists', () => {
@ -44,11 +47,12 @@ describe('DI Registrations', () => {
type: 'WORKER' as const,
},
redis: {
enabled: true,
host: 'localhost',
port: 6379,
db: 1,
},
};
} as any;
registerCacheServices(container, config);
@ -56,30 +60,38 @@ describe('DI Registrations', () => {
expect(cache).toBeDefined();
});
it('should register service cache', () => {
it('should register both cache and globalCache', () => {
const container = createContainer();
// Register dependencies
// Register logger dependency
container.register({
cache: asValue({ type: 'null' }),
config: asValue({
service: { name: 'test-service' },
redis: { host: 'localhost', port: 6379 },
logger: asValue({
info: () => {},
error: () => {},
warn: () => {},
debug: () => {},
}),
logger: asValue({ info: () => {} }),
});
const config = {
service: {
name: 'test-service',
type: 'WORKER' as const,
serviceName: 'test-service',
},
};
redis: {
enabled: true,
host: 'localhost',
port: 6379,
db: 1,
},
} as any;
registerCacheServices(container, config);
const serviceCache = container.resolve('serviceCache');
expect(serviceCache).toBeDefined();
const cache = container.resolve('cache');
const globalCache = container.resolve('globalCache');
expect(cache).toBeDefined();
expect(globalCache).toBeDefined();
});
});
@ -103,16 +115,19 @@ describe('DI Registrations', () => {
type: 'WORKER' as const,
},
mongodb: {
enabled: true,
uri: 'mongodb://localhost:27017',
database: 'test-db',
},
};
redis: { enabled: false, host: 'localhost', port: 6379 },
postgres: { enabled: false, host: 'localhost', port: 5432, database: 'test', user: 'test', password: 'test' },
} as any;
registerDatabaseServices(container, config);
// Check that mongodb is registered
// Check that mongoClient is registered (not mongodb)
const registrations = container.registrations;
expect(registrations.mongodb).toBeDefined();
expect(registrations.mongoClient).toBeDefined();
});
it('should register Postgres when config exists', () => {
@ -129,18 +144,21 @@ describe('DI Registrations', () => {
type: 'WORKER' as const,
},
postgres: {
enabled: true,
host: 'localhost',
port: 5432,
database: 'test-db',
username: 'user',
user: 'user',
password: 'pass',
},
};
mongodb: { enabled: false, uri: 'mongodb://localhost', database: 'test' },
redis: { enabled: false, host: 'localhost', port: 6379 },
} as any;
registerDatabaseServices(container, config);
const registrations = container.registrations;
expect(registrations.postgres).toBeDefined();
expect(registrations.postgresClient).toBeDefined();
});
it('should register QuestDB when config exists', () => {
@ -157,38 +175,46 @@ describe('DI Registrations', () => {
type: 'WORKER' as const,
},
questdb: {
enabled: true,
host: 'localhost',
httpPort: 9000,
pgPort: 8812,
influxPort: 9009,
database: 'test',
},
};
mongodb: { enabled: false, uri: 'mongodb://localhost', database: 'test' },
postgres: { enabled: false, host: 'localhost', port: 5432, database: 'test', user: 'test', password: 'test' },
redis: { enabled: false, host: 'localhost', port: 6379 },
} as any;
registerDatabaseServices(container, config);
const registrations = container.registrations;
expect(registrations.questdb).toBeDefined();
expect(registrations.questdbClient).toBeDefined();
});
it('should not register databases without config', () => {
it('should register null for disabled databases', () => {
const container = createContainer();
const config = {
service: {
name: 'test-service',
type: 'WORKER' as const,
},
// No database configs
};
mongodb: { enabled: false, uri: 'mongodb://localhost', database: 'test' },
postgres: { enabled: false, host: 'localhost', port: 5432, database: 'test', user: 'test', password: 'test' },
redis: { enabled: false, host: 'localhost', port: 6379 },
// questdb is optional
} as any;
registerDatabaseServices(container, config);
const registrations = container.registrations;
expect(registrations.mongodb).toBeUndefined();
expect(registrations.postgres).toBeUndefined();
expect(registrations.questdb).toBeUndefined();
expect(container.resolve('mongoClient')).toBeNull();
expect(container.resolve('postgresClient')).toBeNull();
expect(container.resolve('questdbClient')).toBeNull();
});
});
describe('registerServiceDependencies', () => {
describe('registerApplicationServices', () => {
it('should register browser service when config exists', () => {
const container = createContainer();
const mockLogger = { info: () => {}, error: () => {} };
@ -209,9 +235,12 @@ describe('DI Registrations', () => {
headless: true,
timeout: 30000,
},
};
redis: { enabled: true, host: 'localhost', port: 6379 },
mongodb: { enabled: false, uri: 'mongodb://localhost', database: 'test' },
postgres: { enabled: false, host: 'localhost', port: 5432, database: 'test', user: 'test', password: 'test' },
} as any;
registerServiceDependencies(container, config);
registerApplicationServices(container, config);
const registrations = container.registrations;
expect(registrations.browser).toBeDefined();
@ -232,62 +261,81 @@ describe('DI Registrations', () => {
},
proxy: {
enabled: true,
rotateOnError: true,
cachePrefix: 'proxy:',
ttl: 3600,
},
};
redis: { enabled: true, host: 'localhost', port: 6379 },
mongodb: { enabled: false, uri: 'mongodb://localhost', database: 'test' },
postgres: { enabled: false, host: 'localhost', port: 5432, database: 'test', user: 'test', password: 'test' },
} as any;
registerServiceDependencies(container, config);
registerApplicationServices(container, config);
const registrations = container.registrations;
expect(registrations.proxyManager).toBeDefined();
});
it('should register queue services for worker type', () => {
it('should register queue services when queue enabled', () => {
const container = createContainer();
const mockLogger = { info: () => {}, error: () => {} };
const mockHandlerRegistry = { getAllHandlers: () => [] };
container.register({
logger: asValue(mockLogger),
config: asValue({
service: { name: 'test-service', type: 'WORKER' },
redis: { host: 'localhost', port: 6379 },
}),
handlerRegistry: asValue(mockHandlerRegistry),
});
const config = {
service: {
name: 'test-service',
type: 'WORKER' as const,
serviceName: 'test-service',
},
queue: {
enabled: true,
workers: 2,
concurrency: 5,
enableScheduledJobs: true,
defaultJobOptions: {},
},
redis: {
enabled: true,
host: 'localhost',
port: 6379,
},
};
mongodb: { enabled: false, uri: 'mongodb://localhost', database: 'test' },
postgres: { enabled: false, host: 'localhost', port: 5432, database: 'test', user: 'test', password: 'test' },
} as any;
registerServiceDependencies(container, config);
registerApplicationServices(container, config);
const registrations = container.registrations;
expect(registrations.queueManager).toBeDefined();
});
it('should not register queue for API type', () => {
it('should not register queue when disabled', () => {
const container = createContainer();
const config = {
service: {
name: 'test-api',
type: 'API' as const,
},
queue: {
enabled: false,
},
redis: {
enabled: true,
host: 'localhost',
port: 6379,
},
};
mongodb: { enabled: false, uri: 'mongodb://localhost', database: 'test' },
postgres: { enabled: false, host: 'localhost', port: 5432, database: 'test', user: 'test', password: 'test' },
} as any;
registerServiceDependencies(container, config);
registerApplicationServices(container, config);
const registrations = container.registrations;
expect(registrations.queueManager).toBeUndefined();
expect(registrations.queueManager).toBeDefined();
expect(container.resolve('queueManager')).toBeNull();
});
});
})