@allternit/workflow-engine 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/engine/workflow-engine.d.ts.map +1 -1
- package/dist/engine/workflow-engine.js +32 -6
- package/dist/engine/workflow-engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +4 -16
- package/src/engine/workflow-engine.ts +35 -6
- package/src/index.ts +1 -1
- package/src/types.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/engine/workflow-engine.d.ts.bak +0 -39
- package/dist/engine/workflow-engine.js.bak +0 -532
- package/dist/index.d.ts.bak +0 -38
- package/dist/index.js.bak +0 -41
- package/dist/scheduler/index.d.ts.bak +0 -53
- package/dist/scheduler/index.js.bak +0 -135
- package/dist/types.d.ts.bak +0 -402
- package/dist/types.js.bak +0 -7
- package/dist/visualizer/index.d.ts.bak +0 -81
- package/dist/visualizer/index.js.bak +0 -277
- package/src/__tests__/visualizer.test.ts.bak +0 -110
- package/src/__tests__/workflow-engine.test.ts.bak +0 -239
- package/src/engine/workflow-engine.test.ts.bak +0 -852
- package/src/engine/workflow-engine.ts.bak +0 -651
- package/src/index.ts.bak +0 -89
- package/src/scheduler/index.ts.bak +0 -204
- package/src/scheduler/scheduler.test.ts.bak +0 -521
- package/src/types.ts.bak +0 -449
- package/src/visualizer/index.ts.bak +0 -409
- package/src/visualizer/visualizer.test.ts.bak +0 -844
- package/vitest.config.ts.bak +0 -9
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workflow Scheduler Tests
|
|
3
|
-
*
|
|
4
|
-
* Comprehensive tests for the WorkflowScheduler class covering priority queue,
|
|
5
|
-
* resource limits, cron-based scheduling, task cancellation, and queue statistics.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
9
|
-
import { createScheduler, globalScheduler } from './index';
|
|
10
|
-
import type { Task, WorkflowNode, WorkflowExecution } from '../types';
|
|
11
|
-
|
|
12
|
-
describe('WorkflowScheduler', () => {
|
|
13
|
-
let scheduler: ReturnType<typeof createScheduler>;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
scheduler = createScheduler();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('basic task submission', () => {
|
|
20
|
-
it('should submit a single task', async () => {
|
|
21
|
-
const task: Task = {
|
|
22
|
-
id: 'task-1',
|
|
23
|
-
node: { id: 'node-1', type: 'test', name: 'Test Node' },
|
|
24
|
-
execution: {
|
|
25
|
-
id: 'exec-1',
|
|
26
|
-
workflowId: 'wf-1',
|
|
27
|
-
status: 'running',
|
|
28
|
-
} as WorkflowExecution,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// Submit returns a promise that resolves when task completes
|
|
32
|
-
const resultPromise = scheduler.submit(task);
|
|
33
|
-
expect(resultPromise).toBeInstanceOf(Promise);
|
|
34
|
-
|
|
35
|
-
const result = await resultPromise;
|
|
36
|
-
// Result may be undefined if task completes without returning value
|
|
37
|
-
expect(result !== undefined || result === undefined).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should submit multiple tasks', async () => {
|
|
41
|
-
const tasks: Task[] = [
|
|
42
|
-
{
|
|
43
|
-
id: 'task-1',
|
|
44
|
-
node: { id: 'node-1', type: 'test', name: 'Test Node 1' },
|
|
45
|
-
execution: { id: 'exec-1', workflowId: 'wf-1', status: 'running' } as WorkflowExecution,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: 'task-2',
|
|
49
|
-
node: { id: 'node-2', type: 'test', name: 'Test Node 2' },
|
|
50
|
-
execution: { id: 'exec-2', workflowId: 'wf-1', status: 'running' } as WorkflowExecution,
|
|
51
|
-
},
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
const results = await scheduler.submitAll(tasks);
|
|
55
|
-
expect(results).toHaveLength(2);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('priority queue', () => {
|
|
60
|
-
it('should respect priority when enabled', async () => {
|
|
61
|
-
const priorityScheduler = createScheduler({ enablePriority: true });
|
|
62
|
-
const executionOrder: string[] = [];
|
|
63
|
-
|
|
64
|
-
// Create custom scheduler that tracks execution order
|
|
65
|
-
const tasks: Task[] = [
|
|
66
|
-
{ id: 'low', priority: 1, node: { id: 'n1', type: 'test', name: 'Low' }, execution: { id: 'e1', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
67
|
-
{ id: 'high', priority: 10, node: { id: 'n2', type: 'test', name: 'High' }, execution: { id: 'e2', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
68
|
-
{ id: 'medium', priority: 5, node: { id: 'n3', type: 'test', name: 'Medium' }, execution: { id: 'e3', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
// Submit all tasks
|
|
72
|
-
await Promise.all(tasks.map(t => priorityScheduler.submit(t)));
|
|
73
|
-
|
|
74
|
-
const status = priorityScheduler.getStatus();
|
|
75
|
-
expect(status.queued).toBe(0);
|
|
76
|
-
expect(status.completed).toBeGreaterThanOrEqual(0);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should process high priority tasks before low priority', async () => {
|
|
80
|
-
const priorityScheduler = createScheduler({
|
|
81
|
-
enablePriority: true,
|
|
82
|
-
maxConcurrency: 1, // Single concurrency to ensure ordering
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const taskTimes: Record<string, number> = {};
|
|
86
|
-
let counter = 0;
|
|
87
|
-
|
|
88
|
-
// Submit tasks in reverse priority order
|
|
89
|
-
const tasks: Task[] = [
|
|
90
|
-
{ id: 'low', priority: 1, node: { id: 'n1', type: 'test', name: 'Low' }, execution: { id: 'e1', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
91
|
-
{ id: 'high', priority: 10, node: { id: 'n2', type: 'test', name: 'High' }, execution: { id: 'e2', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
// Submit low first, then high
|
|
95
|
-
await priorityScheduler.submit(tasks[0]);
|
|
96
|
-
await priorityScheduler.submit(tasks[1]);
|
|
97
|
-
|
|
98
|
-
const status = priorityScheduler.getStatus();
|
|
99
|
-
expect(status.completed).toBeGreaterThanOrEqual(0);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should default to no priority when disabled', async () => {
|
|
103
|
-
const noPriorityScheduler = createScheduler({ enablePriority: false });
|
|
104
|
-
|
|
105
|
-
const tasks: Task[] = [
|
|
106
|
-
{ id: 'task-1', priority: 10, node: { id: 'n1', type: 'test', name: 'Node' }, execution: { id: 'e1', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
107
|
-
{ id: 'task-2', priority: 1, node: { id: 'n2', type: 'test', name: 'Node' }, execution: { id: 'e2', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
await noPriorityScheduler.submitAll(tasks);
|
|
111
|
-
|
|
112
|
-
const status = noPriorityScheduler.getStatus();
|
|
113
|
-
expect(status.queued).toBe(0);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('resource limits enforcement', () => {
|
|
118
|
-
it('should enforce max concurrency limit', async () => {
|
|
119
|
-
const limitedScheduler = createScheduler({ maxConcurrency: 2 });
|
|
120
|
-
|
|
121
|
-
// Create many tasks
|
|
122
|
-
const tasks: Task[] = Array.from({ length: 5 }, (_, i) => ({
|
|
123
|
-
id: `task-${i}`,
|
|
124
|
-
node: { id: `node-${i}`, type: 'test', name: `Node ${i}` },
|
|
125
|
-
execution: { id: `exec-${i}`, workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
126
|
-
}));
|
|
127
|
-
|
|
128
|
-
// Submit all tasks
|
|
129
|
-
await limitedScheduler.submitAll(tasks);
|
|
130
|
-
|
|
131
|
-
// After submission, some should be completed
|
|
132
|
-
const status = limitedScheduler.getStatus();
|
|
133
|
-
expect(status.running).toBeLessThanOrEqual(2);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should enforce queue size limit', async () => {
|
|
137
|
-
const limitedScheduler = createScheduler({
|
|
138
|
-
queueLimit: 2,
|
|
139
|
-
maxConcurrency: 1, // Low concurrency to fill queue
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Create more tasks than the limit
|
|
143
|
-
const tasks: Task[] = Array.from({ length: 10 }, (_, i) => ({
|
|
144
|
-
id: `task-${i}`,
|
|
145
|
-
node: { id: `node-${i}`, type: 'test', name: `Node ${i}` },
|
|
146
|
-
execution: { id: `exec-${i}`, workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
147
|
-
}));
|
|
148
|
-
|
|
149
|
-
// Some submissions should fail due to queue limit
|
|
150
|
-
let rejectedCount = 0;
|
|
151
|
-
for (const task of tasks) {
|
|
152
|
-
try {
|
|
153
|
-
await limitedScheduler.submit(task);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
if (error instanceof Error && error.message === 'Queue limit reached') {
|
|
156
|
-
rejectedCount++;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// At least some should have been rejected
|
|
162
|
-
expect(rejectedCount).toBeGreaterThanOrEqual(0);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should enforce default timeout', async () => {
|
|
166
|
-
const timeoutScheduler = createScheduler({
|
|
167
|
-
defaultTimeout: 50, // Very short timeout
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
const task: Task = {
|
|
171
|
-
id: 'timeout-task',
|
|
172
|
-
node: { id: 'node-1', type: 'test', name: 'Test Node' },
|
|
173
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// Task should complete or timeout
|
|
177
|
-
await timeoutScheduler.submit(task);
|
|
178
|
-
|
|
179
|
-
const status = timeoutScheduler.getStatus();
|
|
180
|
-
expect(status.failed + status.completed).toBeGreaterThanOrEqual(0);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should allow task-specific timeout override', async () => {
|
|
184
|
-
const schedulerWithTimeout = createScheduler({
|
|
185
|
-
defaultTimeout: 1000,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const task: Task = {
|
|
189
|
-
id: 'custom-timeout-task',
|
|
190
|
-
node: { id: 'node-1', type: 'test', name: 'Test Node' },
|
|
191
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
192
|
-
timeout: 100, // Override default
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
await schedulerWithTimeout.submit(task);
|
|
196
|
-
|
|
197
|
-
const status = schedulerWithTimeout.getStatus();
|
|
198
|
-
expect(status.queued).toBe(0);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe('task cancellation', () => {
|
|
203
|
-
it('should cancel a queued task', async () => {
|
|
204
|
-
// Create scheduler with low concurrency to keep tasks in queue
|
|
205
|
-
const limitedScheduler = createScheduler({
|
|
206
|
-
maxConcurrency: 1,
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const task: Task = {
|
|
210
|
-
id: 'cancel-task',
|
|
211
|
-
node: { id: 'node-1', type: 'test', name: 'Test Node' },
|
|
212
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// Submit task
|
|
216
|
-
const submitPromise = limitedScheduler.submit(task);
|
|
217
|
-
|
|
218
|
-
// Try to cancel
|
|
219
|
-
const cancelled = limitedScheduler.cancel('cancel-task');
|
|
220
|
-
|
|
221
|
-
// Cancel may or may not succeed depending on timing
|
|
222
|
-
expect(typeof cancelled).toBe('boolean');
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
await submitPromise;
|
|
226
|
-
} catch {
|
|
227
|
-
// Task might be cancelled
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should cancel a running task with AbortController', async () => {
|
|
232
|
-
const scheduler = createScheduler({ maxConcurrency: 1 });
|
|
233
|
-
|
|
234
|
-
const slowTask: Task = {
|
|
235
|
-
id: 'slow-task',
|
|
236
|
-
node: { id: 'node-1', type: 'test', name: 'Slow Node' },
|
|
237
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
238
|
-
timeout: 5000, // Long timeout
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// Submit slow task
|
|
242
|
-
const submitPromise = scheduler.submit(slowTask);
|
|
243
|
-
|
|
244
|
-
// Give it time to start
|
|
245
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
246
|
-
|
|
247
|
-
// Cancel the running task
|
|
248
|
-
const cancelled = scheduler.cancel('slow-task');
|
|
249
|
-
expect(typeof cancelled).toBe('boolean');
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
await submitPromise;
|
|
253
|
-
} catch {
|
|
254
|
-
// Expected - task was cancelled
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('should return false when cancelling non-existent task', () => {
|
|
259
|
-
const cancelled = scheduler.cancel('non-existent-task');
|
|
260
|
-
expect(cancelled).toBe(false);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('should handle concurrent cancellation attempts', async () => {
|
|
264
|
-
const scheduler = createScheduler({ maxConcurrency: 1 });
|
|
265
|
-
|
|
266
|
-
const task: Task = {
|
|
267
|
-
id: 'concurrent-cancel',
|
|
268
|
-
node: { id: 'node-1', type: 'test', name: 'Test Node' },
|
|
269
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const submitPromise = scheduler.submit(task);
|
|
273
|
-
|
|
274
|
-
// Try to cancel multiple times
|
|
275
|
-
const results = [
|
|
276
|
-
scheduler.cancel('concurrent-cancel'),
|
|
277
|
-
scheduler.cancel('concurrent-cancel'),
|
|
278
|
-
scheduler.cancel('concurrent-cancel'),
|
|
279
|
-
];
|
|
280
|
-
|
|
281
|
-
// At least one should succeed or all should return booleans
|
|
282
|
-
results.forEach(result => {
|
|
283
|
-
expect(typeof result).toBe('boolean');
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
await submitPromise;
|
|
288
|
-
} catch {
|
|
289
|
-
// Task might be cancelled
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
describe('queue statistics', () => {
|
|
295
|
-
it('should report initial status', () => {
|
|
296
|
-
const status = scheduler.getStatus();
|
|
297
|
-
|
|
298
|
-
expect(status).toHaveProperty('running');
|
|
299
|
-
expect(status).toHaveProperty('queued');
|
|
300
|
-
expect(status).toHaveProperty('completed');
|
|
301
|
-
expect(status).toHaveProperty('failed');
|
|
302
|
-
expect(status).toHaveProperty('isPaused');
|
|
303
|
-
|
|
304
|
-
expect(status.running).toBe(0);
|
|
305
|
-
expect(status.queued).toBe(0);
|
|
306
|
-
expect(status.completed).toBe(0);
|
|
307
|
-
expect(status.failed).toBe(0);
|
|
308
|
-
expect(status.isPaused).toBe(false);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it('should update status after task completion', async () => {
|
|
312
|
-
const task: Task = {
|
|
313
|
-
id: 'stats-task',
|
|
314
|
-
node: { id: 'node-1', type: 'test', name: 'Test Node' },
|
|
315
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
await scheduler.submit(task);
|
|
319
|
-
|
|
320
|
-
const status = scheduler.getStatus();
|
|
321
|
-
expect(status.completed + status.failed).toBeGreaterThanOrEqual(0);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('should track multiple task statistics', async () => {
|
|
325
|
-
const tasks: Task[] = [
|
|
326
|
-
{ id: 'task-1', node: { id: 'n1', type: 'test', name: 'Node 1' }, execution: { id: 'e1', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
327
|
-
{ id: 'task-2', node: { id: 'n2', type: 'test', name: 'Node 2' }, execution: { id: 'e2', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
328
|
-
{ id: 'task-3', node: { id: 'n3', type: 'test', name: 'Node 3' }, execution: { id: 'e3', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
329
|
-
];
|
|
330
|
-
|
|
331
|
-
await scheduler.submitAll(tasks);
|
|
332
|
-
|
|
333
|
-
const status = scheduler.getStatus();
|
|
334
|
-
expect(status.queued).toBe(0); // All should be processed
|
|
335
|
-
expect(status.completed + status.failed).toBeGreaterThanOrEqual(0);
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
describe('pause and resume', () => {
|
|
340
|
-
it('should pause the scheduler', () => {
|
|
341
|
-
scheduler.pause();
|
|
342
|
-
const status = scheduler.getStatus();
|
|
343
|
-
expect(status.isPaused).toBe(true);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it('should resume the scheduler', async () => {
|
|
347
|
-
scheduler.pause();
|
|
348
|
-
expect(scheduler.getStatus().isPaused).toBe(true);
|
|
349
|
-
|
|
350
|
-
scheduler.resume();
|
|
351
|
-
expect(scheduler.getStatus().isPaused).toBe(false);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('should not process new tasks when paused', async () => {
|
|
355
|
-
const pausedScheduler = createScheduler();
|
|
356
|
-
pausedScheduler.pause();
|
|
357
|
-
|
|
358
|
-
const task: Task = {
|
|
359
|
-
id: 'paused-task',
|
|
360
|
-
node: { id: 'node-1', type: 'test', name: 'Test Node' },
|
|
361
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
// Submit task while paused
|
|
365
|
-
await pausedScheduler.submit(task);
|
|
366
|
-
|
|
367
|
-
// Task might be queued but not processed
|
|
368
|
-
const status = pausedScheduler.getStatus();
|
|
369
|
-
expect(status.isPaused).toBe(true);
|
|
370
|
-
|
|
371
|
-
// Resume and let it process
|
|
372
|
-
pausedScheduler.resume();
|
|
373
|
-
|
|
374
|
-
// Give it time to process
|
|
375
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('should process queued tasks after resume', async () => {
|
|
379
|
-
const scheduler = createScheduler({ maxConcurrency: 1 });
|
|
380
|
-
scheduler.pause();
|
|
381
|
-
|
|
382
|
-
const tasks: Task[] = [
|
|
383
|
-
{ id: 'queued-1', node: { id: 'n1', type: 'test', name: 'Node 1' }, execution: { id: 'e1', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
384
|
-
{ id: 'queued-2', node: { id: 'n2', type: 'test', name: 'Node 2' }, execution: { id: 'e2', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
385
|
-
];
|
|
386
|
-
|
|
387
|
-
// Submit while paused
|
|
388
|
-
for (const task of tasks) {
|
|
389
|
-
await scheduler.submit(task);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Resume and process
|
|
393
|
-
scheduler.resume();
|
|
394
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
395
|
-
|
|
396
|
-
const status = scheduler.getStatus();
|
|
397
|
-
expect(status.isPaused).toBe(false);
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
describe('cron-based scheduling', () => {
|
|
402
|
-
it('should support cron-like scheduling configuration', () => {
|
|
403
|
-
// The scheduler config supports scheduling parameters
|
|
404
|
-
const cronScheduler = createScheduler({
|
|
405
|
-
maxConcurrency: 5,
|
|
406
|
-
defaultTimeout: 30000,
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
expect(cronScheduler).toBeDefined();
|
|
410
|
-
expect(typeof cronScheduler.submit).toBe('function');
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it('should handle scheduled task execution', async () => {
|
|
414
|
-
const scheduledScheduler = createScheduler();
|
|
415
|
-
|
|
416
|
-
const task: Task = {
|
|
417
|
-
id: 'scheduled-task',
|
|
418
|
-
node: { id: 'node-1', type: 'trigger:schedule', name: 'Scheduled Trigger' },
|
|
419
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'pending' } as WorkflowExecution,
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
await scheduledScheduler.submit(task);
|
|
423
|
-
|
|
424
|
-
const status = scheduledScheduler.getStatus();
|
|
425
|
-
expect(status.queued).toBe(0);
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
describe('error handling', () => {
|
|
430
|
-
it('should handle task execution errors', async () => {
|
|
431
|
-
const errorScheduler = createScheduler();
|
|
432
|
-
|
|
433
|
-
// Submit a task - errors are tracked in status
|
|
434
|
-
const task: Task = {
|
|
435
|
-
id: 'error-task',
|
|
436
|
-
node: { id: 'node-1', type: 'test', name: 'Error Node' },
|
|
437
|
-
execution: { id: 'exec-1', workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
await errorScheduler.submit(task);
|
|
441
|
-
|
|
442
|
-
const status = errorScheduler.getStatus();
|
|
443
|
-
expect(status.failed + status.completed).toBeGreaterThanOrEqual(0);
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it('should continue processing after task failure', async () => {
|
|
447
|
-
const resilientScheduler = createScheduler({ maxConcurrency: 1 });
|
|
448
|
-
|
|
449
|
-
const tasks: Task[] = [
|
|
450
|
-
{ id: 'task-1', node: { id: 'n1', type: 'test', name: 'Node 1' }, execution: { id: 'e1', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
451
|
-
{ id: 'task-2', node: { id: 'n2', type: 'test', name: 'Node 2' }, execution: { id: 'e2', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
452
|
-
];
|
|
453
|
-
|
|
454
|
-
await resilientScheduler.submitAll(tasks);
|
|
455
|
-
|
|
456
|
-
const status = resilientScheduler.getStatus();
|
|
457
|
-
expect(status.queued).toBe(0);
|
|
458
|
-
});
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
describe('global scheduler instance', () => {
|
|
462
|
-
it('should have a global scheduler instance', () => {
|
|
463
|
-
expect(globalScheduler).toBeDefined();
|
|
464
|
-
expect(typeof globalScheduler.submit).toBe('function');
|
|
465
|
-
expect(typeof globalScheduler.submitAll).toBe('function');
|
|
466
|
-
expect(typeof globalScheduler.cancel).toBe('function');
|
|
467
|
-
expect(typeof globalScheduler.getStatus).toBe('function');
|
|
468
|
-
expect(typeof globalScheduler.pause).toBe('function');
|
|
469
|
-
expect(typeof globalScheduler.resume).toBe('function');
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
describe('complex scenarios', () => {
|
|
474
|
-
it('should handle mixed priority and concurrency', async () => {
|
|
475
|
-
const complexScheduler = createScheduler({
|
|
476
|
-
enablePriority: true,
|
|
477
|
-
maxConcurrency: 2,
|
|
478
|
-
queueLimit: 10,
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
const tasks: Task[] = [
|
|
482
|
-
{ id: 'high-1', priority: 10, node: { id: 'h1', type: 'test', name: 'High 1' }, execution: { id: 'e1', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
483
|
-
{ id: 'low-1', priority: 1, node: { id: 'l1', type: 'test', name: 'Low 1' }, execution: { id: 'e2', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
484
|
-
{ id: 'high-2', priority: 10, node: { id: 'h2', type: 'test', name: 'High 2' }, execution: { id: 'e3', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
485
|
-
{ id: 'medium-1', priority: 5, node: { id: 'm1', type: 'test', name: 'Medium 1' }, execution: { id: 'e4', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
486
|
-
{ id: 'low-2', priority: 1, node: { id: 'l2', type: 'test', name: 'Low 2' }, execution: { id: 'e5', workflowId: 'wf', status: 'running' } as WorkflowExecution },
|
|
487
|
-
];
|
|
488
|
-
|
|
489
|
-
await complexScheduler.submitAll(tasks);
|
|
490
|
-
|
|
491
|
-
const status = complexScheduler.getStatus();
|
|
492
|
-
expect(status.queued).toBe(0);
|
|
493
|
-
expect(status.completed + status.failed).toBeGreaterThanOrEqual(0);
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
it('should handle rapid submit/cancel cycles', async () => {
|
|
497
|
-
const rapidScheduler = createScheduler({ maxConcurrency: 3 });
|
|
498
|
-
|
|
499
|
-
for (let i = 0; i < 10; i++) {
|
|
500
|
-
const task: Task = {
|
|
501
|
-
id: `rapid-${i}`,
|
|
502
|
-
node: { id: `node-${i}`, type: 'test', name: `Node ${i}` },
|
|
503
|
-
execution: { id: `exec-${i}`, workflowId: 'wf', status: 'running' } as WorkflowExecution,
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
rapidScheduler.submit(task);
|
|
507
|
-
|
|
508
|
-
// Randomly cancel some tasks
|
|
509
|
-
if (i % 3 === 0) {
|
|
510
|
-
rapidScheduler.cancel(`rapid-${i}`);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Give time for processing
|
|
515
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
516
|
-
|
|
517
|
-
const status = rapidScheduler.getStatus();
|
|
518
|
-
expect(status.queued).toBe(0);
|
|
519
|
-
});
|
|
520
|
-
});
|
|
521
|
-
});
|