237 lines
5.6 KiB
TypeScript
237 lines
5.6 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
import { createJobHandler } from '../src/utils/create-job-handler';
|
|
|
|
describe('createJobHandler', () => {
|
|
interface TestPayload {
|
|
userId: string;
|
|
action: string;
|
|
data?: any;
|
|
}
|
|
|
|
interface TestResult {
|
|
success: boolean;
|
|
processedBy: string;
|
|
timestamp: Date;
|
|
}
|
|
|
|
it('should create a type-safe job handler function', () => {
|
|
const handler = createJobHandler<TestPayload, TestResult>(async job => {
|
|
// Job should have correct payload type
|
|
const { userId, action, data } = job.data;
|
|
|
|
return {
|
|
success: true,
|
|
processedBy: userId,
|
|
timestamp: new Date(),
|
|
};
|
|
});
|
|
|
|
expect(typeof handler).toBe('function');
|
|
});
|
|
|
|
it('should execute handler with job data', async () => {
|
|
const testPayload: TestPayload = {
|
|
userId: 'user-123',
|
|
action: 'process',
|
|
data: { value: 42 },
|
|
};
|
|
|
|
const handler = createJobHandler<TestPayload, TestResult>(async job => {
|
|
expect(job.data).toEqual(testPayload);
|
|
expect(job.id).toBe('job-123');
|
|
expect(job.name).toBe('test-job');
|
|
|
|
return {
|
|
success: true,
|
|
processedBy: job.data.userId,
|
|
timestamp: new Date(),
|
|
};
|
|
});
|
|
|
|
// Create a mock job
|
|
const mockJob = {
|
|
id: 'job-123',
|
|
name: 'test-job',
|
|
data: testPayload,
|
|
opts: {},
|
|
progress: () => {},
|
|
log: () => {},
|
|
updateProgress: async () => {},
|
|
};
|
|
|
|
const result = await handler(mockJob as any);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.processedBy).toBe('user-123');
|
|
expect(result.timestamp).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it('should handle errors in handler', async () => {
|
|
const handler = createJobHandler<TestPayload, TestResult>(async job => {
|
|
if (job.data.action === 'fail') {
|
|
throw new Error('Handler error');
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
processedBy: job.data.userId,
|
|
timestamp: new Date(),
|
|
};
|
|
});
|
|
|
|
const mockJob = {
|
|
id: 'job-456',
|
|
name: 'test-job',
|
|
data: {
|
|
userId: 'user-456',
|
|
action: 'fail',
|
|
},
|
|
opts: {},
|
|
progress: () => {},
|
|
log: () => {},
|
|
updateProgress: async () => {},
|
|
};
|
|
|
|
await expect(handler(mockJob as any)).rejects.toThrow('Handler error');
|
|
});
|
|
|
|
it('should support async operations', async () => {
|
|
const handler = createJobHandler<TestPayload, TestResult>(async job => {
|
|
// Simulate async operation
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
|
|
return {
|
|
success: true,
|
|
processedBy: job.data.userId,
|
|
timestamp: new Date(),
|
|
};
|
|
});
|
|
|
|
const mockJob = {
|
|
id: 'job-789',
|
|
name: 'async-job',
|
|
data: {
|
|
userId: 'user-789',
|
|
action: 'async-process',
|
|
},
|
|
opts: {},
|
|
progress: () => {},
|
|
log: () => {},
|
|
updateProgress: async () => {},
|
|
};
|
|
|
|
const startTime = Date.now();
|
|
const result = await handler(mockJob as any);
|
|
const endTime = Date.now();
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(endTime - startTime).toBeGreaterThanOrEqual(10);
|
|
});
|
|
|
|
it('should maintain type safety for complex payloads', () => {
|
|
interface ComplexPayload {
|
|
user: {
|
|
id: string;
|
|
name: string;
|
|
roles: string[];
|
|
};
|
|
request: {
|
|
type: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
resource: string;
|
|
data: Record<string, any>;
|
|
};
|
|
metadata: {
|
|
timestamp: Date;
|
|
source: string;
|
|
version: number;
|
|
};
|
|
}
|
|
|
|
interface ComplexResult {
|
|
status: 'success' | 'failure';
|
|
changes: Array<{
|
|
field: string;
|
|
oldValue: any;
|
|
newValue: any;
|
|
}>;
|
|
audit: {
|
|
performedBy: string;
|
|
performedAt: Date;
|
|
duration: number;
|
|
};
|
|
}
|
|
|
|
const handler = createJobHandler<ComplexPayload, ComplexResult>(async job => {
|
|
const startTime = Date.now();
|
|
|
|
// Type-safe access to nested properties
|
|
const userId = job.data.user.id;
|
|
const requestType = job.data.request.type;
|
|
const version = job.data.metadata.version;
|
|
|
|
return {
|
|
status: 'success',
|
|
changes: [
|
|
{
|
|
field: 'resource',
|
|
oldValue: null,
|
|
newValue: job.data.request.resource,
|
|
},
|
|
],
|
|
audit: {
|
|
performedBy: userId,
|
|
performedAt: new Date(),
|
|
duration: Date.now() - startTime,
|
|
},
|
|
};
|
|
});
|
|
|
|
expect(typeof handler).toBe('function');
|
|
});
|
|
|
|
it('should work with job progress reporting', async () => {
|
|
let progressValue = 0;
|
|
|
|
const handler = createJobHandler<TestPayload, TestResult>(async job => {
|
|
// Report progress
|
|
await job.updateProgress(25);
|
|
progressValue = 25;
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
|
|
await job.updateProgress(50);
|
|
progressValue = 50;
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 10));
|
|
|
|
await job.updateProgress(100);
|
|
progressValue = 100;
|
|
|
|
return {
|
|
success: true,
|
|
processedBy: job.data.userId,
|
|
timestamp: new Date(),
|
|
};
|
|
});
|
|
|
|
const mockJob = {
|
|
id: 'job-progress',
|
|
name: 'progress-job',
|
|
data: {
|
|
userId: 'user-progress',
|
|
action: 'long-process',
|
|
},
|
|
opts: {},
|
|
progress: () => progressValue,
|
|
log: () => {},
|
|
updateProgress: async (value: number) => {
|
|
progressValue = value;
|
|
},
|
|
};
|
|
|
|
const result = await handler(mockJob as any);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(progressValue).toBe(100);
|
|
});
|
|
});
|