GPU_GUARD_MONOREPO/packages/backend/test/mysql_pool_manager.test.ts
2026-05-20 21:39:12 +08:00

96 lines
3.3 KiB
TypeScript

import { NetaClawDataSourceEntity } from '../src/modules/netaclaw/entity/data_source.js';
import { MysqlPoolManager } from '../src/modules/netaclaw/service/mysql_pool.js';
function makeDataSource(overrides: Partial<NetaClawDataSourceEntity> = {}) {
return Object.assign(new NetaClawDataSourceEntity(), {
id: 7,
name: 'orders',
label: 'Orders DB',
type: 'mysql',
host: 'db.internal',
port: 3307,
database: 'orders_prod',
username: 'readonly',
passwordEncrypted: 'ciphertext',
readonly: true,
status: 1,
allowedAgentIds: [9],
extra: {
poolConnectionLimit: 20,
connectTimeout: 500,
ssl: { rejectUnauthorized: true },
},
...overrides,
});
}
describe('MysqlPoolManager', () => {
it('reuses pools by data source id, clamps connection limits, and decrypts passwords', async () => {
const pool = { end: jest.fn() };
const createPool = jest.fn().mockReturnValue(pool);
const manager = new MysqlPoolManager(createPool as any);
manager.secretCrypto = {
decryptText: jest.fn().mockReturnValue('plain-password'),
} as any;
const first = await manager.getPool(makeDataSource());
const second = await manager.getPool(makeDataSource({ host: 'other.internal' }));
expect(first).toBe(pool);
expect(second).toBe(pool);
expect(createPool).toHaveBeenCalledTimes(1);
expect(createPool).toHaveBeenCalledWith({
host: 'db.internal',
port: 3307,
database: 'orders_prod',
user: 'readonly',
password: 'plain-password',
waitForConnections: true,
connectionLimit: 10,
connectTimeout: 1000,
ssl: { rejectUnauthorized: true },
});
});
it('closes and forgets cached pools', async () => {
const firstPool = { end: jest.fn().mockResolvedValue(undefined) };
const secondPool = { end: jest.fn().mockResolvedValue(undefined) };
const createPool = jest.fn().mockReturnValueOnce(firstPool).mockReturnValueOnce(secondPool);
const manager = new MysqlPoolManager(createPool as any);
manager.secretCrypto = {
decryptText: jest.fn().mockReturnValue('plain-password'),
} as any;
await manager.getPool(makeDataSource());
await manager.closePool(7);
const next = await manager.getPool(makeDataSource());
expect(firstPool.end).toHaveBeenCalledTimes(1);
expect(next).toBe(secondPool);
expect(createPool).toHaveBeenCalledTimes(2);
});
it('creates transient pools without caching and clamps transient connection limits', async () => {
const firstPool = { end: jest.fn() };
const secondPool = { end: jest.fn() };
const createPool = jest.fn().mockReturnValueOnce(firstPool).mockReturnValueOnce(secondPool);
const manager = new MysqlPoolManager(createPool as any);
manager.secretCrypto = {
decryptText: jest.fn().mockReturnValue('plain-password'),
} as any;
const first = await manager.createTransientPool(makeDataSource());
const second = await manager.createTransientPool(makeDataSource());
expect(first).toBe(firstPool);
expect(second).toBe(secondPool);
expect(createPool).toHaveBeenCalledTimes(2);
expect(createPool).toHaveBeenLastCalledWith(
expect.objectContaining({
connectionLimit: 3,
password: 'plain-password',
})
);
});
});