fixed format issues

This commit is contained in:
Boki 2025-06-26 16:12:27 -04:00
parent a700818a06
commit 08f713d98b
55 changed files with 5680 additions and 5533 deletions

View file

@ -1,94 +1,94 @@
import { describe, it, expect } from 'bun:test';
import { RedisConnectionManager } from '../src/connection-manager';
describe('RedisConnectionManager', () => {
it('should be a singleton', () => {
const instance1 = RedisConnectionManager.getInstance();
const instance2 = RedisConnectionManager.getInstance();
expect(instance1).toBe(instance2);
});
it('should create connections', () => {
const manager = RedisConnectionManager.getInstance();
const connection = manager.getConnection({
name: 'test',
redisConfig: {
host: 'localhost',
port: 6379,
},
});
expect(connection).toBeDefined();
expect(connection.options.host).toBe('localhost');
expect(connection.options.port).toBe(6379);
});
it('should reuse singleton connections', () => {
const manager = RedisConnectionManager.getInstance();
const conn1 = manager.getConnection({
name: 'shared',
singleton: true,
redisConfig: {
host: 'localhost',
port: 6379,
},
});
const conn2 = manager.getConnection({
name: 'shared',
singleton: true,
redisConfig: {
host: 'localhost',
port: 6379,
},
});
expect(conn1).toBe(conn2);
});
it('should create separate non-singleton connections', () => {
const manager = RedisConnectionManager.getInstance();
const conn1 = manager.getConnection({
name: 'separate1',
singleton: false,
redisConfig: {
host: 'localhost',
port: 6379,
},
});
const conn2 = manager.getConnection({
name: 'separate2',
singleton: false,
redisConfig: {
host: 'localhost',
port: 6379,
},
});
expect(conn1).not.toBe(conn2);
});
it('should close all connections', async () => {
const manager = RedisConnectionManager.getInstance();
// Create a few connections
manager.getConnection({
name: 'close-test-1',
redisConfig: { host: 'localhost', port: 6379 },
});
manager.getConnection({
name: 'close-test-2',
redisConfig: { host: 'localhost', port: 6379 },
});
// Close all
await RedisConnectionManager.closeAll();
// Should not throw
expect(true).toBe(true);
});
});
import { describe, expect, it } from 'bun:test';
import { RedisConnectionManager } from '../src/connection-manager';
describe('RedisConnectionManager', () => {
it('should be a singleton', () => {
const instance1 = RedisConnectionManager.getInstance();
const instance2 = RedisConnectionManager.getInstance();
expect(instance1).toBe(instance2);
});
it('should create connections', () => {
const manager = RedisConnectionManager.getInstance();
const connection = manager.getConnection({
name: 'test',
redisConfig: {
host: 'localhost',
port: 6379,
},
});
expect(connection).toBeDefined();
expect(connection.options.host).toBe('localhost');
expect(connection.options.port).toBe(6379);
});
it('should reuse singleton connections', () => {
const manager = RedisConnectionManager.getInstance();
const conn1 = manager.getConnection({
name: 'shared',
singleton: true,
redisConfig: {
host: 'localhost',
port: 6379,
},
});
const conn2 = manager.getConnection({
name: 'shared',
singleton: true,
redisConfig: {
host: 'localhost',
port: 6379,
},
});
expect(conn1).toBe(conn2);
});
it('should create separate non-singleton connections', () => {
const manager = RedisConnectionManager.getInstance();
const conn1 = manager.getConnection({
name: 'separate1',
singleton: false,
redisConfig: {
host: 'localhost',
port: 6379,
},
});
const conn2 = manager.getConnection({
name: 'separate2',
singleton: false,
redisConfig: {
host: 'localhost',
port: 6379,
},
});
expect(conn1).not.toBe(conn2);
});
it('should close all connections', async () => {
const manager = RedisConnectionManager.getInstance();
// Create a few connections
manager.getConnection({
name: 'close-test-1',
redisConfig: { host: 'localhost', port: 6379 },
});
manager.getConnection({
name: 'close-test-2',
redisConfig: { host: 'localhost', port: 6379 },
});
// Close all
await RedisConnectionManager.closeAll();
// Should not throw
expect(true).toBe(true);
});
});

View file

@ -1,429 +1,429 @@
import { describe, it, expect, beforeEach, mock } from 'bun:test';
import { NamespacedCache, CacheAdapter } from '../src/namespaced-cache';
import type { CacheProvider, ICache } from '../src/types';
describe('NamespacedCache', () => {
let mockCache: CacheProvider;
let namespacedCache: NamespacedCache;
beforeEach(() => {
// Create mock base cache
mockCache = {
get: mock(async () => null),
set: mock(async () => null),
del: mock(async () => {}),
exists: mock(async () => false),
clear: mock(async () => {}),
keys: mock(async () => []),
getStats: mock(() => ({
hits: 100,
misses: 20,
errors: 5,
hitRate: 0.83,
total: 120,
uptime: 3600,
})),
health: mock(async () => true),
waitForReady: mock(async () => {}),
isReady: mock(() => true),
};
// Create namespaced cache
namespacedCache = new NamespacedCache(mockCache, 'test-namespace');
});
describe('constructor', () => {
it('should set namespace and prefix correctly', () => {
expect(namespacedCache.getNamespace()).toBe('test-namespace');
expect(namespacedCache.getFullPrefix()).toBe('test-namespace:');
});
it('should handle empty namespace', () => {
const emptyNamespace = new NamespacedCache(mockCache, '');
expect(emptyNamespace.getNamespace()).toBe('');
expect(emptyNamespace.getFullPrefix()).toBe(':');
});
});
describe('get', () => {
it('should prefix key when getting', async () => {
const testData = { value: 'test' };
(mockCache.get as any).mockResolvedValue(testData);
const result = await namespacedCache.get('mykey');
expect(mockCache.get).toHaveBeenCalledWith('test-namespace:mykey');
expect(result).toEqual(testData);
});
it('should handle null values', async () => {
(mockCache.get as any).mockResolvedValue(null);
const result = await namespacedCache.get('nonexistent');
expect(mockCache.get).toHaveBeenCalledWith('test-namespace:nonexistent');
expect(result).toBeNull();
});
});
describe('set', () => {
it('should prefix key when setting with ttl number', async () => {
const value = { data: 'test' };
const ttl = 3600;
await namespacedCache.set('mykey', value, ttl);
expect(mockCache.set).toHaveBeenCalledWith('test-namespace:mykey', value, ttl);
});
it('should prefix key when setting with options object', async () => {
const value = 'test-value';
const options = { ttl: 7200 };
await namespacedCache.set('mykey', value, options);
expect(mockCache.set).toHaveBeenCalledWith('test-namespace:mykey', value, options);
});
it('should handle set without TTL', async () => {
const value = [1, 2, 3];
await namespacedCache.set('mykey', value);
expect(mockCache.set).toHaveBeenCalledWith('test-namespace:mykey', value, undefined);
});
});
describe('del', () => {
it('should prefix key when deleting', async () => {
await namespacedCache.del('mykey');
expect(mockCache.del).toHaveBeenCalledWith('test-namespace:mykey');
});
it('should handle multiple deletes', async () => {
await namespacedCache.del('key1');
await namespacedCache.del('key2');
expect(mockCache.del).toHaveBeenCalledTimes(2);
expect(mockCache.del).toHaveBeenCalledWith('test-namespace:key1');
expect(mockCache.del).toHaveBeenCalledWith('test-namespace:key2');
});
});
describe('exists', () => {
it('should prefix key when checking existence', async () => {
(mockCache.exists as any).mockResolvedValue(true);
const result = await namespacedCache.exists('mykey');
expect(mockCache.exists).toHaveBeenCalledWith('test-namespace:mykey');
expect(result).toBe(true);
});
it('should return false for non-existent keys', async () => {
(mockCache.exists as any).mockResolvedValue(false);
const result = await namespacedCache.exists('nonexistent');
expect(result).toBe(false);
});
});
describe('keys', () => {
it('should prefix pattern and strip prefix from results', async () => {
(mockCache.keys as any).mockResolvedValue([
'test-namespace:key1',
'test-namespace:key2',
'test-namespace:key3',
]);
const keys = await namespacedCache.keys('*');
expect(mockCache.keys).toHaveBeenCalledWith('test-namespace:*');
expect(keys).toEqual(['key1', 'key2', 'key3']);
});
it('should handle specific patterns', async () => {
(mockCache.keys as any).mockResolvedValue([
'test-namespace:user:123',
'test-namespace:user:456',
]);
const keys = await namespacedCache.keys('user:*');
expect(mockCache.keys).toHaveBeenCalledWith('test-namespace:user:*');
expect(keys).toEqual(['user:123', 'user:456']);
});
it('should filter out keys from other namespaces', async () => {
(mockCache.keys as any).mockResolvedValue([
'test-namespace:key1',
'other-namespace:key2',
'test-namespace:key3',
]);
const keys = await namespacedCache.keys('*');
expect(keys).toEqual(['key1', 'key3']);
});
it('should handle empty results', async () => {
(mockCache.keys as any).mockResolvedValue([]);
const keys = await namespacedCache.keys('nonexistent*');
expect(keys).toEqual([]);
});
});
describe('clear', () => {
it('should clear only namespaced keys', async () => {
(mockCache.keys as any).mockResolvedValue([
'test-namespace:key1',
'test-namespace:key2',
'test-namespace:key3',
]);
await namespacedCache.clear();
expect(mockCache.keys).toHaveBeenCalledWith('test-namespace:*');
expect(mockCache.del).toHaveBeenCalledTimes(3);
expect(mockCache.del).toHaveBeenCalledWith('key1');
expect(mockCache.del).toHaveBeenCalledWith('key2');
expect(mockCache.del).toHaveBeenCalledWith('key3');
});
it('should handle empty namespace', async () => {
(mockCache.keys as any).mockResolvedValue([]);
await namespacedCache.clear();
expect(mockCache.keys).toHaveBeenCalledWith('test-namespace:*');
expect(mockCache.del).not.toHaveBeenCalled();
});
});
describe('delegated methods', () => {
it('should delegate getStats', () => {
const stats = namespacedCache.getStats();
expect(mockCache.getStats).toHaveBeenCalled();
expect(stats).toEqual({
hits: 100,
misses: 20,
errors: 5,
hitRate: 0.83,
total: 120,
uptime: 3600,
});
});
it('should delegate health', async () => {
const health = await namespacedCache.health();
expect(mockCache.health).toHaveBeenCalled();
expect(health).toBe(true);
});
it('should delegate waitForReady', async () => {
await namespacedCache.waitForReady(5000);
expect(mockCache.waitForReady).toHaveBeenCalledWith(5000);
});
it('should delegate isReady', () => {
const ready = namespacedCache.isReady();
expect(mockCache.isReady).toHaveBeenCalled();
expect(ready).toBe(true);
});
});
describe('edge cases', () => {
it('should handle special characters in namespace', () => {
const specialNamespace = new NamespacedCache(mockCache, 'test:namespace:with:colons');
expect(specialNamespace.getFullPrefix()).toBe('test:namespace:with:colons:');
});
it('should handle very long keys', async () => {
const longKey = 'a'.repeat(1000);
await namespacedCache.get(longKey);
expect(mockCache.get).toHaveBeenCalledWith(`test-namespace:${longKey}`);
});
it('should handle errors from underlying cache', async () => {
const error = new Error('Cache error');
(mockCache.get as any).mockRejectedValue(error);
await expect(namespacedCache.get('key')).rejects.toThrow('Cache error');
});
});
});
describe('CacheAdapter', () => {
let mockICache: ICache;
let adapter: CacheAdapter;
beforeEach(() => {
mockICache = {
get: mock(async () => null),
set: mock(async () => {}),
del: mock(async () => {}),
exists: mock(async () => false),
clear: mock(async () => {}),
keys: mock(async () => []),
ping: mock(async () => true),
isConnected: mock(() => true),
has: mock(async () => false),
ttl: mock(async () => -1),
type: 'memory' as const,
};
adapter = new CacheAdapter(mockICache);
});
describe('get', () => {
it('should delegate to ICache.get', async () => {
const data = { value: 'test' };
(mockICache.get as any).mockResolvedValue(data);
const result = await adapter.get('key');
expect(mockICache.get).toHaveBeenCalledWith('key');
expect(result).toEqual(data);
});
});
describe('set', () => {
it('should handle TTL as number', async () => {
await adapter.set('key', 'value', 3600);
expect(mockICache.set).toHaveBeenCalledWith('key', 'value', 3600);
});
it('should handle TTL as options object', async () => {
await adapter.set('key', 'value', { ttl: 7200 });
expect(mockICache.set).toHaveBeenCalledWith('key', 'value', 7200);
});
it('should handle no TTL', async () => {
await adapter.set('key', 'value');
expect(mockICache.set).toHaveBeenCalledWith('key', 'value', undefined);
});
it('should always return null', async () => {
const result = await adapter.set('key', 'value');
expect(result).toBeNull();
});
});
describe('del', () => {
it('should delegate to ICache.del', async () => {
await adapter.del('key');
expect(mockICache.del).toHaveBeenCalledWith('key');
});
});
describe('exists', () => {
it('should delegate to ICache.exists', async () => {
(mockICache.exists as any).mockResolvedValue(true);
const result = await adapter.exists('key');
expect(mockICache.exists).toHaveBeenCalledWith('key');
expect(result).toBe(true);
});
});
describe('clear', () => {
it('should delegate to ICache.clear', async () => {
await adapter.clear();
expect(mockICache.clear).toHaveBeenCalled();
});
});
describe('keys', () => {
it('should delegate to ICache.keys', async () => {
const keys = ['key1', 'key2'];
(mockICache.keys as any).mockResolvedValue(keys);
const result = await adapter.keys('*');
expect(mockICache.keys).toHaveBeenCalledWith('*');
expect(result).toEqual(keys);
});
});
describe('getStats', () => {
it('should return default stats', () => {
const stats = adapter.getStats();
expect(stats).toEqual({
hits: 0,
misses: 0,
errors: 0,
hitRate: 0,
total: 0,
uptime: expect.any(Number),
});
});
});
describe('health', () => {
it('should use ping for health check', async () => {
(mockICache.ping as any).mockResolvedValue(true);
const result = await adapter.health();
expect(mockICache.ping).toHaveBeenCalled();
expect(result).toBe(true);
});
it('should handle ping failures', async () => {
(mockICache.ping as any).mockResolvedValue(false);
const result = await adapter.health();
expect(result).toBe(false);
});
});
describe('waitForReady', () => {
it('should succeed if connected', async () => {
(mockICache.isConnected as any).mockReturnValue(true);
await expect(adapter.waitForReady()).resolves.toBeUndefined();
});
it('should throw if not connected', async () => {
(mockICache.isConnected as any).mockReturnValue(false);
await expect(adapter.waitForReady()).rejects.toThrow('Cache not connected');
});
});
describe('isReady', () => {
it('should delegate to isConnected', () => {
(mockICache.isConnected as any).mockReturnValue(true);
const result = adapter.isReady();
expect(mockICache.isConnected).toHaveBeenCalled();
expect(result).toBe(true);
});
it('should return false when not connected', () => {
(mockICache.isConnected as any).mockReturnValue(false);
const result = adapter.isReady();
expect(result).toBe(false);
});
});
});
import { beforeEach, describe, expect, it, mock } from 'bun:test';
import { CacheAdapter, NamespacedCache } from '../src/namespaced-cache';
import type { CacheProvider, ICache } from '../src/types';
describe('NamespacedCache', () => {
let mockCache: CacheProvider;
let namespacedCache: NamespacedCache;
beforeEach(() => {
// Create mock base cache
mockCache = {
get: mock(async () => null),
set: mock(async () => null),
del: mock(async () => {}),
exists: mock(async () => false),
clear: mock(async () => {}),
keys: mock(async () => []),
getStats: mock(() => ({
hits: 100,
misses: 20,
errors: 5,
hitRate: 0.83,
total: 120,
uptime: 3600,
})),
health: mock(async () => true),
waitForReady: mock(async () => {}),
isReady: mock(() => true),
};
// Create namespaced cache
namespacedCache = new NamespacedCache(mockCache, 'test-namespace');
});
describe('constructor', () => {
it('should set namespace and prefix correctly', () => {
expect(namespacedCache.getNamespace()).toBe('test-namespace');
expect(namespacedCache.getFullPrefix()).toBe('test-namespace:');
});
it('should handle empty namespace', () => {
const emptyNamespace = new NamespacedCache(mockCache, '');
expect(emptyNamespace.getNamespace()).toBe('');
expect(emptyNamespace.getFullPrefix()).toBe(':');
});
});
describe('get', () => {
it('should prefix key when getting', async () => {
const testData = { value: 'test' };
(mockCache.get as any).mockResolvedValue(testData);
const result = await namespacedCache.get('mykey');
expect(mockCache.get).toHaveBeenCalledWith('test-namespace:mykey');
expect(result).toEqual(testData);
});
it('should handle null values', async () => {
(mockCache.get as any).mockResolvedValue(null);
const result = await namespacedCache.get('nonexistent');
expect(mockCache.get).toHaveBeenCalledWith('test-namespace:nonexistent');
expect(result).toBeNull();
});
});
describe('set', () => {
it('should prefix key when setting with ttl number', async () => {
const value = { data: 'test' };
const ttl = 3600;
await namespacedCache.set('mykey', value, ttl);
expect(mockCache.set).toHaveBeenCalledWith('test-namespace:mykey', value, ttl);
});
it('should prefix key when setting with options object', async () => {
const value = 'test-value';
const options = { ttl: 7200 };
await namespacedCache.set('mykey', value, options);
expect(mockCache.set).toHaveBeenCalledWith('test-namespace:mykey', value, options);
});
it('should handle set without TTL', async () => {
const value = [1, 2, 3];
await namespacedCache.set('mykey', value);
expect(mockCache.set).toHaveBeenCalledWith('test-namespace:mykey', value, undefined);
});
});
describe('del', () => {
it('should prefix key when deleting', async () => {
await namespacedCache.del('mykey');
expect(mockCache.del).toHaveBeenCalledWith('test-namespace:mykey');
});
it('should handle multiple deletes', async () => {
await namespacedCache.del('key1');
await namespacedCache.del('key2');
expect(mockCache.del).toHaveBeenCalledTimes(2);
expect(mockCache.del).toHaveBeenCalledWith('test-namespace:key1');
expect(mockCache.del).toHaveBeenCalledWith('test-namespace:key2');
});
});
describe('exists', () => {
it('should prefix key when checking existence', async () => {
(mockCache.exists as any).mockResolvedValue(true);
const result = await namespacedCache.exists('mykey');
expect(mockCache.exists).toHaveBeenCalledWith('test-namespace:mykey');
expect(result).toBe(true);
});
it('should return false for non-existent keys', async () => {
(mockCache.exists as any).mockResolvedValue(false);
const result = await namespacedCache.exists('nonexistent');
expect(result).toBe(false);
});
});
describe('keys', () => {
it('should prefix pattern and strip prefix from results', async () => {
(mockCache.keys as any).mockResolvedValue([
'test-namespace:key1',
'test-namespace:key2',
'test-namespace:key3',
]);
const keys = await namespacedCache.keys('*');
expect(mockCache.keys).toHaveBeenCalledWith('test-namespace:*');
expect(keys).toEqual(['key1', 'key2', 'key3']);
});
it('should handle specific patterns', async () => {
(mockCache.keys as any).mockResolvedValue([
'test-namespace:user:123',
'test-namespace:user:456',
]);
const keys = await namespacedCache.keys('user:*');
expect(mockCache.keys).toHaveBeenCalledWith('test-namespace:user:*');
expect(keys).toEqual(['user:123', 'user:456']);
});
it('should filter out keys from other namespaces', async () => {
(mockCache.keys as any).mockResolvedValue([
'test-namespace:key1',
'other-namespace:key2',
'test-namespace:key3',
]);
const keys = await namespacedCache.keys('*');
expect(keys).toEqual(['key1', 'key3']);
});
it('should handle empty results', async () => {
(mockCache.keys as any).mockResolvedValue([]);
const keys = await namespacedCache.keys('nonexistent*');
expect(keys).toEqual([]);
});
});
describe('clear', () => {
it('should clear only namespaced keys', async () => {
(mockCache.keys as any).mockResolvedValue([
'test-namespace:key1',
'test-namespace:key2',
'test-namespace:key3',
]);
await namespacedCache.clear();
expect(mockCache.keys).toHaveBeenCalledWith('test-namespace:*');
expect(mockCache.del).toHaveBeenCalledTimes(3);
expect(mockCache.del).toHaveBeenCalledWith('key1');
expect(mockCache.del).toHaveBeenCalledWith('key2');
expect(mockCache.del).toHaveBeenCalledWith('key3');
});
it('should handle empty namespace', async () => {
(mockCache.keys as any).mockResolvedValue([]);
await namespacedCache.clear();
expect(mockCache.keys).toHaveBeenCalledWith('test-namespace:*');
expect(mockCache.del).not.toHaveBeenCalled();
});
});
describe('delegated methods', () => {
it('should delegate getStats', () => {
const stats = namespacedCache.getStats();
expect(mockCache.getStats).toHaveBeenCalled();
expect(stats).toEqual({
hits: 100,
misses: 20,
errors: 5,
hitRate: 0.83,
total: 120,
uptime: 3600,
});
});
it('should delegate health', async () => {
const health = await namespacedCache.health();
expect(mockCache.health).toHaveBeenCalled();
expect(health).toBe(true);
});
it('should delegate waitForReady', async () => {
await namespacedCache.waitForReady(5000);
expect(mockCache.waitForReady).toHaveBeenCalledWith(5000);
});
it('should delegate isReady', () => {
const ready = namespacedCache.isReady();
expect(mockCache.isReady).toHaveBeenCalled();
expect(ready).toBe(true);
});
});
describe('edge cases', () => {
it('should handle special characters in namespace', () => {
const specialNamespace = new NamespacedCache(mockCache, 'test:namespace:with:colons');
expect(specialNamespace.getFullPrefix()).toBe('test:namespace:with:colons:');
});
it('should handle very long keys', async () => {
const longKey = 'a'.repeat(1000);
await namespacedCache.get(longKey);
expect(mockCache.get).toHaveBeenCalledWith(`test-namespace:${longKey}`);
});
it('should handle errors from underlying cache', async () => {
const error = new Error('Cache error');
(mockCache.get as any).mockRejectedValue(error);
await expect(namespacedCache.get('key')).rejects.toThrow('Cache error');
});
});
});
describe('CacheAdapter', () => {
let mockICache: ICache;
let adapter: CacheAdapter;
beforeEach(() => {
mockICache = {
get: mock(async () => null),
set: mock(async () => {}),
del: mock(async () => {}),
exists: mock(async () => false),
clear: mock(async () => {}),
keys: mock(async () => []),
ping: mock(async () => true),
isConnected: mock(() => true),
has: mock(async () => false),
ttl: mock(async () => -1),
type: 'memory' as const,
};
adapter = new CacheAdapter(mockICache);
});
describe('get', () => {
it('should delegate to ICache.get', async () => {
const data = { value: 'test' };
(mockICache.get as any).mockResolvedValue(data);
const result = await adapter.get('key');
expect(mockICache.get).toHaveBeenCalledWith('key');
expect(result).toEqual(data);
});
});
describe('set', () => {
it('should handle TTL as number', async () => {
await adapter.set('key', 'value', 3600);
expect(mockICache.set).toHaveBeenCalledWith('key', 'value', 3600);
});
it('should handle TTL as options object', async () => {
await adapter.set('key', 'value', { ttl: 7200 });
expect(mockICache.set).toHaveBeenCalledWith('key', 'value', 7200);
});
it('should handle no TTL', async () => {
await adapter.set('key', 'value');
expect(mockICache.set).toHaveBeenCalledWith('key', 'value', undefined);
});
it('should always return null', async () => {
const result = await adapter.set('key', 'value');
expect(result).toBeNull();
});
});
describe('del', () => {
it('should delegate to ICache.del', async () => {
await adapter.del('key');
expect(mockICache.del).toHaveBeenCalledWith('key');
});
});
describe('exists', () => {
it('should delegate to ICache.exists', async () => {
(mockICache.exists as any).mockResolvedValue(true);
const result = await adapter.exists('key');
expect(mockICache.exists).toHaveBeenCalledWith('key');
expect(result).toBe(true);
});
});
describe('clear', () => {
it('should delegate to ICache.clear', async () => {
await adapter.clear();
expect(mockICache.clear).toHaveBeenCalled();
});
});
describe('keys', () => {
it('should delegate to ICache.keys', async () => {
const keys = ['key1', 'key2'];
(mockICache.keys as any).mockResolvedValue(keys);
const result = await adapter.keys('*');
expect(mockICache.keys).toHaveBeenCalledWith('*');
expect(result).toEqual(keys);
});
});
describe('getStats', () => {
it('should return default stats', () => {
const stats = adapter.getStats();
expect(stats).toEqual({
hits: 0,
misses: 0,
errors: 0,
hitRate: 0,
total: 0,
uptime: expect.any(Number),
});
});
});
describe('health', () => {
it('should use ping for health check', async () => {
(mockICache.ping as any).mockResolvedValue(true);
const result = await adapter.health();
expect(mockICache.ping).toHaveBeenCalled();
expect(result).toBe(true);
});
it('should handle ping failures', async () => {
(mockICache.ping as any).mockResolvedValue(false);
const result = await adapter.health();
expect(result).toBe(false);
});
});
describe('waitForReady', () => {
it('should succeed if connected', async () => {
(mockICache.isConnected as any).mockReturnValue(true);
await expect(adapter.waitForReady()).resolves.toBeUndefined();
});
it('should throw if not connected', async () => {
(mockICache.isConnected as any).mockReturnValue(false);
await expect(adapter.waitForReady()).rejects.toThrow('Cache not connected');
});
});
describe('isReady', () => {
it('should delegate to isConnected', () => {
(mockICache.isConnected as any).mockReturnValue(true);
const result = adapter.isReady();
expect(mockICache.isConnected).toHaveBeenCalled();
expect(result).toBe(true);
});
it('should return false when not connected', () => {
(mockICache.isConnected as any).mockReturnValue(false);
const result = adapter.isReady();
expect(result).toBe(false);
});
});
});

View file

@ -1,37 +1,37 @@
import { describe, it, expect, beforeEach } from 'bun:test';
import { RedisCache } from '../src/redis-cache';
import type { CacheOptions } from '../src/types';
describe('RedisCache Simple', () => {
let cache: RedisCache;
beforeEach(() => {
const options: CacheOptions = {
keyPrefix: 'test:',
ttl: 3600,
redisConfig: { host: 'localhost', port: 6379 },
};
cache = new RedisCache(options);
});
describe('Core functionality', () => {
it('should create cache instance', () => {
expect(cache).toBeDefined();
expect(cache.isReady).toBeDefined();
expect(cache.get).toBeDefined();
expect(cache.set).toBeDefined();
});
it('should have stats tracking', () => {
const stats = cache.getStats();
expect(stats).toMatchObject({
hits: 0,
misses: 0,
errors: 0,
hitRate: 0,
total: 0,
});
expect(stats.uptime).toBeGreaterThanOrEqual(0);
});
});
});
import { beforeEach, describe, expect, it } from 'bun:test';
import { RedisCache } from '../src/redis-cache';
import type { CacheOptions } from '../src/types';
describe('RedisCache Simple', () => {
let cache: RedisCache;
beforeEach(() => {
const options: CacheOptions = {
keyPrefix: 'test:',
ttl: 3600,
redisConfig: { host: 'localhost', port: 6379 },
};
cache = new RedisCache(options);
});
describe('Core functionality', () => {
it('should create cache instance', () => {
expect(cache).toBeDefined();
expect(cache.isReady).toBeDefined();
expect(cache.get).toBeDefined();
expect(cache.set).toBeDefined();
});
it('should have stats tracking', () => {
const stats = cache.getStats();
expect(stats).toMatchObject({
hits: 0,
misses: 0,
errors: 0,
hitRate: 0,
total: 0,
});
expect(stats.uptime).toBeGreaterThanOrEqual(0);
});
});
});

View file

@ -1,210 +1,210 @@
import { describe, it, expect, beforeEach } from 'bun:test';
import { RedisCache } from '../src/redis-cache';
import type { CacheOptions } from '../src/types';
describe('RedisCache', () => {
let cache: RedisCache;
beforeEach(() => {
const options: CacheOptions = {
keyPrefix: 'test:',
ttl: 3600,
redisConfig: { host: 'localhost', port: 6379 },
};
cache = new RedisCache(options);
});
describe('Core functionality', () => {
it('should create cache instance', () => {
expect(cache).toBeDefined();
expect(cache.isReady).toBeDefined();
expect(cache.get).toBeDefined();
expect(cache.set).toBeDefined();
});
it('should have stats tracking', () => {
const stats = cache.getStats();
expect(stats).toMatchObject({
hits: 0,
misses: 0,
errors: 0,
hitRate: 0,
total: 0,
});
expect(stats.uptime).toBeGreaterThanOrEqual(0);
});
});
describe('Basic operations', () => {
it('should handle get/set operations', async () => {
const key = 'test-key';
const value = { foo: 'bar' };
// Should return null for non-existent key
const miss = await cache.get(key);
expect(miss).toBeNull();
// Should set and retrieve value
await cache.set(key, value);
const retrieved = await cache.get(key);
expect(retrieved).toEqual(value);
// Should delete key
await cache.del(key);
const deleted = await cache.get(key);
expect(deleted).toBeNull();
});
it('should check key existence', async () => {
const key = 'existence-test';
expect(await cache.exists(key)).toBe(false);
await cache.set(key, 'value');
expect(await cache.exists(key)).toBe(true);
await cache.del(key);
expect(await cache.exists(key)).toBe(false);
});
it('should handle TTL in set operations', async () => {
const key = 'ttl-test';
const value = 'test-value';
// Set with custom TTL as number
await cache.set(key, value, 1);
expect(await cache.get(key)).toBe(value);
// Set with custom TTL in options
await cache.set(key, value, { ttl: 2 });
expect(await cache.get(key)).toBe(value);
});
});
describe('Advanced set options', () => {
it('should handle onlyIfExists option', async () => {
const key = 'conditional-test';
const value1 = 'value1';
const value2 = 'value2';
// Should not set if key doesn't exist
await cache.set(key, value1, { onlyIfExists: true });
expect(await cache.get(key)).toBeNull();
// Create the key
await cache.set(key, value1);
// Should update if key exists
await cache.set(key, value2, { onlyIfExists: true });
expect(await cache.get(key)).toBe(value2);
});
it('should handle onlyIfNotExists option', async () => {
const key = 'nx-test';
const value1 = 'value1';
const value2 = 'value2';
// Should set if key doesn't exist
await cache.set(key, value1, { onlyIfNotExists: true });
expect(await cache.get(key)).toBe(value1);
// Should not update if key exists
await cache.set(key, value2, { onlyIfNotExists: true });
expect(await cache.get(key)).toBe(value1);
});
it('should handle preserveTTL option', async () => {
const key = 'preserve-ttl-test';
const value1 = 'value1';
const value2 = 'value2';
// Set with short TTL
await cache.set(key, value1, 10);
// Update preserving TTL
await cache.set(key, value2, { preserveTTL: true });
expect(await cache.get(key)).toBe(value2);
});
it('should handle getOldValue option', async () => {
const key = 'old-value-test';
const value1 = 'value1';
const value2 = 'value2';
// Should return null when no old value
const oldValue1 = await cache.set(key, value1, { getOldValue: true });
expect(oldValue1).toBeNull();
// Should return old value
const oldValue2 = await cache.set(key, value2, { getOldValue: true });
expect(oldValue2).toBe(value1);
});
});
describe('Error handling', () => {
it('should handle errors gracefully in get', async () => {
// Force an error by using invalid JSON
const badCache = new RedisCache({
keyPrefix: 'bad:',
redisConfig: { host: 'localhost', port: 6379 },
});
// This would normally throw but should return null
const result = await badCache.get('non-existent');
expect(result).toBeNull();
// Check stats updated
const stats = badCache.getStats();
expect(stats.misses).toBe(1);
});
});
describe('Pattern operations', () => {
it('should find keys by pattern', async () => {
// Clear first to ensure clean state
await cache.clear();
await cache.set('user:1', { id: 1 });
await cache.set('user:2', { id: 2 });
await cache.set('post:1', { id: 1 });
const userKeys = await cache.keys('user:*');
expect(userKeys).toHaveLength(2);
expect(userKeys).toContain('user:1');
expect(userKeys).toContain('user:2');
const allKeys = await cache.keys('*');
expect(allKeys.length).toBeGreaterThanOrEqual(3);
expect(allKeys).toContain('user:1');
expect(allKeys).toContain('user:2');
expect(allKeys).toContain('post:1');
});
it('should clear all keys with prefix', async () => {
await cache.set('key1', 'value1');
await cache.set('key2', 'value2');
await cache.clear();
const keys = await cache.keys('*');
expect(keys).toHaveLength(0);
});
});
describe('Health checks', () => {
it('should check health', async () => {
const healthy = await cache.health();
expect(healthy).toBe(true);
});
it('should check if ready', () => {
// May not be ready immediately
const ready = cache.isReady();
expect(typeof ready).toBe('boolean');
});
it('should wait for ready', async () => {
await expect(cache.waitForReady(1000)).resolves.toBeUndefined();
});
});
});
import { beforeEach, describe, expect, it } from 'bun:test';
import { RedisCache } from '../src/redis-cache';
import type { CacheOptions } from '../src/types';
describe('RedisCache', () => {
let cache: RedisCache;
beforeEach(() => {
const options: CacheOptions = {
keyPrefix: 'test:',
ttl: 3600,
redisConfig: { host: 'localhost', port: 6379 },
};
cache = new RedisCache(options);
});
describe('Core functionality', () => {
it('should create cache instance', () => {
expect(cache).toBeDefined();
expect(cache.isReady).toBeDefined();
expect(cache.get).toBeDefined();
expect(cache.set).toBeDefined();
});
it('should have stats tracking', () => {
const stats = cache.getStats();
expect(stats).toMatchObject({
hits: 0,
misses: 0,
errors: 0,
hitRate: 0,
total: 0,
});
expect(stats.uptime).toBeGreaterThanOrEqual(0);
});
});
describe('Basic operations', () => {
it('should handle get/set operations', async () => {
const key = 'test-key';
const value = { foo: 'bar' };
// Should return null for non-existent key
const miss = await cache.get(key);
expect(miss).toBeNull();
// Should set and retrieve value
await cache.set(key, value);
const retrieved = await cache.get(key);
expect(retrieved).toEqual(value);
// Should delete key
await cache.del(key);
const deleted = await cache.get(key);
expect(deleted).toBeNull();
});
it('should check key existence', async () => {
const key = 'existence-test';
expect(await cache.exists(key)).toBe(false);
await cache.set(key, 'value');
expect(await cache.exists(key)).toBe(true);
await cache.del(key);
expect(await cache.exists(key)).toBe(false);
});
it('should handle TTL in set operations', async () => {
const key = 'ttl-test';
const value = 'test-value';
// Set with custom TTL as number
await cache.set(key, value, 1);
expect(await cache.get(key)).toBe(value);
// Set with custom TTL in options
await cache.set(key, value, { ttl: 2 });
expect(await cache.get(key)).toBe(value);
});
});
describe('Advanced set options', () => {
it('should handle onlyIfExists option', async () => {
const key = 'conditional-test';
const value1 = 'value1';
const value2 = 'value2';
// Should not set if key doesn't exist
await cache.set(key, value1, { onlyIfExists: true });
expect(await cache.get(key)).toBeNull();
// Create the key
await cache.set(key, value1);
// Should update if key exists
await cache.set(key, value2, { onlyIfExists: true });
expect(await cache.get(key)).toBe(value2);
});
it('should handle onlyIfNotExists option', async () => {
const key = 'nx-test';
const value1 = 'value1';
const value2 = 'value2';
// Should set if key doesn't exist
await cache.set(key, value1, { onlyIfNotExists: true });
expect(await cache.get(key)).toBe(value1);
// Should not update if key exists
await cache.set(key, value2, { onlyIfNotExists: true });
expect(await cache.get(key)).toBe(value1);
});
it('should handle preserveTTL option', async () => {
const key = 'preserve-ttl-test';
const value1 = 'value1';
const value2 = 'value2';
// Set with short TTL
await cache.set(key, value1, 10);
// Update preserving TTL
await cache.set(key, value2, { preserveTTL: true });
expect(await cache.get(key)).toBe(value2);
});
it('should handle getOldValue option', async () => {
const key = 'old-value-test';
const value1 = 'value1';
const value2 = 'value2';
// Should return null when no old value
const oldValue1 = await cache.set(key, value1, { getOldValue: true });
expect(oldValue1).toBeNull();
// Should return old value
const oldValue2 = await cache.set(key, value2, { getOldValue: true });
expect(oldValue2).toBe(value1);
});
});
describe('Error handling', () => {
it('should handle errors gracefully in get', async () => {
// Force an error by using invalid JSON
const badCache = new RedisCache({
keyPrefix: 'bad:',
redisConfig: { host: 'localhost', port: 6379 },
});
// This would normally throw but should return null
const result = await badCache.get('non-existent');
expect(result).toBeNull();
// Check stats updated
const stats = badCache.getStats();
expect(stats.misses).toBe(1);
});
});
describe('Pattern operations', () => {
it('should find keys by pattern', async () => {
// Clear first to ensure clean state
await cache.clear();
await cache.set('user:1', { id: 1 });
await cache.set('user:2', { id: 2 });
await cache.set('post:1', { id: 1 });
const userKeys = await cache.keys('user:*');
expect(userKeys).toHaveLength(2);
expect(userKeys).toContain('user:1');
expect(userKeys).toContain('user:2');
const allKeys = await cache.keys('*');
expect(allKeys.length).toBeGreaterThanOrEqual(3);
expect(allKeys).toContain('user:1');
expect(allKeys).toContain('user:2');
expect(allKeys).toContain('post:1');
});
it('should clear all keys with prefix', async () => {
await cache.set('key1', 'value1');
await cache.set('key2', 'value2');
await cache.clear();
const keys = await cache.keys('*');
expect(keys).toHaveLength(0);
});
});
describe('Health checks', () => {
it('should check health', async () => {
const healthy = await cache.health();
expect(healthy).toBe(true);
});
it('should check if ready', () => {
// May not be ready immediately
const ready = cache.isReady();
expect(typeof ready).toBe('boolean');
});
it('should wait for ready', async () => {
await expect(cache.waitForReady(1000)).resolves.toBeUndefined();
});
});
});