415 lines
11 KiB
TypeScript
415 lines
11 KiB
TypeScript
import { asClass, asFunction, asValue, createContainer } from 'awilix';
|
|
import { describe, expect, it, mock } from 'bun:test';
|
|
import {
|
|
registerApplicationServices,
|
|
registerCacheServices,
|
|
registerDatabaseServices,
|
|
} from '../src/registrations';
|
|
|
|
// Mock the queue module
|
|
mock.module('@stock-bot/queue', () => ({
|
|
createServiceCache: mock(() => ({
|
|
get: mock(() => Promise.resolve(null)),
|
|
set: mock(() => Promise.resolve()),
|
|
del: mock(() => Promise.resolve()),
|
|
})),
|
|
}));
|
|
|
|
describe('DI Registrations', () => {
|
|
describe('registerCacheServices', () => {
|
|
it('should register null cache when redis disabled', () => {
|
|
const container = createContainer();
|
|
const config = {
|
|
service: {
|
|
name: 'test-service',
|
|
type: 'WORKER' as const,
|
|
},
|
|
redis: {
|
|
enabled: false,
|
|
host: 'localhost',
|
|
port: 6379,
|
|
},
|
|
} as any;
|
|
|
|
registerCacheServices(container, config);
|
|
|
|
const cache = container.resolve('cache');
|
|
expect(cache).toBeNull();
|
|
});
|
|
|
|
it('should register redis cache when redis config exists', () => {
|
|
const container = createContainer();
|
|
|
|
// Register logger first as it's a dependency
|
|
container.register({
|
|
logger: asValue({
|
|
info: () => {},
|
|
error: () => {},
|
|
warn: () => {},
|
|
debug: () => {},
|
|
}),
|
|
});
|
|
|
|
const config = {
|
|
service: {
|
|
name: 'test-service',
|
|
type: 'WORKER' as const,
|
|
},
|
|
redis: {
|
|
enabled: true,
|
|
host: 'localhost',
|
|
port: 6379,
|
|
db: 1,
|
|
},
|
|
} as any;
|
|
|
|
registerCacheServices(container, config);
|
|
|
|
const cache = container.resolve('cache');
|
|
expect(cache).toBeDefined();
|
|
});
|
|
|
|
it('should register both cache and globalCache', () => {
|
|
const container = createContainer();
|
|
|
|
// Register logger dependency
|
|
container.register({
|
|
logger: asValue({
|
|
info: () => {},
|
|
error: () => {},
|
|
warn: () => {},
|
|
debug: () => {},
|
|
}),
|
|
});
|
|
|
|
const config = {
|
|
service: {
|
|
name: 'test-service',
|
|
serviceName: 'test-service',
|
|
},
|
|
redis: {
|
|
enabled: true,
|
|
host: 'localhost',
|
|
port: 6379,
|
|
db: 1,
|
|
},
|
|
} as any;
|
|
|
|
registerCacheServices(container, config);
|
|
|
|
const cache = container.resolve('cache');
|
|
const globalCache = container.resolve('globalCache');
|
|
expect(cache).toBeDefined();
|
|
expect(globalCache).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('registerDatabaseServices', () => {
|
|
it('should register MongoDB when config exists', () => {
|
|
const container = createContainer();
|
|
|
|
// Mock MongoDB client
|
|
const mockMongoClient = {
|
|
connect: mock(() => Promise.resolve()),
|
|
disconnect: mock(() => Promise.resolve()),
|
|
getDb: mock(() => ({})),
|
|
};
|
|
|
|
// Mock the MongoDB factory
|
|
mock.module('@stock-bot/mongodb', () => ({
|
|
MongoDBClient: class {
|
|
constructor() {
|
|
return mockMongoClient;
|
|
}
|
|
},
|
|
}));
|
|
|
|
const config = {
|
|
mongodb: {
|
|
enabled: true,
|
|
uri: 'mongodb://localhost',
|
|
database: 'test-db',
|
|
},
|
|
} as any;
|
|
|
|
registerDatabaseServices(container, config);
|
|
|
|
expect(container.hasRegistration('mongoClient')).toBe(true);
|
|
});
|
|
|
|
it('should register PostgreSQL when config exists', () => {
|
|
const container = createContainer();
|
|
|
|
// Mock Postgres client
|
|
const mockPostgresClient = {
|
|
connect: mock(() => Promise.resolve()),
|
|
disconnect: mock(() => Promise.resolve()),
|
|
query: mock(() => Promise.resolve({ rows: [] })),
|
|
};
|
|
|
|
// Mock the Postgres factory
|
|
mock.module('@stock-bot/postgres', () => ({
|
|
PostgresClient: class {
|
|
constructor() {
|
|
return mockPostgresClient;
|
|
}
|
|
},
|
|
}));
|
|
|
|
const config = {
|
|
postgres: {
|
|
enabled: true,
|
|
host: 'localhost',
|
|
port: 5432,
|
|
user: 'user',
|
|
password: 'pass',
|
|
database: 'test-db',
|
|
},
|
|
} as any;
|
|
|
|
registerDatabaseServices(container, config);
|
|
|
|
expect(container.hasRegistration('postgresClient')).toBe(true);
|
|
});
|
|
|
|
it('should register QuestDB when config exists', () => {
|
|
const container = createContainer();
|
|
|
|
// Mock QuestDB client
|
|
const mockQuestdbClient = {
|
|
connect: mock(() => Promise.resolve()),
|
|
disconnect: mock(() => Promise.resolve()),
|
|
query: mock(() => Promise.resolve({ data: [] })),
|
|
};
|
|
|
|
// Mock the QuestDB factory
|
|
mock.module('@stock-bot/questdb', () => ({
|
|
QuestDBClient: class {
|
|
constructor() {
|
|
return mockQuestdbClient;
|
|
}
|
|
},
|
|
}));
|
|
|
|
const config = {
|
|
questdb: {
|
|
enabled: true,
|
|
host: 'localhost',
|
|
httpPort: 9000,
|
|
pgPort: 8812,
|
|
influxPort: 9009,
|
|
database: 'questdb',
|
|
},
|
|
} as any;
|
|
|
|
registerDatabaseServices(container, config);
|
|
|
|
expect(container.hasRegistration('questdbClient')).toBe(true);
|
|
});
|
|
|
|
it('should not register disabled databases', () => {
|
|
const container = createContainer();
|
|
|
|
const config = {
|
|
mongodb: { enabled: false },
|
|
postgres: { enabled: false },
|
|
questdb: undefined,
|
|
} as any;
|
|
|
|
registerDatabaseServices(container, config);
|
|
|
|
// Services are registered but with null values when disabled
|
|
expect(container.hasRegistration('mongoClient')).toBe(true);
|
|
expect(container.hasRegistration('postgresClient')).toBe(true);
|
|
expect(container.hasRegistration('questdbClient')).toBe(true);
|
|
|
|
// Verify they resolve to null
|
|
expect(container.resolve('mongoClient')).toBeNull();
|
|
expect(container.resolve('postgresClient')).toBeNull();
|
|
expect(container.resolve('questdbClient')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('registerApplicationServices', () => {
|
|
it('should register browser when config exists', () => {
|
|
const container = createContainer();
|
|
|
|
// Mock browser factory
|
|
const mockBrowser = {
|
|
launch: mock(() => Promise.resolve()),
|
|
close: mock(() => Promise.resolve()),
|
|
};
|
|
|
|
mock.module('@stock-bot/browser', () => ({
|
|
createBrowser: () => mockBrowser,
|
|
}));
|
|
|
|
const config = {
|
|
browser: {
|
|
headless: true,
|
|
timeout: 30000,
|
|
},
|
|
} as any;
|
|
|
|
registerApplicationServices(container, config);
|
|
|
|
expect(container.hasRegistration('browser')).toBe(true);
|
|
});
|
|
|
|
it('should register proxy when config exists', () => {
|
|
const container = createContainer();
|
|
|
|
// Mock proxy factory
|
|
const mockProxy = {
|
|
getProxy: mock(() => 'http://proxy:8080'),
|
|
};
|
|
|
|
mock.module('@stock-bot/proxy', () => ({
|
|
createProxyManager: () => mockProxy,
|
|
}));
|
|
|
|
const config = {
|
|
proxy: {
|
|
enabled: true,
|
|
url: 'http://proxy:8080',
|
|
},
|
|
} as any;
|
|
|
|
registerApplicationServices(container, config);
|
|
|
|
expect(container.hasRegistration('proxyManager')).toBe(true);
|
|
});
|
|
|
|
it('should register queue manager when queue config exists', () => {
|
|
const container = createContainer();
|
|
|
|
// Mock dependencies
|
|
container.register({
|
|
cache: asValue({
|
|
get: mock(() => Promise.resolve(null)),
|
|
set: mock(() => Promise.resolve()),
|
|
}),
|
|
handlerRegistry: asValue({
|
|
getHandler: mock(() => null),
|
|
getAllHandlers: mock(() => []),
|
|
}),
|
|
logger: asValue({
|
|
info: mock(() => {}),
|
|
error: mock(() => {}),
|
|
warn: mock(() => {}),
|
|
debug: mock(() => {}),
|
|
}),
|
|
});
|
|
|
|
// Mock queue manager
|
|
const mockQueueManager = {
|
|
getQueue: mock(() => ({})),
|
|
startAllWorkers: mock(() => {}),
|
|
shutdown: mock(() => Promise.resolve()),
|
|
};
|
|
|
|
mock.module('@stock-bot/queue', () => ({
|
|
QueueManager: class {
|
|
constructor() {
|
|
return mockQueueManager;
|
|
}
|
|
},
|
|
}));
|
|
|
|
const config = {
|
|
service: {
|
|
name: 'test-service',
|
|
serviceName: 'test-service',
|
|
},
|
|
queue: {
|
|
enabled: true,
|
|
workers: 2,
|
|
concurrency: 5,
|
|
},
|
|
} as any;
|
|
|
|
registerApplicationServices(container, config);
|
|
|
|
expect(container.hasRegistration('queueManager')).toBe(true);
|
|
});
|
|
|
|
it('should not register services when configs are missing', () => {
|
|
const container = createContainer();
|
|
|
|
const config = {} as any;
|
|
|
|
registerApplicationServices(container, config);
|
|
|
|
expect(container.hasRegistration('browser')).toBe(true);
|
|
expect(container.hasRegistration('proxyManager')).toBe(true);
|
|
expect(container.hasRegistration('queueManager')).toBe(true);
|
|
|
|
// They should be registered as null
|
|
const browser = container.resolve('browser');
|
|
const proxyManager = container.resolve('proxyManager');
|
|
const queueManager = container.resolve('queueManager');
|
|
|
|
expect(browser).toBe(null);
|
|
expect(proxyManager).toBe(null);
|
|
expect(queueManager).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('dependency resolution', () => {
|
|
it('should properly resolve cache dependencies', () => {
|
|
const container = createContainer();
|
|
|
|
const config = {
|
|
service: {
|
|
name: 'test-service',
|
|
serviceName: 'test-service',
|
|
},
|
|
redis: {
|
|
enabled: true,
|
|
host: 'localhost',
|
|
port: 6379,
|
|
db: 0,
|
|
},
|
|
} as any;
|
|
|
|
registerCacheServices(container, config);
|
|
|
|
// Should have registered cache
|
|
expect(container.hasRegistration('cache')).toBe(true);
|
|
expect(container.hasRegistration('globalCache')).toBe(true);
|
|
});
|
|
|
|
it('should handle circular dependencies gracefully', () => {
|
|
const container = createContainer();
|
|
|
|
// Register services with potential circular deps
|
|
container.register({
|
|
serviceA: asFunction(({ serviceB }) => ({ b: serviceB })).singleton(),
|
|
serviceB: asFunction(({ serviceA }) => ({ a: serviceA })).singleton(),
|
|
});
|
|
|
|
// This should throw or handle gracefully
|
|
expect(() => container.resolve('serviceA')).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('registration options', () => {
|
|
it('should register services as singletons', () => {
|
|
const container = createContainer();
|
|
|
|
const config = {
|
|
browser: {
|
|
headless: true,
|
|
timeout: 30000,
|
|
},
|
|
} as any;
|
|
|
|
registerApplicationServices(container, config);
|
|
|
|
// Check that browser was registered as singleton
|
|
const registration = container.getRegistration('browser');
|
|
expect(registration).toBeDefined();
|
|
expect(registration?.lifetime).toBe('SINGLETON');
|
|
});
|
|
});
|
|
});
|