@ai.ntellect/core 0.7.6 → 0.7.8
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/dist/graph/controller.d.ts +2 -3
- package/dist/graph/controller.d.ts.map +1 -1
- package/dist/graph/controller.js +5 -6
- package/dist/graph/controller.js.map +1 -1
- package/dist/graph/event-manager.d.ts +2 -1
- package/dist/graph/event-manager.d.ts.map +1 -1
- package/dist/graph/event-manager.js +55 -3
- package/dist/graph/event-manager.js.map +1 -1
- package/dist/graph/index.d.ts +1 -1
- package/dist/graph/index.d.ts.map +1 -1
- package/dist/graph/index.js +2 -2
- package/dist/graph/index.js.map +1 -1
- package/dist/graph/node.d.ts +10 -39
- package/dist/graph/node.d.ts.map +1 -1
- package/dist/graph/node.js +41 -64
- package/dist/graph/node.js.map +1 -1
- package/dist/graph/observer.d.ts +13 -11
- package/dist/graph/observer.d.ts.map +1 -1
- package/dist/graph/observer.js.map +1 -1
- package/dist/interfaces/index.d.ts +2 -2
- package/dist/interfaces/index.d.ts.map +1 -1
- package/dist/interfaces/index.js.map +1 -1
- package/dist/types/index.d.ts +13 -9
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/generate-action-schema.js +2 -2
- package/graph/controller.ts +5 -10
- package/graph/event-manager.ts +74 -6
- package/graph/index.ts +4 -6
- package/graph/node.ts +53 -95
- package/graph/observer.ts +10 -10
- package/interfaces/index.ts +4 -1
- package/package.json +1 -1
- package/test/graph/event-manager.test.ts +46 -0
- package/test/graph/index.test.ts +119 -173
- package/test/graph/node.test.ts +323 -0
- package/types/index.ts +15 -9
- package/utils/generate-action-schema.ts +2 -2
package/test/graph/index.test.ts
CHANGED
@@ -1,19 +1,27 @@
|
|
1
|
-
import { expect } from "chai";
|
1
|
+
import { expect, use } from "chai";
|
2
|
+
import chaiAsPromised from "chai-as-promised";
|
2
3
|
import EventEmitter from "events";
|
3
4
|
import sinon from "sinon";
|
4
5
|
import { z } from "zod";
|
5
6
|
import { GraphController } from "../../graph/controller";
|
6
7
|
import { GraphFlow } from "../../graph/index";
|
8
|
+
import { NodeParams } from "../../graph/node";
|
7
9
|
import { GraphContext, GraphDefinition, Node } from "../../types";
|
8
10
|
|
11
|
+
use(chaiAsPromised);
|
12
|
+
|
9
13
|
/**
|
10
14
|
* Test schema definition using Zod for graph context validation
|
11
15
|
* Defines a schema with:
|
12
16
|
* - value: numeric value for tracking state changes
|
17
|
+
* - counter: numeric value for tracking state changes
|
18
|
+
* - message: string for tracking state changes
|
13
19
|
* - eventPayload: optional object containing transaction metadata
|
14
20
|
*/
|
15
21
|
const TestSchema = z.object({
|
16
22
|
value: z.number().default(0),
|
23
|
+
counter: z.number().default(0),
|
24
|
+
message: z.string().default(""),
|
17
25
|
eventPayload: z
|
18
26
|
.object({
|
19
27
|
transactionId: z.string().optional(),
|
@@ -172,18 +180,15 @@ describe("GraphFlow", function () {
|
|
172
180
|
* - Integration between node execution and validation
|
173
181
|
* Ensures type safety and data consistency in node interactions
|
174
182
|
*/
|
175
|
-
it("should execute a node with validated
|
183
|
+
it("should execute a node with validated params and outputs", async function () {
|
176
184
|
const paramNode: Node<TestSchema, { increment: number }> = {
|
177
185
|
name: "paramNode",
|
178
|
-
|
186
|
+
params: z.object({
|
179
187
|
increment: z.number(),
|
180
188
|
}),
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
execute: async (context, inputs?: { increment: number }) => {
|
185
|
-
if (!inputs) throw new Error("Inputs required");
|
186
|
-
context.value = (context.value ?? 0) + inputs.increment;
|
189
|
+
execute: async (context, params?: { increment: number }) => {
|
190
|
+
if (!params) throw new Error("params required");
|
191
|
+
context.value = (context.value ?? 0) + params.increment;
|
187
192
|
},
|
188
193
|
next: [],
|
189
194
|
};
|
@@ -304,12 +309,12 @@ describe("GraphFlow", function () {
|
|
304
309
|
it("should throw error when node input validation fails", async () => {
|
305
310
|
const node: Node<TestSchema> = {
|
306
311
|
name: "test",
|
307
|
-
|
312
|
+
params: z.object({
|
308
313
|
value: z.number().min(0),
|
309
314
|
}),
|
310
|
-
execute: async (context,
|
311
|
-
if (!
|
312
|
-
context.value =
|
315
|
+
execute: async (context, params) => {
|
316
|
+
if (!params) throw new Error("params required");
|
317
|
+
context.value = params.value;
|
313
318
|
},
|
314
319
|
};
|
315
320
|
|
@@ -327,98 +332,68 @@ describe("GraphFlow", function () {
|
|
327
332
|
expect(error.message).to.include("Number must be greater than or equal");
|
328
333
|
}
|
329
334
|
});
|
330
|
-
|
331
|
-
/**
|
332
|
-
* Tests output validation error handling
|
333
|
-
*/
|
334
|
-
it("should throw error when node output validation fails", async function () {
|
335
|
-
const nodeWithOutput: Node<TestSchema> = {
|
336
|
-
name: "outputNode",
|
337
|
-
outputs: z.object({
|
338
|
-
value: z.number().max(10),
|
339
|
-
}),
|
340
|
-
execute: async (context) => {
|
341
|
-
context.value = 20; // This will fail output validation
|
342
|
-
},
|
343
|
-
next: [],
|
344
|
-
};
|
345
|
-
|
346
|
-
graph.addNode(nodeWithOutput);
|
347
|
-
|
348
|
-
try {
|
349
|
-
await graph.execute("outputNode");
|
350
|
-
expect.fail("Should have thrown an error");
|
351
|
-
} catch (error) {
|
352
|
-
expect((error as Error).message).to.include(
|
353
|
-
"Number must be less than or equal to 10"
|
354
|
-
);
|
355
|
-
}
|
356
|
-
});
|
357
|
-
|
358
335
|
/**
|
359
336
|
* Tests successful input/output validation flow
|
360
337
|
*/
|
361
|
-
it("should successfully validate both
|
338
|
+
it("should successfully validate both params and outputs", async function () {
|
362
339
|
const graph = new GraphFlow("test", {
|
363
340
|
name: "test",
|
341
|
+
nodes: [
|
342
|
+
{
|
343
|
+
name: "validatedNode",
|
344
|
+
params: z.object({
|
345
|
+
increment: z.number().min(0).max(5),
|
346
|
+
}),
|
347
|
+
execute: async (
|
348
|
+
context: GraphContext<TestSchema>,
|
349
|
+
params?: NodeParams
|
350
|
+
) => {
|
351
|
+
if (!params) throw new Error("params required");
|
352
|
+
context.value = (context.value ?? 0) + params.increment;
|
353
|
+
},
|
354
|
+
},
|
355
|
+
],
|
364
356
|
schema: TestSchema,
|
365
|
-
context: { value: 0 },
|
366
|
-
nodes: [],
|
357
|
+
context: { value: 0, counter: 0, message: "" },
|
367
358
|
});
|
368
359
|
|
369
|
-
|
370
|
-
name: "validatedNode",
|
371
|
-
inputs: z.object({
|
372
|
-
increment: z.number().min(0).max(5),
|
373
|
-
}),
|
374
|
-
outputs: z.object({
|
375
|
-
value: z.number().min(0).max(10),
|
376
|
-
}),
|
377
|
-
execute: async (context, inputs?: { increment: number }) => {
|
378
|
-
if (!inputs) throw new Error("Inputs required");
|
379
|
-
context.value = (context.value ?? 0) + inputs.increment;
|
380
|
-
},
|
381
|
-
next: [],
|
382
|
-
};
|
383
|
-
|
384
|
-
graph.addNode(validatedNode);
|
385
|
-
|
386
|
-
// Test with valid input that produces valid output
|
360
|
+
// Test avec valeur valide
|
387
361
|
await graph.execute("validatedNode", { increment: 3 });
|
388
362
|
expect(graph.getContext().value).to.equal(3);
|
389
363
|
|
390
|
-
// Test
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
} catch (error) {
|
395
|
-
expect((error as Error).message).to.include(
|
396
|
-
"Number must be less than or equal to 10"
|
397
|
-
);
|
398
|
-
}
|
364
|
+
// Test avec valeur invalide
|
365
|
+
await expect(
|
366
|
+
graph.execute("validatedNode", { increment: 10 })
|
367
|
+
).to.be.rejectedWith("Number must be less than or equal to 5");
|
399
368
|
});
|
400
369
|
|
401
370
|
/**
|
402
|
-
* Tests handling of missing required
|
371
|
+
* Tests handling of missing required params
|
403
372
|
*/
|
404
|
-
it("should throw error when required
|
405
|
-
const
|
406
|
-
name: "
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
373
|
+
it("should throw error when required params are missing", async function () {
|
374
|
+
const graph = new GraphFlow("test", {
|
375
|
+
name: "test",
|
376
|
+
nodes: [
|
377
|
+
{
|
378
|
+
name: "requiredInputNode",
|
379
|
+
params: z.object({
|
380
|
+
value: z.number(),
|
381
|
+
}),
|
382
|
+
execute: async (
|
383
|
+
context: GraphContext<TestSchema>,
|
384
|
+
params?: NodeParams
|
385
|
+
) => {
|
386
|
+
context.counter = params?.value || 0;
|
387
|
+
},
|
388
|
+
},
|
389
|
+
],
|
390
|
+
schema: TestSchema,
|
391
|
+
context: { value: 0, counter: 0, message: "" },
|
392
|
+
});
|
415
393
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
} catch (error) {
|
420
|
-
expect((error as Error).message).to.include("Inputs required for node");
|
421
|
-
}
|
394
|
+
await expect(graph.execute("requiredInputNode")).to.be.rejectedWith(
|
395
|
+
"Params required for node"
|
396
|
+
);
|
422
397
|
});
|
423
398
|
|
424
399
|
/**
|
@@ -616,90 +591,6 @@ describe("GraphFlow", function () {
|
|
616
591
|
expect(results[1].value).to.equal(5); // Graph2: 3 + 2
|
617
592
|
});
|
618
593
|
|
619
|
-
/**
|
620
|
-
* Tests event correlation functionality
|
621
|
-
* Demonstrates:
|
622
|
-
* - Event correlation based on transaction ID
|
623
|
-
* - Timeout handling
|
624
|
-
* - Multiple event synchronization
|
625
|
-
* - Context updates after correlation
|
626
|
-
* Critical for integrating with external event sources
|
627
|
-
*/
|
628
|
-
it("should handle correlated events correctly", async function () {
|
629
|
-
this.timeout(10000);
|
630
|
-
const graph = new GraphFlow("test", {
|
631
|
-
name: "test",
|
632
|
-
nodes: [],
|
633
|
-
context: { value: 0 },
|
634
|
-
schema: TestSchema,
|
635
|
-
eventEmitter: new EventEmitter(),
|
636
|
-
});
|
637
|
-
|
638
|
-
let eventsReceived = 0;
|
639
|
-
const node = {
|
640
|
-
name: "testNode",
|
641
|
-
waitForEvents: {
|
642
|
-
events: ["eventA", "eventB"],
|
643
|
-
timeout: 5000,
|
644
|
-
strategy: "all" as const,
|
645
|
-
},
|
646
|
-
execute: async (context: GraphContext<typeof TestSchema>) => {
|
647
|
-
eventsReceived = 2;
|
648
|
-
context.value = 42;
|
649
|
-
},
|
650
|
-
};
|
651
|
-
|
652
|
-
graph.addNode(node);
|
653
|
-
|
654
|
-
graph.execute("testNode");
|
655
|
-
|
656
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
657
|
-
|
658
|
-
await graph.emit("eventA", { eventPayload: { status: "A" } });
|
659
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
660
|
-
await graph.emit("eventB", { eventPayload: { status: "B" } });
|
661
|
-
|
662
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
663
|
-
|
664
|
-
expect(eventsReceived).to.equal(2);
|
665
|
-
expect(graph.getContext().value).to.equal(42);
|
666
|
-
});
|
667
|
-
|
668
|
-
/**
|
669
|
-
* Tests multiple event waiting functionality
|
670
|
-
*/
|
671
|
-
it("should wait for multiple events before continuing", async function () {
|
672
|
-
this.timeout(10000);
|
673
|
-
const graph = new GraphFlow("test", {
|
674
|
-
name: "test",
|
675
|
-
nodes: [],
|
676
|
-
context: { value: 0 },
|
677
|
-
schema: TestSchema,
|
678
|
-
eventEmitter: new EventEmitter(),
|
679
|
-
});
|
680
|
-
|
681
|
-
const node = {
|
682
|
-
name: "testNode",
|
683
|
-
waitForEvents: {
|
684
|
-
events: ["event1", "event2"],
|
685
|
-
timeout: 5000,
|
686
|
-
strategy: "all" as const,
|
687
|
-
},
|
688
|
-
execute: async (context: GraphContext<typeof TestSchema>) => {
|
689
|
-
context.value = 42; // Ajouter une modification du contexte
|
690
|
-
},
|
691
|
-
};
|
692
|
-
|
693
|
-
graph.addNode(node);
|
694
|
-
graph.execute("testNode");
|
695
|
-
|
696
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
697
|
-
await graph.emit("event1", { eventPayload: { status: "1" } });
|
698
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
699
|
-
await graph.emit("event2", { eventPayload: { status: "2" } });
|
700
|
-
expect(graph.getContext().value).to.equal(42);
|
701
|
-
});
|
702
|
-
|
703
594
|
/**
|
704
595
|
* Tests single event waiting functionality
|
705
596
|
*/
|
@@ -735,4 +626,59 @@ describe("GraphFlow", function () {
|
|
735
626
|
const result = await resultPromise;
|
736
627
|
expect(result.value).to.equal(6); // 1 (waitingNode) + 5 (finalNode)
|
737
628
|
});
|
629
|
+
|
630
|
+
// Test de validation des paramètres
|
631
|
+
it("should successfully validate params", async () => {
|
632
|
+
const graph = new GraphFlow("test", {
|
633
|
+
name: "test",
|
634
|
+
nodes: [
|
635
|
+
{
|
636
|
+
name: "validationNode",
|
637
|
+
params: z.object({
|
638
|
+
value: z.number().max(10),
|
639
|
+
}),
|
640
|
+
execute: async (
|
641
|
+
context: GraphContext<TestSchema>,
|
642
|
+
params?: NodeParams
|
643
|
+
) => {
|
644
|
+
context.counter = params?.value || 0;
|
645
|
+
},
|
646
|
+
},
|
647
|
+
],
|
648
|
+
schema: TestSchema,
|
649
|
+
context: { value: 0, counter: 0, message: "" },
|
650
|
+
});
|
651
|
+
|
652
|
+
// Test avec valeur invalide
|
653
|
+
await expect(
|
654
|
+
graph.execute("validationNode", { value: 20 })
|
655
|
+
).to.be.rejectedWith("Number must be less than or equal to 10");
|
656
|
+
});
|
657
|
+
|
658
|
+
// Test des paramètres requis
|
659
|
+
it("should throw error when required params are missing", async () => {
|
660
|
+
const graph = new GraphFlow("test", {
|
661
|
+
name: "test",
|
662
|
+
nodes: [
|
663
|
+
{
|
664
|
+
name: "requiredInputNode",
|
665
|
+
params: z.object({
|
666
|
+
value: z.number(),
|
667
|
+
}),
|
668
|
+
execute: async (
|
669
|
+
context: GraphContext<TestSchema>,
|
670
|
+
params?: NodeParams
|
671
|
+
) => {
|
672
|
+
context.counter = params?.value || 0;
|
673
|
+
},
|
674
|
+
},
|
675
|
+
],
|
676
|
+
schema: TestSchema,
|
677
|
+
context: { value: 0, counter: 0, message: "" },
|
678
|
+
});
|
679
|
+
|
680
|
+
await expect(graph.execute("requiredInputNode")).to.be.rejectedWith(
|
681
|
+
"Params required for node"
|
682
|
+
);
|
683
|
+
});
|
738
684
|
});
|
package/test/graph/node.test.ts
CHANGED
@@ -329,4 +329,327 @@ describe("GraphNode", () => {
|
|
329
329
|
expect(stateChanges.length).to.equal(uniqueChanges.size);
|
330
330
|
expect(stateChanges).to.have.lengthOf(2); // Un pour counter, un pour message
|
331
331
|
});
|
332
|
+
|
333
|
+
it("should validate node parameters with Zod schema", async () => {
|
334
|
+
const paramSchema = z.object({
|
335
|
+
increment: z.number().min(1),
|
336
|
+
message: z.string().min(1),
|
337
|
+
});
|
338
|
+
|
339
|
+
const nodes = new Map();
|
340
|
+
nodes.set("test", {
|
341
|
+
name: "test",
|
342
|
+
params: paramSchema,
|
343
|
+
execute: async (context: TestContext, params?: NodeParams) => {
|
344
|
+
context.counter += params?.increment || 0;
|
345
|
+
context.message = params?.message || "";
|
346
|
+
},
|
347
|
+
});
|
348
|
+
|
349
|
+
node = new GraphNode(
|
350
|
+
nodes,
|
351
|
+
logger,
|
352
|
+
eventManager,
|
353
|
+
eventSubject,
|
354
|
+
stateSubject
|
355
|
+
);
|
356
|
+
|
357
|
+
// Test avec des paramètres valides
|
358
|
+
await node.executeNode(
|
359
|
+
"test",
|
360
|
+
{ counter: 0, message: "Hello" },
|
361
|
+
{ increment: 5, message: "Valid" }
|
362
|
+
);
|
363
|
+
|
364
|
+
// Test avec des paramètres invalides
|
365
|
+
await expect(
|
366
|
+
node.executeNode(
|
367
|
+
"test",
|
368
|
+
{ counter: 0, message: "Hello" },
|
369
|
+
{ increment: 0, message: "" }
|
370
|
+
)
|
371
|
+
).to.be.rejected; // Enlever le .with() car le message d'erreur vient directement de Zod
|
372
|
+
});
|
373
|
+
|
374
|
+
it("should work without params schema", async () => {
|
375
|
+
const nodes = new Map();
|
376
|
+
nodes.set("test", {
|
377
|
+
name: "test",
|
378
|
+
execute: async (context: TestContext) => {
|
379
|
+
context.counter++;
|
380
|
+
},
|
381
|
+
});
|
382
|
+
|
383
|
+
node = new GraphNode(
|
384
|
+
nodes,
|
385
|
+
logger,
|
386
|
+
eventManager,
|
387
|
+
eventSubject,
|
388
|
+
stateSubject
|
389
|
+
);
|
390
|
+
|
391
|
+
// Devrait fonctionner sans erreur même sans schema de params
|
392
|
+
await node.executeNode("test", { counter: 0, message: "Hello" }, null);
|
393
|
+
});
|
394
|
+
|
395
|
+
it("should not require params when node has no params schema", async () => {
|
396
|
+
const nodes = new Map();
|
397
|
+
nodes.set("test", {
|
398
|
+
name: "test",
|
399
|
+
// Pas de schéma de params défini
|
400
|
+
execute: async (context: TestContext) => {
|
401
|
+
context.counter++;
|
402
|
+
},
|
403
|
+
});
|
404
|
+
|
405
|
+
node = new GraphNode(
|
406
|
+
nodes,
|
407
|
+
logger,
|
408
|
+
eventManager,
|
409
|
+
eventSubject,
|
410
|
+
stateSubject
|
411
|
+
);
|
412
|
+
|
413
|
+
await node.executeNode("test", { counter: 0, message: "Hello" }, null);
|
414
|
+
|
415
|
+
const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
|
416
|
+
expect(stateChanges).to.have.lengthOf(1);
|
417
|
+
expect(stateChanges[0].payload.newValue).to.equal(1);
|
418
|
+
});
|
419
|
+
|
420
|
+
it("should require params only when node has params schema", async () => {
|
421
|
+
const nodes = new Map();
|
422
|
+
nodes.set("test", {
|
423
|
+
name: "test",
|
424
|
+
params: z.object({
|
425
|
+
// Avec un schéma de params
|
426
|
+
value: z.number(),
|
427
|
+
}),
|
428
|
+
execute: async (context: TestContext, params?: NodeParams) => {
|
429
|
+
context.counter = params?.value || 0;
|
430
|
+
},
|
431
|
+
});
|
432
|
+
|
433
|
+
node = new GraphNode(
|
434
|
+
nodes,
|
435
|
+
logger,
|
436
|
+
eventManager,
|
437
|
+
eventSubject,
|
438
|
+
stateSubject
|
439
|
+
);
|
440
|
+
|
441
|
+
// Devrait échouer sans params
|
442
|
+
await expect(
|
443
|
+
node.executeNode("test", { counter: 0, message: "Hello" }, null)
|
444
|
+
).to.be.rejectedWith("Params required for node");
|
445
|
+
});
|
446
|
+
|
447
|
+
it("should execute node without params when no schema is defined (real world scenario)", async () => {
|
448
|
+
const nodes = new Map();
|
449
|
+
nodes.set("incrementCounter", {
|
450
|
+
name: "incrementCounter",
|
451
|
+
execute: async (context: TestContext) => {
|
452
|
+
context.counter++;
|
453
|
+
},
|
454
|
+
});
|
455
|
+
|
456
|
+
node = new GraphNode(
|
457
|
+
nodes,
|
458
|
+
logger,
|
459
|
+
eventManager,
|
460
|
+
eventSubject,
|
461
|
+
stateSubject
|
462
|
+
);
|
463
|
+
|
464
|
+
// Simuler l'appel comme dans examples/t2.ts
|
465
|
+
await node.executeNode(
|
466
|
+
"incrementCounter",
|
467
|
+
{ message: "Hello", counter: 0 },
|
468
|
+
{ test: "test" } // Passer des params même si non requis
|
469
|
+
);
|
470
|
+
|
471
|
+
const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
|
472
|
+
expect(stateChanges).to.have.lengthOf(1);
|
473
|
+
expect(stateChanges[0].payload.newValue).to.equal(1);
|
474
|
+
});
|
475
|
+
|
476
|
+
it("should handle optional params schema", async () => {
|
477
|
+
const nodes = new Map();
|
478
|
+
nodes.set("test", {
|
479
|
+
name: "test",
|
480
|
+
params: z
|
481
|
+
.object({
|
482
|
+
test: z.string(),
|
483
|
+
})
|
484
|
+
.optional(),
|
485
|
+
execute: async (context: TestContext, params?: NodeParams) => {
|
486
|
+
context.counter++;
|
487
|
+
},
|
488
|
+
});
|
489
|
+
|
490
|
+
node = new GraphNode(
|
491
|
+
nodes,
|
492
|
+
logger,
|
493
|
+
eventManager,
|
494
|
+
eventSubject,
|
495
|
+
stateSubject
|
496
|
+
);
|
497
|
+
|
498
|
+
// Devrait fonctionner avec ou sans params
|
499
|
+
await node.executeNode(
|
500
|
+
"test",
|
501
|
+
{ counter: 0, message: "Hello" },
|
502
|
+
{ test: "test" }
|
503
|
+
);
|
504
|
+
await node.executeNode("test", { counter: 1, message: "Hello" }, null);
|
505
|
+
|
506
|
+
const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
|
507
|
+
expect(stateChanges).to.have.lengthOf(2);
|
508
|
+
expect(stateChanges[1].payload.newValue).to.equal(2);
|
509
|
+
});
|
510
|
+
|
511
|
+
it("should wait for events before executing node", async () => {
|
512
|
+
const nodes = new Map();
|
513
|
+
nodes.set("waitForEventsNode", {
|
514
|
+
name: "waitForEventsNode",
|
515
|
+
waitForEvents: {
|
516
|
+
events: ["event1", "event2"],
|
517
|
+
timeout: 1000,
|
518
|
+
strategy: "all",
|
519
|
+
},
|
520
|
+
execute: async (context: TestContext) => {
|
521
|
+
context.message = "Events received";
|
522
|
+
},
|
523
|
+
});
|
524
|
+
|
525
|
+
node = new GraphNode(
|
526
|
+
nodes,
|
527
|
+
logger,
|
528
|
+
eventManager,
|
529
|
+
eventSubject,
|
530
|
+
stateSubject
|
531
|
+
);
|
532
|
+
|
533
|
+
// Lancer l'exécution du nœud
|
534
|
+
const execution = node.executeNode(
|
535
|
+
"waitForEventsNode",
|
536
|
+
{ counter: 0, message: "Hello" },
|
537
|
+
null
|
538
|
+
);
|
539
|
+
|
540
|
+
// Simuler les événements après un court délai
|
541
|
+
setTimeout(() => {
|
542
|
+
eventEmitter.emit("event1", { data: "test1" });
|
543
|
+
eventEmitter.emit("event2", { data: "test2" });
|
544
|
+
}, 100);
|
545
|
+
|
546
|
+
await execution;
|
547
|
+
|
548
|
+
const stateChanges = events.filter((e) => e.type === "nodeStateChanged");
|
549
|
+
expect(stateChanges).to.have.lengthOf(1);
|
550
|
+
expect(stateChanges[0].payload.newValue).to.equal("Events received");
|
551
|
+
});
|
552
|
+
|
553
|
+
it("should timeout if events are not received", async () => {
|
554
|
+
const nodes = new Map();
|
555
|
+
nodes.set("timeoutNode", {
|
556
|
+
name: "timeoutNode",
|
557
|
+
waitForEvents: {
|
558
|
+
events: ["event1", "event2"],
|
559
|
+
timeout: 100,
|
560
|
+
strategy: "all",
|
561
|
+
},
|
562
|
+
execute: async (context: TestContext) => {
|
563
|
+
context.message = "Should not execute";
|
564
|
+
},
|
565
|
+
});
|
566
|
+
|
567
|
+
node = new GraphNode(
|
568
|
+
nodes,
|
569
|
+
logger,
|
570
|
+
eventManager,
|
571
|
+
eventSubject,
|
572
|
+
stateSubject
|
573
|
+
);
|
574
|
+
|
575
|
+
await expect(
|
576
|
+
node.executeNode("timeoutNode", { counter: 0, message: "Hello" }, null)
|
577
|
+
).to.be.rejectedWith("Timeout waiting for events");
|
578
|
+
});
|
579
|
+
|
580
|
+
it("should handle partial event reception", async () => {
|
581
|
+
const nodes = new Map();
|
582
|
+
nodes.set("partialEventsNode", {
|
583
|
+
name: "partialEventsNode",
|
584
|
+
waitForEvents: {
|
585
|
+
events: ["event1", "event2"],
|
586
|
+
timeout: 1000,
|
587
|
+
strategy: "all",
|
588
|
+
},
|
589
|
+
execute: async (context: TestContext) => {
|
590
|
+
context.message = "All events received";
|
591
|
+
},
|
592
|
+
});
|
593
|
+
|
594
|
+
node = new GraphNode(
|
595
|
+
nodes,
|
596
|
+
logger,
|
597
|
+
eventManager,
|
598
|
+
eventSubject,
|
599
|
+
stateSubject
|
600
|
+
);
|
601
|
+
|
602
|
+
const execution = node.executeNode(
|
603
|
+
"partialEventsNode",
|
604
|
+
{ counter: 0, message: "Hello" },
|
605
|
+
null
|
606
|
+
);
|
607
|
+
|
608
|
+
// N'émettre qu'un seul événement
|
609
|
+
setTimeout(() => {
|
610
|
+
eventEmitter.emit("event1", { data: "test1" });
|
611
|
+
}, 100);
|
612
|
+
|
613
|
+
await expect(execution).to.be.rejectedWith("Timeout waiting for events");
|
614
|
+
});
|
615
|
+
|
616
|
+
it("should handle correlated events", (done) => {
|
617
|
+
const nodes = new Map();
|
618
|
+
nodes.set("correlatedEventsNode", {
|
619
|
+
name: "correlatedEventsNode",
|
620
|
+
correlateEvents: {
|
621
|
+
events: ["payment", "stock"],
|
622
|
+
timeout: 1000,
|
623
|
+
correlation: (events: Array<{ type: string; payload?: any }>) => {
|
624
|
+
const paymentEvent = events.find((e) => e.type === "payment");
|
625
|
+
const stockEvent = events.find((e) => e.type === "stock");
|
626
|
+
return paymentEvent?.payload?.id === stockEvent?.payload?.id;
|
627
|
+
},
|
628
|
+
},
|
629
|
+
execute: (context: TestContext) => {
|
630
|
+
context.message = "Correlated events received";
|
631
|
+
done();
|
632
|
+
return Promise.resolve();
|
633
|
+
},
|
634
|
+
});
|
635
|
+
|
636
|
+
node = new GraphNode(
|
637
|
+
nodes,
|
638
|
+
logger,
|
639
|
+
eventManager,
|
640
|
+
eventSubject,
|
641
|
+
stateSubject
|
642
|
+
);
|
643
|
+
|
644
|
+
node.executeNode(
|
645
|
+
"correlatedEventsNode",
|
646
|
+
{ counter: 0, message: "Hello" },
|
647
|
+
null
|
648
|
+
);
|
649
|
+
|
650
|
+
setTimeout(() => {
|
651
|
+
eventEmitter.emit("payment", { id: "123", status: "completed" });
|
652
|
+
eventEmitter.emit("stock", { id: "123", status: "available" });
|
653
|
+
}, 100);
|
654
|
+
});
|
332
655
|
});
|