@auto-engineer/pipeline 0.14.0 → 0.15.0
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/.turbo/turbo-build.log +5 -6
- package/CHANGELOG.md +12 -0
- package/README.md +279 -0
- package/dist/src/builder/define.d.ts +6 -2
- package/dist/src/builder/define.d.ts.map +1 -1
- package/dist/src/builder/define.js +17 -7
- package/dist/src/builder/define.js.map +1 -1
- package/dist/src/builder/define.specs.js +3 -3
- package/dist/src/builder/define.specs.js.map +1 -1
- package/dist/src/core/descriptors.d.ts +6 -2
- package/dist/src/core/descriptors.d.ts.map +1 -1
- package/dist/src/graph/filter-graph.d.ts +3 -0
- package/dist/src/graph/filter-graph.d.ts.map +1 -0
- package/dist/src/graph/filter-graph.js +80 -0
- package/dist/src/graph/filter-graph.js.map +1 -0
- package/dist/src/graph/filter-graph.specs.d.ts +2 -0
- package/dist/src/graph/filter-graph.specs.d.ts.map +1 -0
- package/dist/src/graph/filter-graph.specs.js +204 -0
- package/dist/src/graph/filter-graph.specs.js.map +1 -0
- package/dist/src/graph/types.d.ts +8 -0
- package/dist/src/graph/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/projections/await-tracker-projection.d.ts +31 -0
- package/dist/src/projections/await-tracker-projection.d.ts.map +1 -0
- package/dist/src/projections/await-tracker-projection.js +35 -0
- package/dist/src/projections/await-tracker-projection.js.map +1 -0
- package/dist/src/projections/index.d.ts +4 -0
- package/dist/src/projections/index.d.ts.map +1 -0
- package/dist/src/projections/index.js +4 -0
- package/dist/src/projections/index.js.map +1 -0
- package/dist/src/projections/item-status-projection.d.ts +22 -0
- package/dist/src/projections/item-status-projection.d.ts.map +1 -0
- package/dist/src/projections/item-status-projection.js +11 -0
- package/dist/src/projections/item-status-projection.js.map +1 -0
- package/dist/src/projections/item-status-projection.specs.d.ts +2 -0
- package/dist/src/projections/item-status-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/item-status-projection.specs.js +119 -0
- package/dist/src/projections/item-status-projection.specs.js.map +1 -0
- package/dist/src/projections/latest-run-projection.d.ts +15 -0
- package/dist/src/projections/latest-run-projection.d.ts.map +1 -0
- package/dist/src/projections/latest-run-projection.js +7 -0
- package/dist/src/projections/latest-run-projection.js.map +1 -0
- package/dist/src/projections/latest-run-projection.specs.d.ts +2 -0
- package/dist/src/projections/latest-run-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/latest-run-projection.specs.js +33 -0
- package/dist/src/projections/latest-run-projection.specs.js.map +1 -0
- package/dist/src/projections/message-log-projection.d.ts +51 -0
- package/dist/src/projections/message-log-projection.d.ts.map +1 -0
- package/dist/src/projections/message-log-projection.js +51 -0
- package/dist/src/projections/message-log-projection.js.map +1 -0
- package/dist/src/projections/message-log-projection.specs.d.ts +2 -0
- package/dist/src/projections/message-log-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/message-log-projection.specs.js +101 -0
- package/dist/src/projections/message-log-projection.specs.js.map +1 -0
- package/dist/src/projections/node-status-projection.d.ts +23 -0
- package/dist/src/projections/node-status-projection.d.ts.map +1 -0
- package/dist/src/projections/node-status-projection.js +10 -0
- package/dist/src/projections/node-status-projection.js.map +1 -0
- package/dist/src/projections/node-status-projection.specs.d.ts +2 -0
- package/dist/src/projections/node-status-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/node-status-projection.specs.js +116 -0
- package/dist/src/projections/node-status-projection.specs.js.map +1 -0
- package/dist/src/projections/phased-execution-projection.d.ts +77 -0
- package/dist/src/projections/phased-execution-projection.d.ts.map +1 -0
- package/dist/src/projections/phased-execution-projection.js +54 -0
- package/dist/src/projections/phased-execution-projection.js.map +1 -0
- package/dist/src/projections/phased-execution-projection.specs.d.ts +2 -0
- package/dist/src/projections/phased-execution-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/phased-execution-projection.specs.js +171 -0
- package/dist/src/projections/phased-execution-projection.specs.js.map +1 -0
- package/dist/src/projections/settled-instance-projection.d.ts +67 -0
- package/dist/src/projections/settled-instance-projection.d.ts.map +1 -0
- package/dist/src/projections/settled-instance-projection.js +66 -0
- package/dist/src/projections/settled-instance-projection.js.map +1 -0
- package/dist/src/projections/settled-instance-projection.specs.d.ts +2 -0
- package/dist/src/projections/settled-instance-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/settled-instance-projection.specs.js +217 -0
- package/dist/src/projections/settled-instance-projection.specs.js.map +1 -0
- package/dist/src/projections/stats-projection.d.ts +9 -0
- package/dist/src/projections/stats-projection.d.ts.map +1 -0
- package/dist/src/projections/stats-projection.js +16 -0
- package/dist/src/projections/stats-projection.js.map +1 -0
- package/dist/src/projections/stats-projection.specs.d.ts +2 -0
- package/dist/src/projections/stats-projection.specs.d.ts.map +1 -0
- package/dist/src/projections/stats-projection.specs.js +91 -0
- package/dist/src/projections/stats-projection.specs.js.map +1 -0
- package/dist/src/runtime/await-tracker.d.ts +17 -7
- package/dist/src/runtime/await-tracker.d.ts.map +1 -1
- package/dist/src/runtime/await-tracker.js +32 -29
- package/dist/src/runtime/await-tracker.js.map +1 -1
- package/dist/src/runtime/await-tracker.specs.js +56 -38
- package/dist/src/runtime/await-tracker.specs.js.map +1 -1
- package/dist/src/runtime/context.d.ts +1 -1
- package/dist/src/runtime/context.d.ts.map +1 -1
- package/dist/src/runtime/event-command-map.d.ts +3 -3
- package/dist/src/runtime/event-command-map.d.ts.map +1 -1
- package/dist/src/runtime/event-command-map.js +6 -2
- package/dist/src/runtime/event-command-map.js.map +1 -1
- package/dist/src/runtime/phased-executor.d.ts +15 -9
- package/dist/src/runtime/phased-executor.d.ts.map +1 -1
- package/dist/src/runtime/phased-executor.js +126 -104
- package/dist/src/runtime/phased-executor.js.map +1 -1
- package/dist/src/runtime/phased-executor.specs.js +243 -81
- package/dist/src/runtime/phased-executor.specs.js.map +1 -1
- package/dist/src/runtime/pipeline-runtime.d.ts.map +1 -1
- package/dist/src/runtime/pipeline-runtime.js +2 -2
- package/dist/src/runtime/pipeline-runtime.js.map +1 -1
- package/dist/src/runtime/pipeline-runtime.specs.js +35 -0
- package/dist/src/runtime/pipeline-runtime.specs.js.map +1 -1
- package/dist/src/runtime/settled-tracker.d.ts +12 -9
- package/dist/src/runtime/settled-tracker.d.ts.map +1 -1
- package/dist/src/runtime/settled-tracker.js +92 -77
- package/dist/src/runtime/settled-tracker.js.map +1 -1
- package/dist/src/runtime/settled-tracker.specs.js +568 -118
- package/dist/src/runtime/settled-tracker.specs.js.map +1 -1
- package/dist/src/server/pipeline-server.d.ts +31 -9
- package/dist/src/server/pipeline-server.d.ts.map +1 -1
- package/dist/src/server/pipeline-server.e2e.specs.js +2 -10
- package/dist/src/server/pipeline-server.e2e.specs.js.map +1 -1
- package/dist/src/server/pipeline-server.js +408 -123
- package/dist/src/server/pipeline-server.js.map +1 -1
- package/dist/src/server/pipeline-server.specs.js +777 -32
- package/dist/src/server/pipeline-server.specs.js.map +1 -1
- package/dist/src/server/sse-manager.specs.js +55 -35
- package/dist/src/server/sse-manager.specs.js.map +1 -1
- package/dist/src/store/index.d.ts +3 -0
- package/dist/src/store/index.d.ts.map +1 -0
- package/dist/src/store/index.js +3 -0
- package/dist/src/store/index.js.map +1 -0
- package/dist/src/store/pipeline-event-store.d.ts +10 -0
- package/dist/src/store/pipeline-event-store.d.ts.map +1 -0
- package/dist/src/store/pipeline-event-store.js +112 -0
- package/dist/src/store/pipeline-event-store.js.map +1 -0
- package/dist/src/store/pipeline-event-store.specs.d.ts +2 -0
- package/dist/src/store/pipeline-event-store.specs.d.ts.map +1 -0
- package/dist/src/store/pipeline-event-store.specs.js +287 -0
- package/dist/src/store/pipeline-event-store.specs.js.map +1 -0
- package/dist/src/store/pipeline-read-model.d.ts +49 -0
- package/dist/src/store/pipeline-read-model.d.ts.map +1 -0
- package/dist/src/store/pipeline-read-model.js +157 -0
- package/dist/src/store/pipeline-read-model.js.map +1 -0
- package/dist/src/store/pipeline-read-model.specs.d.ts +2 -0
- package/dist/src/store/pipeline-read-model.specs.d.ts.map +1 -0
- package/dist/src/store/pipeline-read-model.specs.js +830 -0
- package/dist/src/store/pipeline-read-model.specs.js.map +1 -0
- package/dist/src/testing/fixtures/kanban-full.pipeline.js +2 -2
- package/dist/src/testing/fixtures/kanban-full.pipeline.js.map +1 -1
- package/dist/src/testing/fixtures/kanban.pipeline.js +2 -2
- package/dist/src/testing/fixtures/kanban.pipeline.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +960 -0
- package/package.json +5 -4
- package/src/builder/define.specs.ts +3 -3
- package/src/builder/define.ts +24 -11
- package/src/core/descriptors.ts +7 -2
- package/src/graph/filter-graph.specs.ts +241 -0
- package/src/graph/filter-graph.ts +111 -0
- package/src/graph/types.ts +10 -0
- package/src/index.ts +1 -0
- package/src/projections/await-tracker-projection.ts +68 -0
- package/src/projections/index.ts +11 -0
- package/src/projections/item-status-projection.specs.ts +130 -0
- package/src/projections/item-status-projection.ts +32 -0
- package/src/projections/latest-run-projection.specs.ts +38 -0
- package/src/projections/latest-run-projection.ts +20 -0
- package/src/projections/message-log-projection.specs.ts +118 -0
- package/src/projections/message-log-projection.ts +113 -0
- package/src/projections/node-status-projection.specs.ts +127 -0
- package/src/projections/node-status-projection.ts +33 -0
- package/src/projections/phased-execution-projection.specs.ts +202 -0
- package/src/projections/phased-execution-projection.ts +146 -0
- package/src/projections/settled-instance-projection.specs.ts +249 -0
- package/src/projections/settled-instance-projection.ts +160 -0
- package/src/projections/stats-projection.specs.ts +105 -0
- package/src/projections/stats-projection.ts +26 -0
- package/src/runtime/await-tracker.specs.ts +57 -34
- package/src/runtime/await-tracker.ts +43 -31
- package/src/runtime/context.ts +1 -1
- package/src/runtime/event-command-map.ts +11 -4
- package/src/runtime/phased-executor.specs.ts +357 -81
- package/src/runtime/phased-executor.ts +142 -126
- package/src/runtime/pipeline-runtime.specs.ts +42 -0
- package/src/runtime/pipeline-runtime.ts +6 -4
- package/src/runtime/settled-tracker.specs.ts +716 -120
- package/src/runtime/settled-tracker.ts +104 -98
- package/src/server/pipeline-server.e2e.specs.ts +10 -16
- package/src/server/pipeline-server.specs.ts +964 -49
- package/src/server/pipeline-server.ts +512 -144
- package/src/server/sse-manager.specs.ts +67 -36
- package/src/store/index.ts +2 -0
- package/src/store/pipeline-event-store.specs.ts +309 -0
- package/src/store/pipeline-event-store.ts +156 -0
- package/src/store/pipeline-read-model.specs.ts +967 -0
- package/src/store/pipeline-read-model.ts +223 -0
- package/src/testing/fixtures/kanban-full.pipeline.ts +2 -2
- package/src/testing/fixtures/kanban.pipeline.ts +2 -2
- package/claude.md +0 -160
- package/dist/src/__tests__/e2e/helpers.d.ts +0 -48
- package/dist/src/__tests__/e2e/helpers.d.ts.map +0 -1
- package/dist/src/__tests__/e2e/helpers.js +0 -253
- package/dist/src/__tests__/e2e/helpers.js.map +0 -1
- package/dist/src/__tests__/e2e/kanban-migration.e2e.specs.d.ts +0 -2
- package/dist/src/__tests__/e2e/kanban-migration.e2e.specs.d.ts.map +0 -1
- package/dist/src/__tests__/e2e/kanban-migration.e2e.specs.js +0 -195
- package/dist/src/__tests__/e2e/kanban-migration.e2e.specs.js.map +0 -1
- package/dist/src/__tests__/e2e/types.d.ts +0 -107
- package/dist/src/__tests__/e2e/types.d.ts.map +0 -1
- package/dist/src/__tests__/e2e/types.js +0 -2
- package/dist/src/__tests__/e2e/types.js.map +0 -1
- package/dist/src/file-syncer/crypto/jwe-encryptor.d.ts +0 -15
- package/dist/src/file-syncer/crypto/jwe-encryptor.d.ts.map +0 -1
- package/dist/src/file-syncer/crypto/jwe-encryptor.js +0 -64
- package/dist/src/file-syncer/crypto/jwe-encryptor.js.map +0 -1
- package/dist/src/file-syncer/crypto/provider-resolver.d.ts +0 -24
- package/dist/src/file-syncer/crypto/provider-resolver.d.ts.map +0 -1
- package/dist/src/file-syncer/crypto/provider-resolver.js +0 -71
- package/dist/src/file-syncer/crypto/provider-resolver.js.map +0 -1
- package/dist/src/file-syncer/discovery/bareImports.d.ts +0 -3
- package/dist/src/file-syncer/discovery/bareImports.d.ts.map +0 -1
- package/dist/src/file-syncer/discovery/bareImports.js +0 -36
- package/dist/src/file-syncer/discovery/bareImports.js.map +0 -1
- package/dist/src/file-syncer/discovery/dts.d.ts +0 -8
- package/dist/src/file-syncer/discovery/dts.d.ts.map +0 -1
- package/dist/src/file-syncer/discovery/dts.js +0 -99
- package/dist/src/file-syncer/discovery/dts.js.map +0 -1
- package/dist/src/file-syncer/index.d.ts +0 -46
- package/dist/src/file-syncer/index.d.ts.map +0 -1
- package/dist/src/file-syncer/index.js +0 -392
- package/dist/src/file-syncer/index.js.map +0 -1
- package/dist/src/file-syncer/sync/resolveSyncFileSet.d.ts +0 -7
- package/dist/src/file-syncer/sync/resolveSyncFileSet.d.ts.map +0 -1
- package/dist/src/file-syncer/sync/resolveSyncFileSet.js +0 -86
- package/dist/src/file-syncer/sync/resolveSyncFileSet.js.map +0 -1
- package/dist/src/file-syncer/types/wire.d.ts +0 -14
- package/dist/src/file-syncer/types/wire.d.ts.map +0 -1
- package/dist/src/file-syncer/types/wire.js +0 -2
- package/dist/src/file-syncer/types/wire.js.map +0 -1
- package/dist/src/file-syncer/utils/hash.d.ts +0 -5
- package/dist/src/file-syncer/utils/hash.d.ts.map +0 -1
- package/dist/src/file-syncer/utils/hash.js +0 -19
- package/dist/src/file-syncer/utils/hash.js.map +0 -1
- package/dist/src/file-syncer/utils/path.d.ts +0 -13
- package/dist/src/file-syncer/utils/path.d.ts.map +0 -1
- package/dist/src/file-syncer/utils/path.js +0 -74
- package/dist/src/file-syncer/utils/path.js.map +0 -1
- package/docs/testing-analysis.md +0 -395
- package/pomodoro-plan.md +0 -651
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Event } from '@auto-engineer/message-bus';
|
|
2
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
3
3
|
import type { ForEachPhasedDescriptor } from '../core/descriptors';
|
|
4
|
+
import type { PhasedExecutionEvent } from '../projections/phased-execution-projection';
|
|
5
|
+
import type { PipelineEventStoreContext } from '../store/pipeline-event-store';
|
|
6
|
+
import { createPipelineEventStore } from '../store/pipeline-event-store';
|
|
4
7
|
import { PhasedExecutor } from './phased-executor';
|
|
5
8
|
|
|
6
9
|
interface TestItem {
|
|
@@ -21,33 +24,59 @@ function createHandler(_items: TestItem[]): ForEachPhasedDescriptor {
|
|
|
21
24
|
data: { filePath: (item as TestItem).id },
|
|
22
25
|
}),
|
|
23
26
|
completion: {
|
|
24
|
-
successEvent: 'AllComponentsImplemented',
|
|
25
|
-
failureEvent: 'ComponentsFailed',
|
|
27
|
+
successEvent: { name: 'AllComponentsImplemented' },
|
|
28
|
+
failureEvent: { name: 'ComponentsFailed' },
|
|
26
29
|
itemKey: (e: Event) => (e.data as { filePath?: string; id?: string }).filePath ?? (e.data as TestItem).id,
|
|
27
30
|
},
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
interface ESExecutorOptions {
|
|
35
|
+
onEventEmit?: (event: PhasedExecutionEvent) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createESExecutor(
|
|
39
|
+
ctx: PipelineEventStoreContext,
|
|
40
|
+
dispatched: Array<{ commandType: string; data: unknown; correlationId: string }>,
|
|
41
|
+
completed: Event[],
|
|
42
|
+
options: ESExecutorOptions = {},
|
|
43
|
+
): PhasedExecutor {
|
|
44
|
+
return new PhasedExecutor({
|
|
45
|
+
readModel: ctx.readModel,
|
|
46
|
+
onDispatch: (commandType, data, correlationId) => {
|
|
47
|
+
dispatched.push({ commandType, data, correlationId });
|
|
48
|
+
},
|
|
49
|
+
onComplete: (event) => {
|
|
50
|
+
completed.push(event);
|
|
51
|
+
},
|
|
52
|
+
onEventEmit: async (event) => {
|
|
53
|
+
const data = event.data as Record<string, unknown>;
|
|
54
|
+
const correlationId = (data.correlationId as string) ?? (data.executionId as string)?.split('-')[1] ?? 'default';
|
|
55
|
+
await ctx.eventStore.appendToStream(`phased-${correlationId}`, [{ type: event.type, data: event.data }]);
|
|
56
|
+
options.onEventEmit?.(event);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
31
61
|
describe('PhasedExecutor', () => {
|
|
32
62
|
let executor: PhasedExecutor;
|
|
33
63
|
let dispatched: Array<{ commandType: string; data: unknown; correlationId: string }>;
|
|
34
64
|
let completed: Event[];
|
|
65
|
+
let ctx: PipelineEventStoreContext;
|
|
35
66
|
|
|
36
67
|
beforeEach(() => {
|
|
37
68
|
dispatched = [];
|
|
38
69
|
completed = [];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
46
|
-
});
|
|
70
|
+
ctx = createPipelineEventStore();
|
|
71
|
+
executor = createESExecutor(ctx, dispatched, completed);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(async () => {
|
|
75
|
+
await ctx.close();
|
|
47
76
|
});
|
|
48
77
|
|
|
49
78
|
describe('phase gating', () => {
|
|
50
|
-
it('should dispatch only first phase items initially', () => {
|
|
79
|
+
it('should dispatch only first phase items initially', async () => {
|
|
51
80
|
const items: TestItem[] = [
|
|
52
81
|
{ id: 'm1', type: 'molecule' },
|
|
53
82
|
{ id: 'm2', type: 'molecule' },
|
|
@@ -57,49 +86,60 @@ describe('PhasedExecutor', () => {
|
|
|
57
86
|
const handler = createHandler(items);
|
|
58
87
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
59
88
|
|
|
60
|
-
executor.startPhased(handler, event, 'c1');
|
|
89
|
+
await executor.startPhased(handler, event, 'c1');
|
|
61
90
|
|
|
62
91
|
expect(dispatched).toHaveLength(2);
|
|
63
92
|
expect(dispatched.map((d) => (d.data as { filePath: string }).filePath)).toEqual(['m1', 'm2']);
|
|
64
93
|
});
|
|
65
94
|
|
|
66
|
-
it('should wait for all items in phase to complete before next phase', () => {
|
|
95
|
+
it('should wait for all items in phase to complete before next phase', async () => {
|
|
67
96
|
const items: TestItem[] = [
|
|
68
97
|
{ id: 'm1', type: 'molecule' },
|
|
69
98
|
{ id: 'm2', type: 'molecule' },
|
|
70
99
|
{ id: 'o1', type: 'organism' },
|
|
71
100
|
];
|
|
72
101
|
const handler = createHandler(items);
|
|
102
|
+
executor.registerHandler(handler);
|
|
73
103
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
74
104
|
|
|
75
|
-
executor.startPhased(handler, event, 'c1');
|
|
105
|
+
await executor.startPhased(handler, event, 'c1');
|
|
76
106
|
|
|
77
107
|
expect(dispatched).toHaveLength(2);
|
|
78
108
|
|
|
79
|
-
executor.onEventReceived(
|
|
109
|
+
await executor.onEventReceived(
|
|
110
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
111
|
+
'm1',
|
|
112
|
+
);
|
|
80
113
|
|
|
81
114
|
expect(dispatched).toHaveLength(2);
|
|
82
115
|
|
|
83
|
-
executor.onEventReceived(
|
|
116
|
+
await executor.onEventReceived(
|
|
117
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm2' } },
|
|
118
|
+
'm2',
|
|
119
|
+
);
|
|
84
120
|
|
|
85
121
|
expect(dispatched).toHaveLength(3);
|
|
86
122
|
expect((dispatched[2].data as { filePath: string }).filePath).toBe('o1');
|
|
87
123
|
});
|
|
88
124
|
|
|
89
|
-
it('should skip empty phases', () => {
|
|
125
|
+
it('should skip empty phases', async () => {
|
|
90
126
|
const items: TestItem[] = [
|
|
91
127
|
{ id: 'm1', type: 'molecule' },
|
|
92
128
|
{ id: 'p1', type: 'page' },
|
|
93
129
|
];
|
|
94
130
|
const handler = createHandler(items);
|
|
131
|
+
executor.registerHandler(handler);
|
|
95
132
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
96
133
|
|
|
97
|
-
executor.startPhased(handler, event, 'c1');
|
|
134
|
+
await executor.startPhased(handler, event, 'c1');
|
|
98
135
|
|
|
99
136
|
expect(dispatched).toHaveLength(1);
|
|
100
137
|
expect((dispatched[0].data as { filePath: string }).filePath).toBe('m1');
|
|
101
138
|
|
|
102
|
-
executor.onEventReceived(
|
|
139
|
+
await executor.onEventReceived(
|
|
140
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
141
|
+
'm1',
|
|
142
|
+
);
|
|
103
143
|
|
|
104
144
|
expect(dispatched).toHaveLength(2);
|
|
105
145
|
expect((dispatched[1].data as { filePath: string }).filePath).toBe('p1');
|
|
@@ -107,73 +147,93 @@ describe('PhasedExecutor', () => {
|
|
|
107
147
|
});
|
|
108
148
|
|
|
109
149
|
describe('completion tracking', () => {
|
|
110
|
-
it('should emit success event when all phases complete', () => {
|
|
150
|
+
it('should emit success event when all phases complete', async () => {
|
|
111
151
|
const items: TestItem[] = [
|
|
112
152
|
{ id: 'm1', type: 'molecule' },
|
|
113
153
|
{ id: 'o1', type: 'organism' },
|
|
114
154
|
];
|
|
115
155
|
const handler = createHandler(items);
|
|
156
|
+
executor.registerHandler(handler);
|
|
116
157
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
117
158
|
|
|
118
|
-
executor.startPhased(handler, event, 'c1');
|
|
159
|
+
await executor.startPhased(handler, event, 'c1');
|
|
119
160
|
|
|
120
|
-
executor.onEventReceived(
|
|
121
|
-
|
|
161
|
+
await executor.onEventReceived(
|
|
162
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
163
|
+
'm1',
|
|
164
|
+
);
|
|
165
|
+
await executor.onEventReceived(
|
|
166
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'o1' } },
|
|
167
|
+
'o1',
|
|
168
|
+
);
|
|
122
169
|
|
|
123
170
|
expect(completed).toHaveLength(1);
|
|
124
171
|
expect(completed[0].type).toBe('AllComponentsImplemented');
|
|
125
172
|
expect(completed[0].correlationId).toBe('c1');
|
|
126
173
|
});
|
|
127
174
|
|
|
128
|
-
it('should cleanup session after completion', () => {
|
|
175
|
+
it('should cleanup session after completion allowing new session with same correlationId', async () => {
|
|
129
176
|
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
130
177
|
const handler = createHandler(items);
|
|
178
|
+
executor.registerHandler(handler);
|
|
131
179
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
132
180
|
|
|
133
|
-
executor.startPhased(handler, event, 'c1');
|
|
134
|
-
|
|
181
|
+
await executor.startPhased(handler, event, 'c1');
|
|
182
|
+
await executor.onEventReceived(
|
|
183
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
184
|
+
'm1',
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(completed).toHaveLength(1);
|
|
135
188
|
|
|
136
|
-
|
|
189
|
+
dispatched.length = 0;
|
|
190
|
+
completed.length = 0;
|
|
137
191
|
|
|
138
|
-
|
|
192
|
+
await executor.startPhased(handler, event, 'c1');
|
|
193
|
+
expect(dispatched).toHaveLength(1);
|
|
194
|
+
expect((dispatched[0].data as { filePath: string }).filePath).toBe('m1');
|
|
139
195
|
});
|
|
140
196
|
});
|
|
141
197
|
|
|
142
198
|
describe('state queries', () => {
|
|
143
|
-
it('should report phase completion status', () => {
|
|
199
|
+
it('should report phase completion status', async () => {
|
|
144
200
|
const items: TestItem[] = [
|
|
145
201
|
{ id: 'm1', type: 'molecule' },
|
|
146
202
|
{ id: 'o1', type: 'organism' },
|
|
147
203
|
];
|
|
148
204
|
const handler = createHandler(items);
|
|
205
|
+
executor.registerHandler(handler);
|
|
149
206
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
150
207
|
|
|
151
|
-
executor.startPhased(handler, event, 'c1');
|
|
208
|
+
await executor.startPhased(handler, event, 'c1');
|
|
152
209
|
|
|
153
|
-
expect(executor.isPhaseComplete('c1', 'molecule')).toBe(false);
|
|
154
|
-
expect(executor.isPhaseComplete('c1', 'organism')).toBe(false);
|
|
210
|
+
expect(await executor.isPhaseComplete('c1', 'molecule')).toBe(false);
|
|
211
|
+
expect(await executor.isPhaseComplete('c1', 'organism')).toBe(false);
|
|
155
212
|
|
|
156
|
-
executor.onEventReceived(
|
|
213
|
+
await executor.onEventReceived(
|
|
214
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
215
|
+
'm1',
|
|
216
|
+
);
|
|
157
217
|
|
|
158
|
-
expect(executor.isPhaseComplete('c1', 'molecule')).toBe(true);
|
|
159
|
-
expect(executor.isPhaseComplete('c1', 'organism')).toBe(false);
|
|
218
|
+
expect(await executor.isPhaseComplete('c1', 'molecule')).toBe(true);
|
|
219
|
+
expect(await executor.isPhaseComplete('c1', 'organism')).toBe(false);
|
|
160
220
|
});
|
|
161
221
|
|
|
162
|
-
it('should return false for unknown correlationId', () => {
|
|
163
|
-
expect(executor.isPhaseComplete('unknown', 'molecule')).toBe(false);
|
|
222
|
+
it('should return false for unknown correlationId', async () => {
|
|
223
|
+
expect(await executor.isPhaseComplete('unknown', 'molecule')).toBe(false);
|
|
164
224
|
});
|
|
165
225
|
|
|
166
|
-
it('should return false for unknown phase name', () => {
|
|
226
|
+
it('should return false for unknown phase name', async () => {
|
|
167
227
|
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
168
228
|
const handler = createHandler(items);
|
|
169
229
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
170
230
|
|
|
171
|
-
executor.startPhased(handler, event, 'c1');
|
|
231
|
+
await executor.startPhased(handler, event, 'c1');
|
|
172
232
|
|
|
173
|
-
expect(executor.isPhaseComplete('c1', 'nonexistent-phase')).toBe(false);
|
|
233
|
+
expect(await executor.isPhaseComplete('c1', 'nonexistent-phase')).toBe(false);
|
|
174
234
|
});
|
|
175
235
|
|
|
176
|
-
it('should return false for future phase when current phase is earlier', () => {
|
|
236
|
+
it('should return false for future phase when current phase is earlier', async () => {
|
|
177
237
|
const items: TestItem[] = [
|
|
178
238
|
{ id: 'm1', type: 'molecule' },
|
|
179
239
|
{ id: 'p1', type: 'page' },
|
|
@@ -181,12 +241,12 @@ describe('PhasedExecutor', () => {
|
|
|
181
241
|
const handler = createHandler(items);
|
|
182
242
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
183
243
|
|
|
184
|
-
executor.startPhased(handler, event, 'c1');
|
|
244
|
+
await executor.startPhased(handler, event, 'c1');
|
|
185
245
|
|
|
186
|
-
expect(executor.isPhaseComplete('c1', 'page')).toBe(false);
|
|
246
|
+
expect(await executor.isPhaseComplete('c1', 'page')).toBe(false);
|
|
187
247
|
});
|
|
188
248
|
|
|
189
|
-
it('should check correct session when multiple sessions exist with different correlationIds', () => {
|
|
249
|
+
it('should check correct session when multiple sessions exist with different correlationIds', async () => {
|
|
190
250
|
const items1: TestItem[] = [
|
|
191
251
|
{ id: 'm1', type: 'molecule' },
|
|
192
252
|
{ id: 'o1', type: 'organism' },
|
|
@@ -197,27 +257,32 @@ describe('PhasedExecutor', () => {
|
|
|
197
257
|
];
|
|
198
258
|
const handler1 = createHandler(items1);
|
|
199
259
|
const handler2 = createHandler(items2);
|
|
260
|
+
executor.registerHandler(handler1);
|
|
261
|
+
executor.registerHandler(handler2);
|
|
200
262
|
|
|
201
|
-
executor.startPhased(
|
|
263
|
+
await executor.startPhased(
|
|
202
264
|
handler1,
|
|
203
265
|
{ type: 'ClientGenerated', correlationId: 'c1', data: { components: items1 } },
|
|
204
266
|
'c1',
|
|
205
267
|
);
|
|
206
|
-
executor.startPhased(
|
|
268
|
+
await executor.startPhased(
|
|
207
269
|
handler2,
|
|
208
270
|
{ type: 'ClientGenerated', correlationId: 'c2', data: { components: items2 } },
|
|
209
271
|
'c2',
|
|
210
272
|
);
|
|
211
273
|
|
|
212
|
-
executor.onEventReceived(
|
|
274
|
+
await executor.onEventReceived(
|
|
275
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
276
|
+
'm1',
|
|
277
|
+
);
|
|
213
278
|
|
|
214
|
-
expect(executor.isPhaseComplete('c1', 'molecule')).toBe(true);
|
|
215
|
-
expect(executor.isPhaseComplete('c2', 'molecule')).toBe(false);
|
|
279
|
+
expect(await executor.isPhaseComplete('c1', 'molecule')).toBe(true);
|
|
280
|
+
expect(await executor.isPhaseComplete('c2', 'molecule')).toBe(false);
|
|
216
281
|
});
|
|
217
282
|
});
|
|
218
283
|
|
|
219
284
|
describe('failure handling', () => {
|
|
220
|
-
it('should stop on failure when stopOnFailure is true', () => {
|
|
285
|
+
it('should stop on failure when stopOnFailure is true and emit failure event', async () => {
|
|
221
286
|
const items: TestItem[] = [
|
|
222
287
|
{ id: 'm1', type: 'molecule' },
|
|
223
288
|
{ id: 'm2', type: 'molecule' },
|
|
@@ -227,132 +292,343 @@ describe('PhasedExecutor', () => {
|
|
|
227
292
|
...createHandler(items),
|
|
228
293
|
stopOnFailure: true,
|
|
229
294
|
};
|
|
295
|
+
executor.registerHandler(handler);
|
|
230
296
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
231
297
|
|
|
232
|
-
executor.startPhased(handler, event, 'c1');
|
|
298
|
+
await executor.startPhased(handler, event, 'c1');
|
|
233
299
|
|
|
234
|
-
executor.onEventReceived({ type: 'ComponentsFailed', correlationId: 'c1', data: { filePath: 'm1' } }, 'm1');
|
|
300
|
+
await executor.onEventReceived({ type: 'ComponentsFailed', correlationId: 'c1', data: { filePath: 'm1' } }, 'm1');
|
|
235
301
|
|
|
236
302
|
expect(completed).toHaveLength(1);
|
|
237
303
|
expect(completed[0].type).toBe('ComponentsFailed');
|
|
238
|
-
expect(executor.getActiveSessionCount()).toBe(0);
|
|
239
304
|
});
|
|
240
305
|
|
|
241
|
-
it('should continue on failure when stopOnFailure is false', () => {
|
|
306
|
+
it('should continue on failure when stopOnFailure is false', async () => {
|
|
242
307
|
const items: TestItem[] = [
|
|
243
308
|
{ id: 'm1', type: 'molecule' },
|
|
244
309
|
{ id: 'm2', type: 'molecule' },
|
|
245
310
|
{ id: 'o1', type: 'organism' },
|
|
246
311
|
];
|
|
247
312
|
const handler = createHandler(items);
|
|
313
|
+
executor.registerHandler(handler);
|
|
248
314
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
249
315
|
|
|
250
|
-
executor.startPhased(handler, event, 'c1');
|
|
316
|
+
await executor.startPhased(handler, event, 'c1');
|
|
251
317
|
|
|
252
|
-
executor.onEventReceived({ type: 'ComponentsFailed', correlationId: 'c1', data: { filePath: 'm1' } }, 'm1');
|
|
318
|
+
await executor.onEventReceived({ type: 'ComponentsFailed', correlationId: 'c1', data: { filePath: 'm1' } }, 'm1');
|
|
253
319
|
|
|
254
320
|
expect(completed).toHaveLength(0);
|
|
255
|
-
expect(executor.getActiveSessionCount()).toBe(1);
|
|
256
321
|
|
|
257
|
-
executor.onEventReceived(
|
|
322
|
+
await executor.onEventReceived(
|
|
323
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm2' } },
|
|
324
|
+
'm2',
|
|
325
|
+
);
|
|
258
326
|
|
|
259
327
|
expect(dispatched).toHaveLength(3);
|
|
260
328
|
});
|
|
329
|
+
|
|
330
|
+
it('should cleanup session after stopOnFailure allowing new session with same correlationId', async () => {
|
|
331
|
+
const items: TestItem[] = [
|
|
332
|
+
{ id: 'm1', type: 'molecule' },
|
|
333
|
+
{ id: 'o1', type: 'organism' },
|
|
334
|
+
];
|
|
335
|
+
const handler: ForEachPhasedDescriptor = {
|
|
336
|
+
...createHandler(items),
|
|
337
|
+
stopOnFailure: true,
|
|
338
|
+
};
|
|
339
|
+
executor.registerHandler(handler);
|
|
340
|
+
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
341
|
+
|
|
342
|
+
await executor.startPhased(handler, event, 'c1');
|
|
343
|
+
await executor.onEventReceived({ type: 'ComponentsFailed', correlationId: 'c1', data: { filePath: 'm1' } }, 'm1');
|
|
344
|
+
|
|
345
|
+
expect(completed).toHaveLength(1);
|
|
346
|
+
expect(completed[0].type).toBe('ComponentsFailed');
|
|
347
|
+
|
|
348
|
+
dispatched.length = 0;
|
|
349
|
+
completed.length = 0;
|
|
350
|
+
|
|
351
|
+
await executor.startPhased(handler, event, 'c1');
|
|
352
|
+
expect(dispatched).toHaveLength(1);
|
|
353
|
+
});
|
|
261
354
|
});
|
|
262
355
|
|
|
263
356
|
describe('concurrent sessions', () => {
|
|
264
|
-
it('should track sessions independently by correlationId', () => {
|
|
357
|
+
it('should track sessions independently by correlationId', async () => {
|
|
265
358
|
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
266
359
|
const handler = createHandler(items);
|
|
360
|
+
executor.registerHandler(handler);
|
|
267
361
|
|
|
268
|
-
executor.startPhased(
|
|
362
|
+
await executor.startPhased(
|
|
269
363
|
handler,
|
|
270
364
|
{ type: 'ClientGenerated', correlationId: 'c1', data: { components: items } },
|
|
271
365
|
'c1',
|
|
272
366
|
);
|
|
273
|
-
executor.startPhased(
|
|
367
|
+
await executor.startPhased(
|
|
274
368
|
handler,
|
|
275
369
|
{ type: 'ClientGenerated', correlationId: 'c2', data: { components: items } },
|
|
276
370
|
'c2',
|
|
277
371
|
);
|
|
278
372
|
|
|
279
|
-
expect(
|
|
373
|
+
expect(dispatched).toHaveLength(2);
|
|
374
|
+
expect(dispatched[0].correlationId).toBe('c1');
|
|
375
|
+
expect(dispatched[1].correlationId).toBe('c2');
|
|
280
376
|
|
|
281
|
-
executor.onEventReceived(
|
|
377
|
+
await executor.onEventReceived(
|
|
378
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
379
|
+
'm1',
|
|
380
|
+
);
|
|
282
381
|
|
|
283
|
-
expect(executor.getActiveSessionCount()).toBe(1);
|
|
284
382
|
expect(completed).toHaveLength(1);
|
|
285
383
|
expect(completed[0].correlationId).toBe('c1');
|
|
384
|
+
|
|
385
|
+
await executor.onEventReceived(
|
|
386
|
+
{ type: 'ComponentImplemented', correlationId: 'c2', data: { filePath: 'm1' } },
|
|
387
|
+
'm1',
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
expect(completed).toHaveLength(2);
|
|
391
|
+
expect(completed[1].correlationId).toBe('c2');
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should not interfere between concurrent sessions with different items', async () => {
|
|
395
|
+
const items1: TestItem[] = [
|
|
396
|
+
{ id: 'a1', type: 'molecule' },
|
|
397
|
+
{ id: 'a2', type: 'organism' },
|
|
398
|
+
];
|
|
399
|
+
const items2: TestItem[] = [
|
|
400
|
+
{ id: 'b1', type: 'molecule' },
|
|
401
|
+
{ id: 'b2', type: 'page' },
|
|
402
|
+
];
|
|
403
|
+
const handler1 = createHandler(items1);
|
|
404
|
+
const handler2 = createHandler(items2);
|
|
405
|
+
executor.registerHandler(handler1);
|
|
406
|
+
executor.registerHandler(handler2);
|
|
407
|
+
|
|
408
|
+
await executor.startPhased(
|
|
409
|
+
handler1,
|
|
410
|
+
{ type: 'ClientGenerated', correlationId: 'c1', data: { components: items1 } },
|
|
411
|
+
'c1',
|
|
412
|
+
);
|
|
413
|
+
await executor.startPhased(
|
|
414
|
+
handler2,
|
|
415
|
+
{ type: 'ClientGenerated', correlationId: 'c2', data: { components: items2 } },
|
|
416
|
+
'c2',
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
expect(dispatched).toHaveLength(2);
|
|
420
|
+
|
|
421
|
+
await executor.onEventReceived(
|
|
422
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'a1' } },
|
|
423
|
+
'a1',
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
expect(dispatched).toHaveLength(3);
|
|
427
|
+
expect((dispatched[2].data as { filePath: string }).filePath).toBe('a2');
|
|
428
|
+
expect(dispatched[2].correlationId).toBe('c1');
|
|
429
|
+
|
|
430
|
+
await executor.onEventReceived(
|
|
431
|
+
{ type: 'ComponentImplemented', correlationId: 'c2', data: { filePath: 'b1' } },
|
|
432
|
+
'b1',
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
expect(dispatched).toHaveLength(4);
|
|
436
|
+
expect((dispatched[3].data as { filePath: string }).filePath).toBe('b2');
|
|
437
|
+
expect(dispatched[3].correlationId).toBe('c2');
|
|
286
438
|
});
|
|
287
439
|
});
|
|
288
440
|
|
|
289
441
|
describe('event deduplication', () => {
|
|
290
|
-
it('should ignore duplicate events for already completed items', () => {
|
|
442
|
+
it('should ignore duplicate events for already completed items', async () => {
|
|
291
443
|
const items: TestItem[] = [
|
|
292
444
|
{ id: 'm1', type: 'molecule' },
|
|
293
445
|
{ id: 'm2', type: 'molecule' },
|
|
294
446
|
{ id: 'o1', type: 'organism' },
|
|
295
447
|
];
|
|
296
448
|
const handler = createHandler(items);
|
|
449
|
+
executor.registerHandler(handler);
|
|
297
450
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
298
451
|
|
|
299
|
-
executor.startPhased(handler, event, 'c1');
|
|
452
|
+
await executor.startPhased(handler, event, 'c1');
|
|
300
453
|
|
|
301
|
-
executor.onEventReceived(
|
|
454
|
+
await executor.onEventReceived(
|
|
455
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
456
|
+
'm1',
|
|
457
|
+
);
|
|
302
458
|
|
|
303
459
|
expect(dispatched).toHaveLength(2);
|
|
304
460
|
|
|
305
|
-
executor.onEventReceived(
|
|
461
|
+
await executor.onEventReceived(
|
|
462
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
463
|
+
'm1',
|
|
464
|
+
);
|
|
306
465
|
|
|
307
466
|
expect(dispatched).toHaveLength(2);
|
|
308
467
|
});
|
|
309
468
|
});
|
|
310
469
|
|
|
311
470
|
describe('event edge cases', () => {
|
|
312
|
-
it('should ignore events with undefined correlationId', () => {
|
|
471
|
+
it('should ignore events with undefined correlationId', async () => {
|
|
313
472
|
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
314
473
|
const handler = createHandler(items);
|
|
315
474
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
316
475
|
|
|
317
|
-
executor.startPhased(handler, event, 'c1');
|
|
476
|
+
await executor.startPhased(handler, event, 'c1');
|
|
318
477
|
|
|
319
478
|
expect(dispatched).toHaveLength(1);
|
|
320
479
|
|
|
321
|
-
executor.onEventReceived({ type: 'ComponentImplemented', data: { filePath: 'm1' } }, 'm1');
|
|
480
|
+
await executor.onEventReceived({ type: 'ComponentImplemented', data: { filePath: 'm1' } }, 'm1');
|
|
322
481
|
|
|
323
482
|
expect(dispatched).toHaveLength(1);
|
|
324
|
-
expect(
|
|
483
|
+
expect(completed).toHaveLength(0);
|
|
325
484
|
});
|
|
326
485
|
|
|
327
|
-
it('should ignore events with empty correlationId', () => {
|
|
486
|
+
it('should ignore events with empty correlationId', async () => {
|
|
328
487
|
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
329
488
|
const handler = createHandler(items);
|
|
330
489
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
331
490
|
|
|
332
|
-
executor.startPhased(handler, event, 'c1');
|
|
491
|
+
await executor.startPhased(handler, event, 'c1');
|
|
333
492
|
|
|
334
493
|
expect(dispatched).toHaveLength(1);
|
|
335
494
|
|
|
336
|
-
executor.onEventReceived(
|
|
495
|
+
await executor.onEventReceived(
|
|
496
|
+
{ type: 'ComponentImplemented', correlationId: '', data: { filePath: 'm1' } },
|
|
497
|
+
'm1',
|
|
498
|
+
);
|
|
337
499
|
|
|
338
500
|
expect(dispatched).toHaveLength(1);
|
|
339
|
-
expect(
|
|
501
|
+
expect(completed).toHaveLength(0);
|
|
340
502
|
});
|
|
341
503
|
|
|
342
|
-
it('should ignore events with unknown itemKey', () => {
|
|
504
|
+
it('should ignore events with unknown itemKey', async () => {
|
|
343
505
|
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
344
506
|
const handler = createHandler(items);
|
|
345
507
|
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
346
508
|
|
|
347
|
-
executor.startPhased(handler, event, 'c1');
|
|
509
|
+
await executor.startPhased(handler, event, 'c1');
|
|
348
510
|
|
|
349
|
-
executor.onEventReceived(
|
|
511
|
+
await executor.onEventReceived(
|
|
350
512
|
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'unknown' } },
|
|
351
513
|
'unknown',
|
|
352
514
|
);
|
|
353
515
|
|
|
354
516
|
expect(dispatched).toHaveLength(1);
|
|
355
|
-
expect(
|
|
517
|
+
expect(completed).toHaveLength(0);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
describe('event emission', () => {
|
|
522
|
+
let emittedEvents: PhasedExecutionEvent[];
|
|
523
|
+
|
|
524
|
+
beforeEach(() => {
|
|
525
|
+
emittedEvents = [];
|
|
526
|
+
executor = createESExecutor(ctx, dispatched, completed, {
|
|
527
|
+
onEventEmit: (event) => {
|
|
528
|
+
emittedEvents.push(event);
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should emit PhasedExecutionStarted when starting', async () => {
|
|
534
|
+
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
535
|
+
const handler = createHandler(items);
|
|
536
|
+
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
537
|
+
|
|
538
|
+
await executor.startPhased(handler, event, 'c1');
|
|
539
|
+
|
|
540
|
+
const startEvent = emittedEvents.find((e) => e.type === 'PhasedExecutionStarted');
|
|
541
|
+
expect(startEvent).toBeDefined();
|
|
542
|
+
expect(startEvent?.data.correlationId).toBe('c1');
|
|
543
|
+
expect(startEvent?.data.items).toHaveLength(1);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('should emit PhasedItemDispatched when dispatching items', async () => {
|
|
547
|
+
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
548
|
+
const handler = createHandler(items);
|
|
549
|
+
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
550
|
+
|
|
551
|
+
await executor.startPhased(handler, event, 'c1');
|
|
552
|
+
|
|
553
|
+
const dispatchEvents = emittedEvents.filter((e) => e.type === 'PhasedItemDispatched');
|
|
554
|
+
expect(dispatchEvents).toHaveLength(1);
|
|
555
|
+
expect(dispatchEvents[0].data.itemKey).toBe('m1');
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('should emit PhasedItemCompleted when item completes', async () => {
|
|
559
|
+
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
560
|
+
const handler = createHandler(items);
|
|
561
|
+
executor.registerHandler(handler);
|
|
562
|
+
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
563
|
+
|
|
564
|
+
await executor.startPhased(handler, event, 'c1');
|
|
565
|
+
await executor.onEventReceived(
|
|
566
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
567
|
+
'm1',
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
const completeEvents = emittedEvents.filter((e) => e.type === 'PhasedItemCompleted');
|
|
571
|
+
expect(completeEvents).toHaveLength(1);
|
|
572
|
+
expect(completeEvents[0].data.itemKey).toBe('m1');
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should emit PhasedPhaseAdvanced when advancing phases', async () => {
|
|
576
|
+
const items: TestItem[] = [
|
|
577
|
+
{ id: 'm1', type: 'molecule' },
|
|
578
|
+
{ id: 'o1', type: 'organism' },
|
|
579
|
+
];
|
|
580
|
+
const handler = createHandler(items);
|
|
581
|
+
executor.registerHandler(handler);
|
|
582
|
+
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
583
|
+
|
|
584
|
+
await executor.startPhased(handler, event, 'c1');
|
|
585
|
+
await executor.onEventReceived(
|
|
586
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
587
|
+
'm1',
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
const advanceEvents = emittedEvents.filter((e) => e.type === 'PhasedPhaseAdvanced');
|
|
591
|
+
expect(advanceEvents).toHaveLength(1);
|
|
592
|
+
expect(advanceEvents[0].data.fromPhase).toBe(0);
|
|
593
|
+
expect(advanceEvents[0].data.toPhase).toBe(1);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('should emit PhasedExecutionCompleted on success', async () => {
|
|
597
|
+
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
598
|
+
const handler = createHandler(items);
|
|
599
|
+
executor.registerHandler(handler);
|
|
600
|
+
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
601
|
+
|
|
602
|
+
await executor.startPhased(handler, event, 'c1');
|
|
603
|
+
await executor.onEventReceived(
|
|
604
|
+
{ type: 'ComponentImplemented', correlationId: 'c1', data: { filePath: 'm1' } },
|
|
605
|
+
'm1',
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
const completedEvents = emittedEvents.filter((e) => e.type === 'PhasedExecutionCompleted');
|
|
609
|
+
expect(completedEvents).toHaveLength(1);
|
|
610
|
+
expect(completedEvents[0].data.success).toBe(true);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it('should emit PhasedItemFailed and PhasedExecutionCompleted on failure', async () => {
|
|
614
|
+
const items: TestItem[] = [{ id: 'm1', type: 'molecule' }];
|
|
615
|
+
const handler: ForEachPhasedDescriptor = {
|
|
616
|
+
...createHandler(items),
|
|
617
|
+
stopOnFailure: true,
|
|
618
|
+
};
|
|
619
|
+
executor.registerHandler(handler);
|
|
620
|
+
const event: Event = { type: 'ClientGenerated', correlationId: 'c1', data: { components: items } };
|
|
621
|
+
|
|
622
|
+
await executor.startPhased(handler, event, 'c1');
|
|
623
|
+
await executor.onEventReceived({ type: 'ComponentsFailed', correlationId: 'c1', data: { filePath: 'm1' } }, 'm1');
|
|
624
|
+
|
|
625
|
+
const failedEvents = emittedEvents.filter((e) => e.type === 'PhasedItemFailed');
|
|
626
|
+
expect(failedEvents).toHaveLength(1);
|
|
627
|
+
expect(failedEvents[0].data.itemKey).toBe('m1');
|
|
628
|
+
|
|
629
|
+
const completedEvents = emittedEvents.filter((e) => e.type === 'PhasedExecutionCompleted');
|
|
630
|
+
expect(completedEvents).toHaveLength(1);
|
|
631
|
+
expect(completedEvents[0].data.success).toBe(false);
|
|
356
632
|
});
|
|
357
633
|
});
|
|
358
634
|
});
|