@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
254 lines • 10.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const redis_storage_driver_1 = require("./redis-storage.driver");
jest.mock('ioredis', () => {
const MockRedis = jest.fn().mockImplementation(() => ({
on: jest.fn(),
setex: jest.fn(),
zadd: jest.fn(),
hset: jest.fn(),
expire: jest.fn(),
pipeline: jest.fn(),
zrevrange: jest.fn(),
zrevrangebyscore: jest.fn(),
hget: jest.fn(),
get: jest.fn(),
hgetall: jest.fn(),
zrem: jest.fn(),
del: jest.fn(),
keys: jest.fn(),
zcard: jest.fn(),
zrange: jest.fn(),
zrangebyscore: jest.fn(),
ping: jest.fn(),
disconnect: jest.fn(),
}));
return { Redis: MockRedis };
});
describe('RedisStorageDriver', () => {
let driver;
let mockRedis;
beforeEach(() => {
const Redis = require('ioredis');
driver = new redis_storage_driver_1.RedisStorageDriver({
redis: {
host: 'localhost',
port: 6379,
ttl: 3600,
},
});
mockRedis = driver.redis;
});
afterEach(() => {
jest.clearAllMocks();
});
describe('store', () => {
it('should store a single entry', async () => {
const entry = {
id: 'test-id',
type: 'request',
familyHash: 'hash123',
content: { method: 'GET', url: '/test' },
tags: ['http'],
timestamp: new Date(),
sequence: 1,
};
mockRedis.setex.mockResolvedValue('OK');
mockRedis.zadd.mockResolvedValue(1);
mockRedis.hset.mockResolvedValue(1);
mockRedis.expire.mockResolvedValue(1);
await driver.store(entry);
expect(mockRedis.setex).toHaveBeenCalledWith('telescope:entry:test-id', 3600, JSON.stringify(entry));
expect(mockRedis.zadd).toHaveBeenCalledWith('telescope:index:request', entry.timestamp.getTime(), 'test-id');
});
});
describe('storeBatch', () => {
it('should store multiple entries using pipeline', async () => {
const entries = [
{
id: 'test-id-1',
type: 'request',
familyHash: 'hash123',
content: { method: 'GET', url: '/test1' },
tags: ['http'],
timestamp: new Date(),
sequence: 1,
},
{
id: 'test-id-2',
type: 'request',
familyHash: 'hash456',
content: { method: 'POST', url: '/test2' },
tags: ['http'],
timestamp: new Date(),
sequence: 2,
},
];
const mockPipeline = {
setex: jest.fn(),
zadd: jest.fn(),
hset: jest.fn(),
expire: jest.fn(),
exec: jest.fn().mockResolvedValue([]),
};
mockRedis.pipeline.mockReturnValue(mockPipeline);
await driver.storeBatch(entries);
expect(mockRedis.pipeline).toHaveBeenCalled();
expect(mockPipeline.setex).toHaveBeenCalledTimes(2);
expect(mockPipeline.zadd).toHaveBeenCalledTimes(4);
expect(mockPipeline.exec).toHaveBeenCalled();
});
});
describe('find', () => {
it('should find entries with type filter', async () => {
const entryData = {
id: 'test-id',
type: 'request',
familyHash: 'hash123',
content: { method: 'GET', url: '/test' },
tags: ['http'],
timestamp: new Date().toISOString(),
sequence: 1,
};
mockRedis.zrevrange.mockResolvedValue(['test-id']);
const mockPipeline = {
get: jest.fn(),
exec: jest.fn().mockResolvedValue([[null, JSON.stringify(entryData)]]),
};
mockRedis.pipeline.mockReturnValue(mockPipeline);
const result = await driver.find({ type: 'request', limit: 10 });
expect(result.entries).toHaveLength(1);
expect(result.total).toBe(1);
expect(result.hasMore).toBe(false);
expect(mockRedis.zrevrange).toHaveBeenCalledWith('telescope:index:request', 0, -1);
});
it('should find entries with date range filter', async () => {
const dateFrom = new Date(Date.now() - 24 * 60 * 60 * 1000);
const dateTo = new Date();
mockRedis.zrevrangebyscore.mockResolvedValue(['test-id']);
const mockPipeline = {
get: jest.fn(),
exec: jest.fn().mockResolvedValue([[null, JSON.stringify({})]]),
};
mockRedis.pipeline.mockReturnValue(mockPipeline);
await driver.find({ dateFrom, dateTo, limit: 10 });
expect(mockRedis.zrevrangebyscore).toHaveBeenCalledWith('telescope:timestamp', dateTo.getTime(), dateFrom.getTime());
});
it('should find entries with tag filter', async () => {
mockRedis.zrevrange.mockResolvedValue(['test-id']);
mockRedis.hget.mockResolvedValue('["http", "api"]');
const mockPipeline = {
get: jest.fn(),
exec: jest.fn().mockResolvedValue([[null, JSON.stringify({})]]),
};
mockRedis.pipeline.mockReturnValue(mockPipeline);
await driver.find({ tags: ['http'], limit: 10 });
expect(mockRedis.hget).toHaveBeenCalledWith('telescope:meta:test-id', 'tags');
});
});
describe('findById', () => {
it('should find entry by id', async () => {
const entryData = {
id: 'test-id',
type: 'request',
familyHash: 'hash123',
content: { method: 'GET', url: '/test' },
tags: ['http'],
timestamp: new Date().toISOString(),
sequence: 1,
};
mockRedis.get.mockResolvedValue(JSON.stringify(entryData));
const result = await driver.findById('test-id');
expect(result).toEqual(expect.objectContaining({
id: 'test-id',
type: 'request',
timestamp: expect.any(Date),
}));
expect(mockRedis.get).toHaveBeenCalledWith('telescope:entry:test-id');
});
it('should return null if entry not found', async () => {
mockRedis.get.mockResolvedValue(null);
const result = await driver.findById('non-existent');
expect(result).toBeNull();
});
});
describe('delete', () => {
it('should delete entry by id', async () => {
mockRedis.hgetall.mockResolvedValue({ type: 'request' });
const mockPipeline = {
del: jest.fn(),
zrem: jest.fn(),
exec: jest.fn().mockResolvedValue([[null, 1]]),
};
mockRedis.pipeline.mockReturnValue(mockPipeline);
const result = await driver.delete('test-id');
expect(result).toBe(true);
expect(mockPipeline.del).toHaveBeenCalledWith('telescope:entry:test-id');
expect(mockPipeline.zrem).toHaveBeenCalledWith('telescope:index:request', 'test-id');
});
it('should return false if entry not found', async () => {
mockRedis.hgetall.mockResolvedValue({});
const result = await driver.delete('non-existent');
expect(result).toBe(false);
});
});
describe('clear', () => {
it('should clear all telescope entries', async () => {
mockRedis.keys.mockResolvedValue(['telescope:entry:1', 'telescope:entry:2']);
mockRedis.del.mockResolvedValue(2);
await driver.clear();
expect(mockRedis.keys).toHaveBeenCalledWith('telescope:*');
expect(mockRedis.del).toHaveBeenCalledWith('telescope:entry:1', 'telescope:entry:2');
});
});
describe('prune', () => {
it('should prune old entries', async () => {
const olderThan = new Date(Date.now() - 24 * 60 * 60 * 1000);
mockRedis.zrangebyscore.mockResolvedValue(['old-entry-1', 'old-entry-2']);
mockRedis.hgetall.mockResolvedValue({ type: 'request' });
const mockPipeline = {
del: jest.fn(),
zrem: jest.fn(),
exec: jest.fn().mockResolvedValue([]),
};
mockRedis.pipeline.mockReturnValue(mockPipeline);
const result = await driver.prune(olderThan);
expect(result).toBe(2);
expect(mockRedis.zrangebyscore).toHaveBeenCalledWith('telescope:timestamp', 0, olderThan.getTime());
});
});
describe('getStats', () => {
it('should return storage statistics', async () => {
mockRedis.zcard.mockResolvedValue(10);
mockRedis.keys.mockResolvedValue(['telescope:index:request', 'telescope:index:query']);
mockRedis.zrange.mockResolvedValue(['entry-1', '1234567890']);
mockRedis.zrevrange.mockResolvedValue(['entry-2', '1234567890']);
const result = await driver.getStats();
expect(result.totalEntries).toBe(10);
expect(result.entriesByType).toBeDefined();
expect(result.oldestEntry).toBeInstanceOf(Date);
expect(result.newestEntry).toBeInstanceOf(Date);
});
});
describe('healthCheck', () => {
it('should return true when Redis is healthy', async () => {
mockRedis.ping.mockResolvedValue('PONG');
const result = await driver.healthCheck();
expect(result).toBe(true);
expect(mockRedis.ping).toHaveBeenCalled();
});
it('should return false when Redis is unhealthy', async () => {
mockRedis.ping.mockRejectedValue(new Error('Connection failed'));
const result = await driver.healthCheck();
expect(result).toBe(false);
});
});
describe('cleanup', () => {
it('should disconnect from Redis', async () => {
mockRedis.disconnect.mockResolvedValue(undefined);
await driver.cleanup();
expect(mockRedis.disconnect).toHaveBeenCalled();
});
});
});
//# sourceMappingURL=redis-storage.driver.spec.js.map