@dudousxd/nestjs-durable-testing 0.15.0 → 0.16.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/dist/index.d.ts +2 -0
- package/dist/index.js +408 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -208,6 +208,8 @@ declare function assertReplayable(register: (engine: WorkflowEngine) => void, hi
|
|
|
208
208
|
*/
|
|
209
209
|
interface StateStoreContext {
|
|
210
210
|
store: StateStore;
|
|
211
|
+
/** A second wrapper sharing the database/pool, used to exercise cross-instance claims. */
|
|
212
|
+
peerStore?: StateStore;
|
|
211
213
|
cleanup: () => Promise<void>;
|
|
212
214
|
/**
|
|
213
215
|
* Whether the store's optional `transaction` can run an ASYNC work callback. Defaults to `true`.
|
package/dist/index.js
CHANGED
|
@@ -399,7 +399,7 @@ async function assertReplayable(register, history) {
|
|
|
399
399
|
__name(assertReplayable, "assertReplayable");
|
|
400
400
|
|
|
401
401
|
// src/state-store-conformance.ts
|
|
402
|
-
import { WorkflowEngine as WorkflowEngine3 } from "@dudousxd/nestjs-durable-core";
|
|
402
|
+
import { SINGLETON_ADMITTED_TAG, WorkflowEngine as WorkflowEngine3 } from "@dudousxd/nestjs-durable-core";
|
|
403
403
|
import { beforeEach, describe as describe2, expect as expect2, it as it2 } from "vitest";
|
|
404
404
|
var StateStoreUnavailableError = class extends Error {
|
|
405
405
|
static {
|
|
@@ -449,6 +449,7 @@ var checkpoint = /* @__PURE__ */ __name((over = {}) => ({
|
|
|
449
449
|
function runStateStoreContract(name, makeStore) {
|
|
450
450
|
describe2(`StateStore contract: ${name}`, () => {
|
|
451
451
|
let store;
|
|
452
|
+
let peerStore;
|
|
452
453
|
let cleanup;
|
|
453
454
|
let supportsAsyncTransaction = true;
|
|
454
455
|
let supportsTagFilter = true;
|
|
@@ -458,6 +459,7 @@ function runStateStoreContract(name, makeStore) {
|
|
|
458
459
|
try {
|
|
459
460
|
const ctx = await makeStore();
|
|
460
461
|
store = ctx.store;
|
|
462
|
+
peerStore = ctx.peerStore ?? ctx.store;
|
|
461
463
|
cleanup = ctx.cleanup;
|
|
462
464
|
supportsAsyncTransaction = ctx.supportsAsyncTransaction ?? true;
|
|
463
465
|
supportsTagFilter = ctx.supportsTagFilter ?? true;
|
|
@@ -483,6 +485,197 @@ function runStateStoreContract(name, makeStore) {
|
|
|
483
485
|
await fn();
|
|
484
486
|
});
|
|
485
487
|
}, "t");
|
|
488
|
+
t("atomically admits holders without displacement by later same-millisecond IDs", async () => {
|
|
489
|
+
expect2(store.tryAdmitSingleton).toBeTypeOf("function");
|
|
490
|
+
const claim = /* @__PURE__ */ __name((id, limit = 1) => store.tryAdmitSingleton?.(id, "singleton:k", "checkout", limit), "claim");
|
|
491
|
+
await store.createRun(run({
|
|
492
|
+
id: "z-holder",
|
|
493
|
+
tags: [
|
|
494
|
+
"singleton:k",
|
|
495
|
+
"user-tag"
|
|
496
|
+
]
|
|
497
|
+
}));
|
|
498
|
+
expect2(await claim("z-holder")).toBe(true);
|
|
499
|
+
await store.updateRun("z-holder", {
|
|
500
|
+
status: "suspended",
|
|
501
|
+
wakeAt: 1e4
|
|
502
|
+
});
|
|
503
|
+
expect2(await claim("z-holder")).toBe(true);
|
|
504
|
+
await store.updateRun("z-holder", {
|
|
505
|
+
status: "blocked",
|
|
506
|
+
lockedBy: "expired",
|
|
507
|
+
lockedUntil: 0
|
|
508
|
+
});
|
|
509
|
+
await store.createRun(run({
|
|
510
|
+
id: "a-waiter",
|
|
511
|
+
tags: [
|
|
512
|
+
"singleton:k"
|
|
513
|
+
]
|
|
514
|
+
}));
|
|
515
|
+
expect2(await claim("a-waiter")).toBe(false);
|
|
516
|
+
expect2(await claim("z-holder")).toBe(true);
|
|
517
|
+
await store.updateRun("z-holder", {
|
|
518
|
+
status: "cancelling"
|
|
519
|
+
});
|
|
520
|
+
expect2(await claim("a-waiter")).toBe(false);
|
|
521
|
+
await store.updateRun("z-holder", {
|
|
522
|
+
status: "completed"
|
|
523
|
+
});
|
|
524
|
+
expect2((await store.getRun("z-holder"))?.tags).toEqual([
|
|
525
|
+
"singleton:k",
|
|
526
|
+
"user-tag"
|
|
527
|
+
]);
|
|
528
|
+
expect2(await claim("a-waiter")).toBe(true);
|
|
529
|
+
await store.updateRun("z-holder", {
|
|
530
|
+
status: "running"
|
|
531
|
+
});
|
|
532
|
+
expect2(await claim("z-holder")).toBe(false);
|
|
533
|
+
expect2(await claim("missing")).toBe(false);
|
|
534
|
+
});
|
|
535
|
+
t("singleton denial atomically parks waiters and preserves cancelling/terminal state", async () => {
|
|
536
|
+
await store.createRun(run({
|
|
537
|
+
id: "holder",
|
|
538
|
+
tags: [
|
|
539
|
+
"singleton:k"
|
|
540
|
+
]
|
|
541
|
+
}));
|
|
542
|
+
expect2(await store.tryAdmitSingleton?.("holder", "singleton:k", "checkout", 1)).toBe(true);
|
|
543
|
+
await store.createRun(run({
|
|
544
|
+
id: "waiter",
|
|
545
|
+
tags: [
|
|
546
|
+
"singleton:k"
|
|
547
|
+
]
|
|
548
|
+
}));
|
|
549
|
+
expect2(await peerStore.tryAdmitSingleton?.("waiter", "singleton:k", "checkout", 1, 12e3)).toBe(false);
|
|
550
|
+
expect2(await store.getRun("waiter")).toMatchObject({
|
|
551
|
+
status: "suspended",
|
|
552
|
+
wakeAt: 12e3
|
|
553
|
+
});
|
|
554
|
+
await store.updateRun("waiter", {
|
|
555
|
+
status: "cancelling",
|
|
556
|
+
wakeAt: void 0
|
|
557
|
+
});
|
|
558
|
+
expect2(await peerStore.tryAdmitSingleton?.("waiter", "singleton:k", "checkout", 1, 14e3)).toBe(false);
|
|
559
|
+
expect2(await store.getRun("waiter")).toMatchObject({
|
|
560
|
+
status: "cancelling"
|
|
561
|
+
});
|
|
562
|
+
expect2((await store.getRun("waiter"))?.wakeAt).toBeUndefined();
|
|
563
|
+
await store.updateRun("waiter", {
|
|
564
|
+
status: "cancelled"
|
|
565
|
+
});
|
|
566
|
+
expect2(await peerStore.tryAdmitSingleton?.("waiter", "singleton:k", "checkout", 1, 16e3)).toBe(false);
|
|
567
|
+
expect2((await store.getRun("waiter"))?.status).toBe("cancelled");
|
|
568
|
+
await store.updateRun("holder", {
|
|
569
|
+
status: "completed"
|
|
570
|
+
});
|
|
571
|
+
await store.createRun(run({
|
|
572
|
+
id: "cancelling",
|
|
573
|
+
status: "cancelling",
|
|
574
|
+
tags: [
|
|
575
|
+
"singleton:k"
|
|
576
|
+
]
|
|
577
|
+
}));
|
|
578
|
+
expect2(await store.tryAdmitSingleton?.("cancelling", "singleton:k", "checkout", 1, 18e3)).toBe(false);
|
|
579
|
+
expect2((await store.getRun("cancelling"))?.tags).not.toContain(SINGLETON_ADMITTED_TAG);
|
|
580
|
+
});
|
|
581
|
+
t("competing store instances atomically claim an empty singleton slot", async () => {
|
|
582
|
+
for (const id of [
|
|
583
|
+
"a-first",
|
|
584
|
+
"b-second",
|
|
585
|
+
"c-third"
|
|
586
|
+
]) {
|
|
587
|
+
await store.createRun(run({
|
|
588
|
+
id,
|
|
589
|
+
tags: [
|
|
590
|
+
"singleton:k"
|
|
591
|
+
]
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
const results = await Promise.all([
|
|
595
|
+
peerStore.tryAdmitSingleton?.("b-second", "singleton:k", "checkout", 1),
|
|
596
|
+
store.tryAdmitSingleton?.("a-first", "singleton:k", "checkout", 1),
|
|
597
|
+
peerStore.tryAdmitSingleton?.("c-third", "singleton:k", "checkout", 1)
|
|
598
|
+
]);
|
|
599
|
+
expect2(results).toEqual([
|
|
600
|
+
false,
|
|
601
|
+
true,
|
|
602
|
+
false
|
|
603
|
+
]);
|
|
604
|
+
expect2(await peerStore.tryAdmitSingleton?.("a-first", "singleton:k", "checkout", 1)).toBe(true);
|
|
605
|
+
expect2((await store.getRun("a-first"))?.tags).toContain(SINGLETON_ADMITTED_TAG);
|
|
606
|
+
});
|
|
607
|
+
t("concurrent singleton claims honor limits and independent keys/workflows", async () => {
|
|
608
|
+
const claim = /* @__PURE__ */ __name((id, tag = "singleton:k", workflow = "checkout") => store.tryAdmitSingleton?.(id, tag, workflow, 2), "claim");
|
|
609
|
+
for (const id of [
|
|
610
|
+
"z-holder",
|
|
611
|
+
"y-holder"
|
|
612
|
+
]) {
|
|
613
|
+
await store.createRun(run({
|
|
614
|
+
id,
|
|
615
|
+
tags: [
|
|
616
|
+
"singleton:k"
|
|
617
|
+
]
|
|
618
|
+
}));
|
|
619
|
+
expect2(await claim(id)).toBe(true);
|
|
620
|
+
}
|
|
621
|
+
for (const id of [
|
|
622
|
+
"a-waiter",
|
|
623
|
+
"b-waiter",
|
|
624
|
+
"c-waiter"
|
|
625
|
+
]) {
|
|
626
|
+
await store.createRun(run({
|
|
627
|
+
id,
|
|
628
|
+
status: "pending",
|
|
629
|
+
tags: [
|
|
630
|
+
"singleton:k"
|
|
631
|
+
]
|
|
632
|
+
}));
|
|
633
|
+
}
|
|
634
|
+
expect2(await Promise.all([
|
|
635
|
+
"a-waiter",
|
|
636
|
+
"b-waiter",
|
|
637
|
+
"c-waiter"
|
|
638
|
+
].map((id) => claim(id)))).toEqual([
|
|
639
|
+
false,
|
|
640
|
+
false,
|
|
641
|
+
false
|
|
642
|
+
]);
|
|
643
|
+
await store.updateRun("z-holder", {
|
|
644
|
+
status: "failed"
|
|
645
|
+
});
|
|
646
|
+
expect2(await Promise.all([
|
|
647
|
+
"c-waiter",
|
|
648
|
+
"b-waiter",
|
|
649
|
+
"a-waiter"
|
|
650
|
+
].map((id) => claim(id)))).toEqual([
|
|
651
|
+
false,
|
|
652
|
+
false,
|
|
653
|
+
true
|
|
654
|
+
]);
|
|
655
|
+
const active = await store.listRuns({
|
|
656
|
+
workflow: "checkout",
|
|
657
|
+
statuses: [
|
|
658
|
+
"running",
|
|
659
|
+
"pending"
|
|
660
|
+
]
|
|
661
|
+
});
|
|
662
|
+
expect2(active.filter((r) => r.tags?.includes(SINGLETON_ADMITTED_TAG))).toHaveLength(2);
|
|
663
|
+
await store.createRun(run({
|
|
664
|
+
id: "other-key",
|
|
665
|
+
tags: [
|
|
666
|
+
"singleton:other"
|
|
667
|
+
]
|
|
668
|
+
}));
|
|
669
|
+
expect2(await claim("other-key", "singleton:other")).toBe(true);
|
|
670
|
+
await store.createRun(run({
|
|
671
|
+
id: "other-workflow",
|
|
672
|
+
workflow: "other",
|
|
673
|
+
tags: [
|
|
674
|
+
"singleton:k"
|
|
675
|
+
]
|
|
676
|
+
}));
|
|
677
|
+
expect2(await claim("other-workflow", "singleton:k", "other")).toBe(true);
|
|
678
|
+
});
|
|
486
679
|
t("creates a run and reads it back with its JSON input", async () => {
|
|
487
680
|
await store.createRun(run());
|
|
488
681
|
const loaded = await store.getRun("r1");
|
|
@@ -856,6 +1049,42 @@ function runStateStoreContract(name, makeStore) {
|
|
|
856
1049
|
});
|
|
857
1050
|
expect2((await store.getRun("r1"))?.awaitingDecisionTaskId).toBeUndefined();
|
|
858
1051
|
});
|
|
1052
|
+
t("saveCheckpointIfPending writes over a pending row only", async () => {
|
|
1053
|
+
if (!store.saveCheckpointIfPending) return;
|
|
1054
|
+
await store.createRun(run());
|
|
1055
|
+
const pending = checkpoint({
|
|
1056
|
+
status: "pending",
|
|
1057
|
+
output: void 0
|
|
1058
|
+
});
|
|
1059
|
+
await store.saveCheckpoint(pending);
|
|
1060
|
+
expect2(await store.saveCheckpointIfPending({
|
|
1061
|
+
...pending,
|
|
1062
|
+
wakeAt: 5e3
|
|
1063
|
+
})).toBe(true);
|
|
1064
|
+
expect2((await store.getCheckpoint("r1", 0))?.wakeAt).toBe(5e3);
|
|
1065
|
+
const done = {
|
|
1066
|
+
...pending,
|
|
1067
|
+
status: "completed",
|
|
1068
|
+
output: {
|
|
1069
|
+
ok: true
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
expect2(await store.saveCheckpointIfPending(done)).toBe(true);
|
|
1073
|
+
expect2(await store.saveCheckpointIfPending({
|
|
1074
|
+
...pending,
|
|
1075
|
+
wakeAt: 9e3
|
|
1076
|
+
})).toBe(false);
|
|
1077
|
+
const after = await store.getCheckpoint("r1", 0);
|
|
1078
|
+
expect2(after?.status).toBe("completed");
|
|
1079
|
+
expect2(after?.output).toEqual({
|
|
1080
|
+
ok: true
|
|
1081
|
+
});
|
|
1082
|
+
expect2(await store.saveCheckpointIfPending({
|
|
1083
|
+
...pending,
|
|
1084
|
+
seq: 9
|
|
1085
|
+
})).toBe(false);
|
|
1086
|
+
expect2(await store.getCheckpoint("r1", 9)).toBeNull();
|
|
1087
|
+
});
|
|
859
1088
|
t("upserts checkpoints, reads them by (runId, seq), and lists them ordered by seq", async () => {
|
|
860
1089
|
await store.createRun(run());
|
|
861
1090
|
await store.saveCheckpoint(checkpoint());
|
|
@@ -2439,6 +2668,184 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2439
2668
|
expect2((await store.takeSignalWaiter("child:r.child.0"))?.parallelGroup).toBe("gather:0");
|
|
2440
2669
|
expect2((await store.takeSignalWaiter("child:r.child.9"))?.parallelGroup).toBeUndefined();
|
|
2441
2670
|
});
|
|
2671
|
+
t("registerChildWaits writes every waiter and placeholder, and upserts on repeat", async () => {
|
|
2672
|
+
expect2(store.registerChildWaits).toBeTypeOf("function");
|
|
2673
|
+
await store.createRun(run({
|
|
2674
|
+
id: "p"
|
|
2675
|
+
}));
|
|
2676
|
+
const entry = /* @__PURE__ */ __name((i, status) => ({
|
|
2677
|
+
waiter: {
|
|
2678
|
+
token: `child:p.all.0.${i}`,
|
|
2679
|
+
runId: "p",
|
|
2680
|
+
seq: i,
|
|
2681
|
+
parallelGroup: "all:0"
|
|
2682
|
+
},
|
|
2683
|
+
checkpoint: checkpoint({
|
|
2684
|
+
runId: "p",
|
|
2685
|
+
seq: i,
|
|
2686
|
+
stepId: `p:${i}`,
|
|
2687
|
+
name: `signal:child:p.all.0.${i}`,
|
|
2688
|
+
kind: "signal",
|
|
2689
|
+
status,
|
|
2690
|
+
parallelGroup: "all:0"
|
|
2691
|
+
})
|
|
2692
|
+
}), "entry");
|
|
2693
|
+
const entries = Array.from({
|
|
2694
|
+
length: 250
|
|
2695
|
+
}, (_, i) => entry(i, "running"));
|
|
2696
|
+
await store.registerChildWaits?.(entries);
|
|
2697
|
+
const waiters = await store.listSignalWaiters("child:p.all.0.");
|
|
2698
|
+
expect2(waiters).toHaveLength(250);
|
|
2699
|
+
expect2(waiters.find((w) => w.token === "child:p.all.0.7")).toEqual(entries[7]?.waiter);
|
|
2700
|
+
const cps = await store.listCheckpoints("p");
|
|
2701
|
+
expect2(cps).toHaveLength(250);
|
|
2702
|
+
expect2(cps[7]).toMatchObject({
|
|
2703
|
+
name: "signal:child:p.all.0.7",
|
|
2704
|
+
status: "running"
|
|
2705
|
+
});
|
|
2706
|
+
expect2(cps[7]?.parallelGroup).toBe("all:0");
|
|
2707
|
+
await store.registerChildWaits?.([
|
|
2708
|
+
entry(7, "completed")
|
|
2709
|
+
]);
|
|
2710
|
+
expect2((await store.getCheckpoint("p", 7))?.status).toBe("completed");
|
|
2711
|
+
expect2(await store.listCheckpoints("p")).toHaveLength(250);
|
|
2712
|
+
await expect2(store.registerChildWaits?.([])).resolves.toBeUndefined();
|
|
2713
|
+
});
|
|
2714
|
+
t("compactCheckpoints replaces a seq range with one checkpoint, atomically", async () => {
|
|
2715
|
+
expect2(store.compactCheckpoints).toBeTypeOf("function");
|
|
2716
|
+
await store.createRun(run({
|
|
2717
|
+
id: "r"
|
|
2718
|
+
}));
|
|
2719
|
+
await store.createRun(run({
|
|
2720
|
+
id: "other"
|
|
2721
|
+
}));
|
|
2722
|
+
for (let seq = 0; seq < 10; seq += 1) {
|
|
2723
|
+
await store.saveCheckpoint(checkpoint({
|
|
2724
|
+
runId: "r",
|
|
2725
|
+
seq,
|
|
2726
|
+
stepId: `r:${seq}`
|
|
2727
|
+
}));
|
|
2728
|
+
}
|
|
2729
|
+
await store.saveCheckpoint(checkpoint({
|
|
2730
|
+
runId: "other",
|
|
2731
|
+
seq: 3,
|
|
2732
|
+
stepId: "other:3"
|
|
2733
|
+
}));
|
|
2734
|
+
const into = checkpoint({
|
|
2735
|
+
runId: "r",
|
|
2736
|
+
seq: 2,
|
|
2737
|
+
stepId: "r:2",
|
|
2738
|
+
name: "fan:r.all.2.",
|
|
2739
|
+
kind: "signal",
|
|
2740
|
+
output: {
|
|
2741
|
+
from: 0,
|
|
2742
|
+
values: [
|
|
2743
|
+
{
|
|
2744
|
+
n: 1
|
|
2745
|
+
},
|
|
2746
|
+
"two",
|
|
2747
|
+
null
|
|
2748
|
+
]
|
|
2749
|
+
},
|
|
2750
|
+
parallelGroup: "all:2"
|
|
2751
|
+
});
|
|
2752
|
+
await store.compactCheckpoints?.("r", 2, 7, into);
|
|
2753
|
+
const left = await store.listCheckpoints("r");
|
|
2754
|
+
expect2(left.map((cp) => cp.seq)).toEqual([
|
|
2755
|
+
0,
|
|
2756
|
+
1,
|
|
2757
|
+
2,
|
|
2758
|
+
8,
|
|
2759
|
+
9
|
|
2760
|
+
]);
|
|
2761
|
+
expect2(left[2]).toMatchObject({
|
|
2762
|
+
name: "fan:r.all.2.",
|
|
2763
|
+
parallelGroup: "all:2"
|
|
2764
|
+
});
|
|
2765
|
+
expect2(left[2]?.output).toEqual({
|
|
2766
|
+
from: 0,
|
|
2767
|
+
values: [
|
|
2768
|
+
{
|
|
2769
|
+
n: 1
|
|
2770
|
+
},
|
|
2771
|
+
"two",
|
|
2772
|
+
null
|
|
2773
|
+
]
|
|
2774
|
+
});
|
|
2775
|
+
expect2(await store.listCheckpoints("other")).toHaveLength(1);
|
|
2776
|
+
});
|
|
2777
|
+
t("listSignalWaitersByRun returns only the given runs' waiters", async () => {
|
|
2778
|
+
expect2(store.listSignalWaitersByRun).toBeTypeOf("function");
|
|
2779
|
+
await store.putSignalWaiter({
|
|
2780
|
+
token: "child:a.all.0.0",
|
|
2781
|
+
runId: "a",
|
|
2782
|
+
seq: 0,
|
|
2783
|
+
parallelGroup: "all:0"
|
|
2784
|
+
});
|
|
2785
|
+
await store.putSignalWaiter({
|
|
2786
|
+
token: "approve-a",
|
|
2787
|
+
runId: "a",
|
|
2788
|
+
seq: 1
|
|
2789
|
+
});
|
|
2790
|
+
await store.putSignalWaiter({
|
|
2791
|
+
token: "approve-b",
|
|
2792
|
+
runId: "b",
|
|
2793
|
+
seq: 0
|
|
2794
|
+
});
|
|
2795
|
+
await store.putSignalWaiter({
|
|
2796
|
+
token: "approve-c",
|
|
2797
|
+
runId: "c",
|
|
2798
|
+
seq: 0
|
|
2799
|
+
});
|
|
2800
|
+
const tokens = /* @__PURE__ */ __name(async (ids2) => (await store.listSignalWaitersByRun?.(ids2) ?? []).map((w) => w.token).sort(), "tokens");
|
|
2801
|
+
expect2(await tokens([
|
|
2802
|
+
"a",
|
|
2803
|
+
"b"
|
|
2804
|
+
])).toEqual([
|
|
2805
|
+
"approve-a",
|
|
2806
|
+
"approve-b",
|
|
2807
|
+
"child:a.all.0.0"
|
|
2808
|
+
]);
|
|
2809
|
+
expect2(await tokens([
|
|
2810
|
+
"missing"
|
|
2811
|
+
])).toEqual([]);
|
|
2812
|
+
expect2(await tokens([])).toEqual([]);
|
|
2813
|
+
expect2((await store.listSignalWaitersByRun?.([
|
|
2814
|
+
"a"
|
|
2815
|
+
]))?.find((w) => w.seq === 0)).toEqual({
|
|
2816
|
+
token: "child:a.all.0.0",
|
|
2817
|
+
runId: "a",
|
|
2818
|
+
seq: 0,
|
|
2819
|
+
parallelGroup: "all:0"
|
|
2820
|
+
});
|
|
2821
|
+
});
|
|
2822
|
+
t("hasSignalWaiter answers by run and exact token prefix", async () => {
|
|
2823
|
+
expect2(store.hasSignalWaiter).toBeTypeOf("function");
|
|
2824
|
+
const has = /* @__PURE__ */ __name((runId, prefix) => store.hasSignalWaiter?.(runId, prefix), "has");
|
|
2825
|
+
await store.putSignalWaiter({
|
|
2826
|
+
token: "child:p_1.all.3.0",
|
|
2827
|
+
runId: "p_1",
|
|
2828
|
+
seq: 3,
|
|
2829
|
+
parallelGroup: "all:3"
|
|
2830
|
+
});
|
|
2831
|
+
await store.putSignalWaiter({
|
|
2832
|
+
token: "child:p_1.all.4.0.all.0.0",
|
|
2833
|
+
runId: "p_1.all.4.0",
|
|
2834
|
+
seq: 0
|
|
2835
|
+
});
|
|
2836
|
+
await store.putSignalWaiter({
|
|
2837
|
+
token: "child:pX1.all.3.0",
|
|
2838
|
+
runId: "p_1",
|
|
2839
|
+
seq: 9
|
|
2840
|
+
});
|
|
2841
|
+
expect2(await has("p_1", "child:p_1.all.3.")).toBe(true);
|
|
2842
|
+
expect2(await has("p_1", "child:p_1.all.4.")).toBe(false);
|
|
2843
|
+
expect2(await has("other", "child:p_1.all.3.")).toBe(false);
|
|
2844
|
+
expect2(await has("p_1", "child:p%.all.3.")).toBe(false);
|
|
2845
|
+
expect2(await has("p_1", "CHILD:p_1.all.3.")).toBe(false);
|
|
2846
|
+
await store.takeSignalWaiter("child:p_1.all.3.0");
|
|
2847
|
+
expect2(await has("p_1", "child:p_1.all.3.")).toBe(false);
|
|
2848
|
+
});
|
|
2442
2849
|
t("removeSignalWaiter deletes only the exact (token, runId, seq) row, never a different run's", async () => {
|
|
2443
2850
|
await store.putSignalWaiter({
|
|
2444
2851
|
token: "approve-1",
|