durabull
Version:
A durable workflow engine built on top of BullMQ and Redis
206 lines (177 loc) • 5.44 kB
text/typescript
import { WorkflowRecord, History, HistoryEvent } from '../../src/runtime/history';
describe('History and Records', () => {
describe('WorkflowRecord', () => {
it('should create valid workflow record', () => {
const record: WorkflowRecord = {
id: 'wf-123',
class: 'TestWorkflow',
status: 'pending',
args: ['arg1', 42],
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(record.id).toBe('wf-123');
expect(record.class).toBe('TestWorkflow');
expect(record.status).toBe('pending');
expect(record.args).toEqual(['arg1', 42]);
});
it('should support all workflow statuses', () => {
const statuses: Array<WorkflowRecord['status']> = [
'created',
'pending',
'running',
'waiting',
'completed',
'failed',
'continued',
];
statuses.forEach((status) => {
const record: WorkflowRecord = {
id: 'wf-test',
class: 'TestWorkflow',
status,
createdAt: Date.now(),
updatedAt: Date.now(),
};
expect(record.status).toBe(status);
});
});
it('should support waiting configuration', () => {
const record: WorkflowRecord = {
id: 'wf-waiting',
class: 'TestWorkflow',
status: 'waiting',
createdAt: Date.now(),
updatedAt: Date.now(),
waiting: {
type: 'await',
resumeAt: Date.now() + 5000,
},
};
expect(record.waiting?.type).toBe('await');
expect(record.waiting?.resumeAt).toBeGreaterThan(Date.now());
});
it('should support continuation tracking', () => {
const record: WorkflowRecord = {
id: 'wf-old',
class: 'TestWorkflow',
status: 'continued',
createdAt: Date.now(),
updatedAt: Date.now(),
continuedTo: 'wf-new',
};
expect(record.continuedTo).toBe('wf-new');
const newRecord: WorkflowRecord = {
id: 'wf-new',
class: 'TestWorkflow',
status: 'pending',
createdAt: Date.now(),
updatedAt: Date.now(),
continuedFrom: 'wf-old',
};
expect(newRecord.continuedFrom).toBe('wf-old');
});
});
describe('History', () => {
it('should create empty history', () => {
const history: History = {
events: [],
cursor: 0,
};
expect(history.events).toHaveLength(0);
expect(history.cursor).toBe(0);
});
it('should track replay cursor', () => {
const history: History = {
events: [
{ type: 'activity', id: 'act-1', ts: Date.now(), result: 'result1' },
{ type: 'activity', id: 'act-2', ts: Date.now(), result: 'result2' },
],
cursor: 1,
};
expect(history.events).toHaveLength(2);
expect(history.cursor).toBe(1);
});
});
describe('HistoryEvent', () => {
it('should create activity event with result', () => {
const event: HistoryEvent = {
type: 'activity',
id: 'act-123',
ts: Date.now(),
result: { data: 'success' },
};
expect(event.type).toBe('activity');
expect(event.id).toBe('act-123');
expect('result' in event).toBe(true);
});
it('should create activity event with error', () => {
const event: HistoryEvent = {
type: 'activity',
id: 'act-failed',
ts: Date.now(),
error: { message: 'Activity failed', stack: 'stack trace' },
};
expect(event.type).toBe('activity');
expect('error' in event).toBe(true);
expect(event.error?.message).toBe('Activity failed');
});
it('should create timer event', () => {
const event: HistoryEvent = {
type: 'timer',
id: 'timer-1',
ts: Date.now(),
delay: 5,
};
expect(event.type).toBe('timer');
expect(event.delay).toBe(5);
});
it('should create signal event', () => {
const event: HistoryEvent = {
type: 'signal',
id: 'sig-1',
ts: Date.now(),
name: 'cancel',
payload: { reason: 'user requested' },
};
expect(event.type).toBe('signal');
expect(event.name).toBe('cancel');
expect(event.payload).toEqual({ reason: 'user requested' });
});
it('should create child workflow event', () => {
const event: HistoryEvent = {
type: 'child',
id: 'child-1',
ts: Date.now(),
childId: 'wf-child-123',
result: 'child result',
};
expect(event.type).toBe('child');
expect(event.childId).toBe('wf-child-123');
expect(event.result).toBe('child result');
});
it('should create side effect event', () => {
const event: HistoryEvent = {
type: 'sideEffect',
id: 'se-1',
ts: Date.now(),
value: { random: Math.random() },
};
expect(event.type).toBe('sideEffect');
expect('value' in event).toBe(true);
});
it('should create exception event', () => {
const event: HistoryEvent = {
type: 'exception',
id: 'ex-1',
ts: Date.now(),
error: {
message: 'Unexpected error',
stack: 'Error: ...',
},
};
expect(event.type).toBe('exception');
expect(event.error.message).toBe('Unexpected error');
});
});
});