created lots of tests
This commit is contained in:
parent
42baadae38
commit
54f37f9521
21 changed files with 4577 additions and 215 deletions
253
libs/core/queue/test/dlq-handler.test.ts
Normal file
253
libs/core/queue/test/dlq-handler.test.ts
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
import { beforeEach, describe, expect, it, mock } from 'bun:test';
|
||||
import { DeadLetterQueueHandler } from '../src/dlq-handler';
|
||||
import type { Job, Queue } from 'bullmq';
|
||||
import type { RedisConfig } from '../src/types';
|
||||
|
||||
describe('DeadLetterQueueHandler', () => {
|
||||
const mockLogger = {
|
||||
info: mock(() => {}),
|
||||
error: mock(() => {}),
|
||||
warn: mock(() => {}),
|
||||
debug: mock(() => {}),
|
||||
trace: mock(() => {}),
|
||||
};
|
||||
|
||||
const mockQueue = {
|
||||
name: 'test-queue',
|
||||
add: mock(() => Promise.resolve({})),
|
||||
getCompleted: mock(() => Promise.resolve([])),
|
||||
getFailed: mock(() => Promise.resolve([])),
|
||||
getWaiting: mock(() => Promise.resolve([])),
|
||||
} as unknown as Queue;
|
||||
|
||||
const mockRedisConfig: RedisConfig = {
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
};
|
||||
|
||||
let dlqHandler: DeadLetterQueueHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
dlqHandler = new DeadLetterQueueHandler(mockQueue, mockRedisConfig, {}, mockLogger);
|
||||
// Reset mocks
|
||||
mockLogger.info = mock(() => {});
|
||||
mockLogger.error = mock(() => {});
|
||||
mockLogger.warn = mock(() => {});
|
||||
});
|
||||
|
||||
describe('handleFailedJob', () => {
|
||||
it('should log failed job details', async () => {
|
||||
const mockJob = {
|
||||
id: 'job-123',
|
||||
name: 'test-job',
|
||||
queueName: 'test-queue',
|
||||
data: {
|
||||
handler: 'testHandler',
|
||||
operation: 'testOp',
|
||||
payload: { test: true },
|
||||
},
|
||||
attemptsMade: 3,
|
||||
failedReason: 'Test error',
|
||||
finishedOn: Date.now(),
|
||||
processedOn: Date.now() - 5000,
|
||||
} as Job;
|
||||
|
||||
const error = new Error('Job processing failed');
|
||||
|
||||
await dlqHandler.handleFailedJob(mockJob, error);
|
||||
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
'Job moved to DLQ',
|
||||
expect.objectContaining({
|
||||
jobId: 'job-123',
|
||||
jobName: 'test-job',
|
||||
error: 'Job processing failed',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle jobs without data gracefully', async () => {
|
||||
const mockJob = {
|
||||
id: 'job-123',
|
||||
name: 'test-job',
|
||||
queueName: 'test-queue',
|
||||
data: null,
|
||||
attemptsMade: 1,
|
||||
} as any;
|
||||
|
||||
const error = new Error('No data');
|
||||
|
||||
await dlqHandler.handleFailedJob(mockJob, error);
|
||||
|
||||
expect(mockLogger.error).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should check alert threshold', async () => {
|
||||
const mockJob = {
|
||||
id: 'job-123',
|
||||
name: 'critical-job',
|
||||
queueName: 'critical-queue',
|
||||
data: { handler: 'critical', operation: 'process' },
|
||||
attemptsMade: 3,
|
||||
opts: { attempts: 3 },
|
||||
} as Job;
|
||||
|
||||
const error = new Error('Critical failure');
|
||||
|
||||
await dlqHandler.handleFailedJob(mockJob, error);
|
||||
|
||||
expect(mockLogger.warn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStats', () => {
|
||||
it('should return DLQ statistics', async () => {
|
||||
const mockJobs = [
|
||||
{
|
||||
id: 'job-1',
|
||||
data: { originalJob: { name: 'process' } },
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'job-2',
|
||||
data: { originalJob: { name: 'process' } },
|
||||
timestamp: Date.now() - 100000,
|
||||
},
|
||||
{
|
||||
id: 'job-3',
|
||||
data: { originalJob: { name: 'validate' } },
|
||||
timestamp: Date.now() - 200000,
|
||||
},
|
||||
];
|
||||
|
||||
(mockQueue.getCompleted as any) = mock(() => Promise.resolve(mockJobs));
|
||||
(mockQueue.getFailed as any) = mock(() => Promise.resolve([]));
|
||||
(mockQueue.getWaiting as any) = mock(() => Promise.resolve([]));
|
||||
|
||||
const stats = await dlqHandler.getStats();
|
||||
|
||||
expect(stats.total).toBe(3);
|
||||
expect(stats.byJobName['process']).toBe(2);
|
||||
expect(stats.byJobName['validate']).toBe(1);
|
||||
expect(stats.oldestJob).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle empty DLQ', async () => {
|
||||
const stats = await dlqHandler.getStats();
|
||||
|
||||
expect(stats.total).toBe(0);
|
||||
expect(stats.byJobName).toEqual({});
|
||||
expect(stats.oldestJob).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('retryDLQJobs', () => {
|
||||
it('should retry jobs from DLQ', async () => {
|
||||
const mockDLQJobs = [
|
||||
{
|
||||
id: 'dlq-1',
|
||||
data: {
|
||||
originalJob: {
|
||||
id: 'orig-1',
|
||||
name: 'retry-job',
|
||||
data: { test: true },
|
||||
opts: {},
|
||||
},
|
||||
},
|
||||
remove: mock(() => Promise.resolve()),
|
||||
},
|
||||
];
|
||||
|
||||
(dlqHandler as any).dlq = {
|
||||
getCompleted: mock(() => Promise.resolve(mockDLQJobs)),
|
||||
};
|
||||
|
||||
const retriedCount = await dlqHandler.retryDLQJobs(1);
|
||||
|
||||
expect(retriedCount).toBe(1);
|
||||
expect(mockQueue.add).toHaveBeenCalledWith(
|
||||
'retry-job',
|
||||
{ test: true },
|
||||
expect.objectContaining({ delay: expect.any(Number) })
|
||||
);
|
||||
expect(mockDLQJobs[0].remove).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanup', () => {
|
||||
it('should clean up old DLQ entries', async () => {
|
||||
const oldJob = {
|
||||
id: 'old-1',
|
||||
timestamp: Date.now() - 8 * 24 * 60 * 60 * 1000, // 8 days old
|
||||
remove: mock(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
const newJob = {
|
||||
id: 'new-1',
|
||||
timestamp: Date.now() - 1 * 24 * 60 * 60 * 1000, // 1 day old
|
||||
remove: mock(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
(dlqHandler as any).dlq = {
|
||||
getCompleted: mock(() => Promise.resolve([oldJob, newJob])),
|
||||
};
|
||||
|
||||
const removedCount = await dlqHandler.cleanup();
|
||||
|
||||
expect(removedCount).toBe(1);
|
||||
expect(oldJob.remove).toHaveBeenCalled();
|
||||
expect(newJob.remove).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspectFailedJobs', () => {
|
||||
it('should return formatted failed jobs', async () => {
|
||||
const mockDLQJobs = [
|
||||
{
|
||||
data: {
|
||||
originalJob: {
|
||||
id: 'orig-1',
|
||||
name: 'test-job',
|
||||
data: { test: true },
|
||||
attemptsMade: 3,
|
||||
},
|
||||
error: {
|
||||
message: 'Test error',
|
||||
stack: 'Error stack',
|
||||
},
|
||||
movedToDLQAt: '2024-01-01T00:00:00Z',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
(dlqHandler as any).dlq = {
|
||||
getCompleted: mock(() => Promise.resolve(mockDLQJobs)),
|
||||
};
|
||||
|
||||
const inspected = await dlqHandler.inspectFailedJobs(10);
|
||||
|
||||
expect(inspected).toHaveLength(1);
|
||||
expect(inspected[0]).toEqual({
|
||||
id: 'orig-1',
|
||||
name: 'test-job',
|
||||
data: { test: true },
|
||||
error: { message: 'Test error', stack: 'Error stack' },
|
||||
failedAt: '2024-01-01T00:00:00Z',
|
||||
attempts: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('shutdown', () => {
|
||||
it('should close DLQ and clear state', async () => {
|
||||
const mockClose = mock(() => Promise.resolve());
|
||||
(dlqHandler as any).dlq = {
|
||||
close: mockClose,
|
||||
};
|
||||
|
||||
await dlqHandler.shutdown();
|
||||
|
||||
expect(mockClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue