719 lines
No EOL
22 KiB
JavaScript
719 lines
No EOL
22 KiB
JavaScript
import { jest } from '@jest/globals';
|
|
import {
|
|
LRUCache,
|
|
RateLimiter,
|
|
ObjectPool,
|
|
BatchProcessor,
|
|
debounce,
|
|
throttle,
|
|
memoize,
|
|
StringMatcher,
|
|
ConnectionPool
|
|
} from '../dist/utils/performance.js';
|
|
|
|
describe('Performance utilities', () => {
|
|
describe('LRUCache', () => {
|
|
test('should store and retrieve values', () => {
|
|
const cache = new LRUCache(3);
|
|
|
|
cache.set('key1', 'value1');
|
|
cache.set('key2', 'value2');
|
|
|
|
expect(cache.get('key1')).toBe('value1');
|
|
expect(cache.get('key2')).toBe('value2');
|
|
expect(cache.get('nonexistent')).toBeUndefined();
|
|
});
|
|
|
|
test('should evict least recently used items when at capacity', () => {
|
|
const cache = new LRUCache(2);
|
|
|
|
cache.set('key1', 'value1');
|
|
cache.set('key2', 'value2');
|
|
cache.set('key3', 'value3'); // Should evict key1
|
|
|
|
expect(cache.get('key1')).toBeUndefined();
|
|
expect(cache.get('key2')).toBe('value2');
|
|
expect(cache.get('key3')).toBe('value3');
|
|
});
|
|
|
|
test('should handle TTL expiration', () => {
|
|
jest.useFakeTimers();
|
|
const cache = new LRUCache(10, 100); // 100ms TTL
|
|
|
|
cache.set('key1', 'value1');
|
|
expect(cache.get('key1')).toBe('value1');
|
|
|
|
jest.advanceTimersByTime(150);
|
|
expect(cache.get('key1')).toBeUndefined();
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('should delete and clear items', () => {
|
|
const cache = new LRUCache(5);
|
|
|
|
cache.set('key1', 'value1');
|
|
cache.set('key2', 'value2');
|
|
|
|
expect(cache.delete('key1')).toBe(true);
|
|
expect(cache.get('key1')).toBeUndefined();
|
|
|
|
cache.clear();
|
|
expect(cache.size).toBe(0);
|
|
});
|
|
|
|
test('should clean up expired entries with cleanup method', () => {
|
|
jest.useFakeTimers();
|
|
const cache = new LRUCache(10, 100); // 100ms TTL
|
|
|
|
cache.set('key1', 'value1');
|
|
cache.set('key2', 'value2');
|
|
cache.set('key3', 'value3');
|
|
|
|
jest.advanceTimersByTime(150);
|
|
|
|
const cleaned = cache.cleanup();
|
|
expect(cleaned).toBe(3);
|
|
expect(cache.size).toBe(0);
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('should handle has() method with TTL expiration', () => {
|
|
jest.useFakeTimers();
|
|
const cache = new LRUCache(10, 100); // 100ms TTL
|
|
|
|
cache.set('key1', 'value1');
|
|
expect(cache.has('key1')).toBe(true);
|
|
|
|
jest.advanceTimersByTime(150);
|
|
expect(cache.has('key1')).toBe(false);
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('should cleanup without TTL should return 0', () => {
|
|
const cache = new LRUCache(10); // No TTL
|
|
|
|
cache.set('key1', 'value1');
|
|
cache.set('key2', 'value2');
|
|
|
|
const cleaned = cache.cleanup();
|
|
expect(cleaned).toBe(0);
|
|
expect(cache.size).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('RateLimiter', () => {
|
|
let limiterInstances = [];
|
|
|
|
afterEach(() => {
|
|
// Clean up instances to prevent Jest hanging
|
|
limiterInstances.forEach(limiter => {
|
|
if (limiter && typeof limiter.destroy === 'function') {
|
|
limiter.destroy();
|
|
}
|
|
});
|
|
limiterInstances = [];
|
|
});
|
|
|
|
test('should allow requests within limit', () => {
|
|
const limiter = new RateLimiter(1000, 2);
|
|
limiterInstances.push(limiter);
|
|
|
|
expect(limiter.isAllowed('user1')).toBe(true);
|
|
expect(limiter.isAllowed('user1')).toBe(true);
|
|
expect(limiter.isAllowed('user1')).toBe(false);
|
|
});
|
|
|
|
test('should reset after window expires', () => {
|
|
jest.useFakeTimers();
|
|
const limiter = new RateLimiter(100, 2);
|
|
limiterInstances.push(limiter);
|
|
|
|
expect(limiter.isAllowed('user1')).toBe(true);
|
|
expect(limiter.isAllowed('user1')).toBe(true);
|
|
expect(limiter.isAllowed('user1')).toBe(false);
|
|
|
|
jest.advanceTimersByTime(150);
|
|
|
|
expect(limiter.isAllowed('user1')).toBe(true);
|
|
});
|
|
|
|
test('should track different identifiers separately', () => {
|
|
const limiter = new RateLimiter(1000, 2);
|
|
limiterInstances.push(limiter);
|
|
|
|
expect(limiter.isAllowed('user1')).toBe(true);
|
|
expect(limiter.isAllowed('user1')).toBe(true);
|
|
expect(limiter.isAllowed('user1')).toBe(false);
|
|
|
|
expect(limiter.isAllowed('user2')).toBe(true);
|
|
expect(limiter.isAllowed('user2')).toBe(true);
|
|
expect(limiter.isAllowed('user2')).toBe(false);
|
|
});
|
|
|
|
test('should clean up expired entries manually', () => {
|
|
jest.useFakeTimers();
|
|
const limiter = new RateLimiter(100, 2);
|
|
limiterInstances.push(limiter);
|
|
|
|
limiter.isAllowed('user1');
|
|
limiter.isAllowed('user2');
|
|
limiter.isAllowed('user3');
|
|
|
|
jest.advanceTimersByTime(150);
|
|
|
|
const cleaned = limiter.cleanup();
|
|
expect(cleaned).toBeGreaterThan(0);
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('should automatically clean up on interval', () => {
|
|
jest.useFakeTimers();
|
|
const limiter = new RateLimiter(100, 2);
|
|
limiterInstances.push(limiter);
|
|
|
|
limiter.isAllowed('user1');
|
|
limiter.isAllowed('user2');
|
|
|
|
jest.advanceTimersByTime(150);
|
|
|
|
// Trigger auto-cleanup (runs every 60 seconds)
|
|
jest.advanceTimersByTime(60000);
|
|
|
|
// Should still work after cleanup
|
|
expect(limiter.isAllowed('user1')).toBe(true);
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('should handle cleanup of identifiers with partial expired requests', () => {
|
|
jest.useFakeTimers();
|
|
const limiter = new RateLimiter(200, 3);
|
|
limiterInstances.push(limiter);
|
|
|
|
// Make some requests
|
|
limiter.isAllowed('user1');
|
|
jest.advanceTimersByTime(100);
|
|
limiter.isAllowed('user1');
|
|
jest.advanceTimersByTime(150); // First request now expired, second still valid
|
|
|
|
const cleaned = limiter.cleanup();
|
|
expect(cleaned).toBe(0); // user1 still has valid requests, not removed
|
|
|
|
// Advance further to expire all requests
|
|
jest.advanceTimersByTime(100);
|
|
const cleaned2 = limiter.cleanup();
|
|
expect(cleaned2).toBe(1); // user1 removed
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
});
|
|
|
|
describe('ObjectPool', () => {
|
|
test('should create and reuse objects', () => {
|
|
let created = 0;
|
|
const factory = () => ({ id: ++created });
|
|
const reset = (obj) => { obj.used = false; };
|
|
|
|
const pool = new ObjectPool(factory, reset);
|
|
|
|
const obj1 = pool.acquire();
|
|
expect(obj1.id).toBe(1);
|
|
|
|
pool.release(obj1);
|
|
|
|
const obj2 = pool.acquire();
|
|
expect(obj2.id).toBe(1); // Reused
|
|
expect(obj2.used).toBe(false); // Reset was called
|
|
});
|
|
|
|
test('should create new objects when pool is empty', () => {
|
|
let created = 0;
|
|
const factory = () => ({ id: ++created });
|
|
const reset = () => {};
|
|
|
|
const pool = new ObjectPool(factory, reset);
|
|
|
|
const obj1 = pool.acquire();
|
|
const obj2 = pool.acquire();
|
|
|
|
expect(obj1.id).toBe(1);
|
|
expect(obj2.id).toBe(2);
|
|
});
|
|
|
|
test('should not exceed max size', () => {
|
|
const factory = () => ({});
|
|
const reset = () => {};
|
|
|
|
const pool = new ObjectPool(factory, reset, 2);
|
|
|
|
const obj1 = pool.acquire();
|
|
const obj2 = pool.acquire();
|
|
const obj3 = pool.acquire();
|
|
|
|
pool.release(obj1);
|
|
pool.release(obj2);
|
|
pool.release(obj3);
|
|
|
|
const stats = pool.size;
|
|
expect(stats.available).toBeLessThanOrEqual(2);
|
|
});
|
|
|
|
test('should ignore release of objects not in use', () => {
|
|
const factory = () => ({ id: Math.random() });
|
|
const reset = () => {};
|
|
|
|
const pool = new ObjectPool(factory, reset);
|
|
const strangerObj = factory();
|
|
|
|
// Should not throw or affect pool
|
|
pool.release(strangerObj);
|
|
|
|
expect(pool.size.available).toBe(0);
|
|
expect(pool.size.inUse).toBe(0);
|
|
});
|
|
|
|
test('should clear all objects from pool', () => {
|
|
const factory = () => ({ id: Math.random() });
|
|
const reset = () => {};
|
|
|
|
const pool = new ObjectPool(factory, reset);
|
|
|
|
const obj1 = pool.acquire();
|
|
const obj2 = pool.acquire();
|
|
pool.release(obj1);
|
|
|
|
pool.clear();
|
|
|
|
const stats = pool.size;
|
|
expect(stats.available).toBe(0);
|
|
expect(stats.inUse).toBe(0);
|
|
expect(stats.total).toBe(0);
|
|
});
|
|
|
|
test('should provide accurate size statistics', () => {
|
|
const factory = () => ({ id: Math.random() });
|
|
const reset = () => {};
|
|
|
|
const pool = new ObjectPool(factory, reset);
|
|
|
|
const obj1 = pool.acquire();
|
|
const obj2 = pool.acquire();
|
|
pool.release(obj1);
|
|
|
|
const stats = pool.size;
|
|
expect(stats.available).toBe(1);
|
|
expect(stats.inUse).toBe(1);
|
|
expect(stats.total).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('BatchProcessor', () => {
|
|
let batcherInstances = [];
|
|
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
// Clean up any batcher instances to prevent memory leaks
|
|
batcherInstances.forEach(batcher => {
|
|
if (batcher && typeof batcher.destroy === 'function') {
|
|
batcher.destroy();
|
|
}
|
|
});
|
|
batcherInstances = [];
|
|
});
|
|
|
|
test('should process batch when size is reached', async () => {
|
|
const processor = jest.fn();
|
|
const batcher = new BatchProcessor(processor, { batchSize: 3 });
|
|
batcherInstances.push(batcher);
|
|
|
|
batcher.add('item1');
|
|
batcher.add('item2');
|
|
expect(processor).not.toHaveBeenCalled();
|
|
|
|
batcher.add('item3');
|
|
await Promise.resolve(); // Let async processing complete
|
|
|
|
expect(processor).toHaveBeenCalledWith(['item1', 'item2', 'item3']);
|
|
});
|
|
|
|
test('should auto-flush on interval', async () => {
|
|
const processor = jest.fn();
|
|
const batcher = new BatchProcessor(processor, {
|
|
batchSize: 10,
|
|
flushInterval: 100
|
|
});
|
|
batcherInstances.push(batcher);
|
|
|
|
batcher.add('item1');
|
|
batcher.add('item2');
|
|
|
|
jest.advanceTimersByTime(100);
|
|
await Promise.resolve();
|
|
|
|
expect(processor).toHaveBeenCalledWith(['item1', 'item2']);
|
|
});
|
|
|
|
test('should handle processing errors', async () => {
|
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
const processor = jest.fn().mockRejectedValue(new Error('Process error'));
|
|
const batcher = new BatchProcessor(processor, { batchSize: 1 });
|
|
batcherInstances.push(batcher);
|
|
|
|
batcher.add('item1');
|
|
await Promise.resolve();
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Batch processing error:', expect.any(Error));
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
test('should not flush when already processing', async () => {
|
|
let resolveProcessor;
|
|
const processor = jest.fn(() => new Promise(resolve => {
|
|
resolveProcessor = resolve;
|
|
}));
|
|
|
|
const batcher = new BatchProcessor(processor, { batchSize: 2 });
|
|
batcherInstances.push(batcher);
|
|
|
|
batcher.add('item1');
|
|
batcher.add('item2'); // Triggers flush
|
|
|
|
// Add more items while first batch is processing
|
|
batcher.add('item3');
|
|
batcher.flush(); // Should return early
|
|
|
|
expect(processor).toHaveBeenCalledTimes(1);
|
|
|
|
// Resolve the first batch
|
|
resolveProcessor();
|
|
await Promise.resolve();
|
|
|
|
expect(processor).toHaveBeenCalledWith(['item1', 'item2']);
|
|
});
|
|
|
|
test('should not flush empty queue', async () => {
|
|
const processor = jest.fn();
|
|
const batcher = new BatchProcessor(processor, { batchSize: 5 });
|
|
batcherInstances.push(batcher);
|
|
|
|
await batcher.flush();
|
|
|
|
expect(processor).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('debounce', () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('should delay function execution', () => {
|
|
const func = jest.fn();
|
|
const debounced = debounce(func, 100);
|
|
|
|
debounced('arg1');
|
|
debounced('arg2');
|
|
debounced('arg3');
|
|
|
|
expect(func).not.toHaveBeenCalled();
|
|
|
|
jest.advanceTimersByTime(100);
|
|
|
|
expect(func).toHaveBeenCalledTimes(1);
|
|
expect(func).toHaveBeenCalledWith('arg3');
|
|
});
|
|
});
|
|
|
|
describe('throttle', () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('should limit function execution rate', () => {
|
|
const func = jest.fn();
|
|
const throttled = throttle(func, 100);
|
|
|
|
throttled('arg1');
|
|
throttled('arg2');
|
|
throttled('arg3');
|
|
|
|
expect(func).toHaveBeenCalledTimes(1);
|
|
expect(func).toHaveBeenCalledWith('arg1');
|
|
|
|
jest.advanceTimersByTime(100);
|
|
|
|
throttled('arg4');
|
|
expect(func).toHaveBeenCalledTimes(2);
|
|
expect(func).toHaveBeenCalledWith('arg4');
|
|
});
|
|
});
|
|
|
|
describe('memoize', () => {
|
|
test('should cache function results', () => {
|
|
const func = jest.fn((a, b) => a + b);
|
|
const memoized = memoize(func);
|
|
|
|
expect(memoized(1, 2)).toBe(3);
|
|
expect(memoized(1, 2)).toBe(3);
|
|
expect(memoized(1, 2)).toBe(3);
|
|
|
|
expect(func).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('should handle different arguments', () => {
|
|
const func = jest.fn((a, b) => a + b);
|
|
const memoized = memoize(func);
|
|
|
|
expect(memoized(1, 2)).toBe(3);
|
|
expect(memoized(2, 3)).toBe(5);
|
|
|
|
expect(func).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test('should respect TTL option', async () => {
|
|
jest.useFakeTimers();
|
|
const func = jest.fn((a) => a * 2);
|
|
const memoized = memoize(func, { ttl: 100 });
|
|
|
|
expect(memoized(5)).toBe(10);
|
|
expect(func).toHaveBeenCalledTimes(1);
|
|
|
|
expect(memoized(5)).toBe(10);
|
|
expect(func).toHaveBeenCalledTimes(1);
|
|
|
|
jest.advanceTimersByTime(150);
|
|
|
|
expect(memoized(5)).toBe(10);
|
|
expect(func).toHaveBeenCalledTimes(2);
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('should handle undefined cached values correctly', () => {
|
|
const func = jest.fn(() => undefined);
|
|
const memoized = memoize(func);
|
|
|
|
expect(memoized('test')).toBeUndefined();
|
|
expect(memoized('test')).toBeUndefined();
|
|
|
|
// Function should be called twice since undefined is returned
|
|
expect(func).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test('should handle functions returning falsy values', () => {
|
|
const func = jest.fn((x) => x === 'zero' ? 0 : x === 'false' ? false : x === 'null' ? null : 'default');
|
|
const memoized = memoize(func);
|
|
|
|
expect(memoized('zero')).toBe(0);
|
|
expect(memoized('zero')).toBe(0); // Should be cached
|
|
expect(func).toHaveBeenCalledTimes(1);
|
|
|
|
expect(memoized('false')).toBe(false);
|
|
expect(memoized('false')).toBe(false); // Should be cached
|
|
expect(func).toHaveBeenCalledTimes(2);
|
|
|
|
expect(memoized('null')).toBe(null);
|
|
expect(memoized('null')).toBe(null); // Should be cached
|
|
expect(func).toHaveBeenCalledTimes(3);
|
|
});
|
|
});
|
|
|
|
describe('StringMatcher', () => {
|
|
test('should match strings case-insensitively', () => {
|
|
const matcher = new StringMatcher(['apple', 'BANANA', 'Cherry']);
|
|
|
|
expect(matcher.contains('apple')).toBe(true);
|
|
expect(matcher.contains('APPLE')).toBe(true);
|
|
expect(matcher.contains('banana')).toBe(true);
|
|
expect(matcher.contains('cherry')).toBe(true);
|
|
expect(matcher.contains('grape')).toBe(false);
|
|
});
|
|
|
|
test('should add and remove patterns', () => {
|
|
const matcher = new StringMatcher(['apple']);
|
|
|
|
matcher.add('banana');
|
|
expect(matcher.contains('banana')).toBe(true);
|
|
expect(matcher.size).toBe(2);
|
|
|
|
expect(matcher.remove('apple')).toBe(true);
|
|
expect(matcher.contains('apple')).toBe(false);
|
|
expect(matcher.size).toBe(1);
|
|
});
|
|
|
|
test('should check if any text matches with containsAny', () => {
|
|
const matcher = new StringMatcher(['apple', 'banana', 'cherry']);
|
|
|
|
expect(matcher.containsAny(['grape', 'orange'])).toBe(false);
|
|
expect(matcher.containsAny(['grape', 'apple'])).toBe(true);
|
|
expect(matcher.containsAny(['BANANA', 'orange'])).toBe(true);
|
|
expect(matcher.containsAny([])).toBe(false);
|
|
expect(matcher.containsAny(['cherry', 'apple', 'banana'])).toBe(true);
|
|
});
|
|
|
|
test('should handle empty patterns', () => {
|
|
const matcher = new StringMatcher([]);
|
|
|
|
expect(matcher.contains('anything')).toBe(false);
|
|
expect(matcher.containsAny(['test', 'values'])).toBe(false);
|
|
expect(matcher.size).toBe(0);
|
|
});
|
|
|
|
test('should remove non-existent patterns gracefully', () => {
|
|
const matcher = new StringMatcher(['apple']);
|
|
|
|
expect(matcher.remove('banana')).toBe(false);
|
|
expect(matcher.size).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('ConnectionPool', () => {
|
|
test('should create and reuse connections', () => {
|
|
const pool = new ConnectionPool({ maxConnections: 5 });
|
|
|
|
const conn1 = pool.getConnection('host1');
|
|
expect(conn1).not.toBeNull();
|
|
expect(conn1.host).toBe('host1');
|
|
|
|
pool.releaseConnection('host1', conn1);
|
|
|
|
const conn2 = pool.getConnection('host1');
|
|
expect(conn2).toBe(conn1); // Reused
|
|
});
|
|
|
|
test('should respect max connections limit', () => {
|
|
const pool = new ConnectionPool({ maxConnections: 2 });
|
|
|
|
const conn1 = pool.getConnection('host1');
|
|
const conn2 = pool.getConnection('host1');
|
|
const conn3 = pool.getConnection('host1');
|
|
|
|
expect(conn1).not.toBeNull();
|
|
expect(conn2).not.toBeNull();
|
|
expect(conn3).toBeNull(); // Pool exhausted
|
|
});
|
|
|
|
test('should create separate pools for different hosts', () => {
|
|
const pool = new ConnectionPool({ maxConnections: 2 });
|
|
|
|
const conn1 = pool.getConnection('host1');
|
|
const conn2 = pool.getConnection('host2');
|
|
|
|
expect(conn1).not.toBeNull();
|
|
expect(conn2).not.toBeNull();
|
|
expect(conn1.host).toBe('host1');
|
|
expect(conn2.host).toBe('host2');
|
|
});
|
|
|
|
test('should handle release of non-existent connections gracefully', () => {
|
|
const pool = new ConnectionPool({ maxConnections: 5 });
|
|
|
|
const fakeConn = { host: 'fake', created: Date.now() };
|
|
|
|
// Should not throw
|
|
pool.releaseConnection('host1', fakeConn);
|
|
pool.releaseConnection('nonexistent', fakeConn);
|
|
});
|
|
|
|
test('should close connections when pool is over half capacity', () => {
|
|
const pool = new ConnectionPool({ maxConnections: 4 });
|
|
|
|
// Spy on closeConnection method
|
|
const closeConnectionSpy = jest.spyOn(pool, 'closeConnection');
|
|
|
|
// Fill pool
|
|
const conn1 = pool.getConnection('host1');
|
|
const conn2 = pool.getConnection('host1');
|
|
const conn3 = pool.getConnection('host1');
|
|
|
|
// Release connections
|
|
pool.releaseConnection('host1', conn1); // Should keep (pool size 1)
|
|
pool.releaseConnection('host1', conn2); // Should keep (pool size 2 = maxConnections/2)
|
|
pool.releaseConnection('host1', conn3); // Should close (pool would exceed half capacity)
|
|
|
|
expect(closeConnectionSpy).toHaveBeenCalledTimes(1);
|
|
expect(closeConnectionSpy).toHaveBeenCalledWith(conn3);
|
|
|
|
closeConnectionSpy.mockRestore();
|
|
});
|
|
|
|
test('should provide access to connectionTimeout property', () => {
|
|
const pool = new ConnectionPool({ timeout: 5000 });
|
|
|
|
expect(pool.connectionTimeout).toBe(5000);
|
|
});
|
|
|
|
test('should destroy all connections when destroyed', () => {
|
|
const pool = new ConnectionPool({ maxConnections: 3 });
|
|
|
|
// Spy on closeConnection method
|
|
const closeConnectionSpy = jest.spyOn(pool, 'closeConnection');
|
|
|
|
// Create some connections
|
|
const conn1 = pool.getConnection('host1');
|
|
const conn2 = pool.getConnection('host1');
|
|
const conn3 = pool.getConnection('host2');
|
|
|
|
// Release one connection back to pool
|
|
pool.releaseConnection('host1', conn1);
|
|
|
|
// Destroy pool
|
|
pool.destroy();
|
|
|
|
// Should close all connections (1 in pool + 2 in use)
|
|
expect(closeConnectionSpy).toHaveBeenCalledTimes(3);
|
|
expect(closeConnectionSpy).toHaveBeenCalledWith(conn1);
|
|
expect(closeConnectionSpy).toHaveBeenCalledWith(conn2);
|
|
expect(closeConnectionSpy).toHaveBeenCalledWith(conn3);
|
|
|
|
closeConnectionSpy.mockRestore();
|
|
});
|
|
|
|
test('should create connections with timestamp', () => {
|
|
const pool = new ConnectionPool();
|
|
|
|
const before = Date.now();
|
|
const conn = pool.getConnection('host1');
|
|
const after = Date.now();
|
|
|
|
expect(conn.created).toBeGreaterThanOrEqual(before);
|
|
expect(conn.created).toBeLessThanOrEqual(after);
|
|
});
|
|
|
|
test('should use default maxConnections and timeout values', () => {
|
|
const pool = new ConnectionPool();
|
|
|
|
expect(pool.connectionTimeout).toBe(30000); // Default timeout
|
|
|
|
// Create connections up to default limit (50)
|
|
const connections = [];
|
|
for (let i = 0; i < 50; i++) {
|
|
const conn = pool.getConnection('host1');
|
|
if (conn) connections.push(conn);
|
|
}
|
|
|
|
expect(connections).toHaveLength(50);
|
|
|
|
// 51st connection should be null
|
|
const extraConn = pool.getConnection('host1');
|
|
expect(extraConn).toBeNull();
|
|
});
|
|
});
|
|
});
|
|
|