@dudousxd/nestjs-durable-testing 0.19.1 → 0.21.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/dist/index.d.ts +30 -1
- package/dist/index.js +315 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AdmissionBackend, StateStore, RunStatus, WorkflowEngine, InMemoryStateStore, InMemoryTransport, RunResult, WorkflowRef, StartOptions, WorkflowRun, StepCheckpoint, Transport } from '@dudousxd/nestjs-durable-core';
|
|
2
|
+
export { RUN_HISTORY_FIXTURE_VERSION } from '@dudousxd/nestjs-durable-core';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Builds an {@link AdmissionBackend} bound to the given test clock. Implementations that hold their
|
|
@@ -184,6 +185,34 @@ interface RunHistory {
|
|
|
184
185
|
run: WorkflowRun;
|
|
185
186
|
checkpoints: StepCheckpoint[];
|
|
186
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Capture a run's replayable history (the run row + its full checkpoint timeline) from anything with
|
|
191
|
+
* the read API: a `WorkflowEngine`, or a bare `StateStore` (the CLI's `durable export` uses the
|
|
192
|
+
* store). The fixture half of the {@link assertReplayable} loop: capture a representative run,
|
|
193
|
+
* commit it with {@link serializeRunHistory}, and assert it in CI via {@link parseRunHistory}.
|
|
194
|
+
* Returns `null` for an unknown run.
|
|
195
|
+
*/
|
|
196
|
+
declare function captureHistory(source: {
|
|
197
|
+
getRun(runId: string): Promise<WorkflowRun | null | undefined>;
|
|
198
|
+
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
199
|
+
}, runId: string): Promise<RunHistory | null>;
|
|
200
|
+
/**
|
|
201
|
+
* Serialize a captured history as a pretty-printed JSON fixture (with a trailing newline, so it
|
|
202
|
+
* commits cleanly). Read it back with {@link parseRunHistory}.
|
|
203
|
+
*/
|
|
204
|
+
declare function serializeRunHistory(history: RunHistory): string;
|
|
205
|
+
/**
|
|
206
|
+
* Parse a committed fixture (what {@link serializeRunHistory} / `durable export` writes) back into a
|
|
207
|
+
* {@link RunHistory}, reviving the `Date` fields JSON flattened to ISO strings, so
|
|
208
|
+
* `assertReplayable(register, parseRunHistory(readFileSync('fixture.json', 'utf8')))` just works.
|
|
209
|
+
*
|
|
210
|
+
* Throws on a fixture whose `version` is not {@link RUN_HISTORY_FIXTURE_VERSION}: a fixture written
|
|
211
|
+
* in another format would otherwise replay against the wrong shape. A fixture with NO `version` (a
|
|
212
|
+
* hand-built `{ run, checkpoints }`, the shape these docs showed before the marker existed) is read
|
|
213
|
+
* as version 1.
|
|
214
|
+
*/
|
|
215
|
+
declare function parseRunHistory(json: string): RunHistory;
|
|
187
216
|
/**
|
|
188
217
|
* Replay a recorded run's history against the CURRENT workflow code and throw if they diverged.
|
|
189
218
|
*
|
|
@@ -288,4 +317,4 @@ declare function aggregationFixture(): StepCheckpoint[];
|
|
|
288
317
|
*/
|
|
289
318
|
declare function registerCheckpointAggregationCases(t: (label: string, fn: () => Promise<void>) => void, getStore: () => StateStore, run: (over?: Partial<WorkflowRun>) => WorkflowRun): void;
|
|
290
319
|
|
|
291
|
-
export { type AdmissionBackendFactory, DURABLE_CANONICAL_COLUMNS, type DurableColumnMap, type DurableColumnMismatch, type HandleableTransport, MutableClock, type RunHistory, type StateStoreContext, type StateStoreFactory, StateStoreUnavailableError, type TestEngine, aggregationFixture, assertDurableColumns, assertOutput, assertReplayable, assertRunStatus, assertStepAttempts, assertStepsRan, assertTransportConformance, createTestEngine, failOnce, failTimes, preserveColumnExpectation, recordedSteps, registerCheckpointAggregationCases, runAdmissionBackendContract, runStateStoreContract };
|
|
320
|
+
export { type AdmissionBackendFactory, DURABLE_CANONICAL_COLUMNS, type DurableColumnMap, type DurableColumnMismatch, type HandleableTransport, MutableClock, type RunHistory, type StateStoreContext, type StateStoreFactory, StateStoreUnavailableError, type TestEngine, aggregationFixture, assertDurableColumns, assertOutput, assertReplayable, assertRunStatus, assertStepAttempts, assertStepsRan, assertTransportConformance, captureHistory, createTestEngine, failOnce, failTimes, parseRunHistory, preserveColumnExpectation, recordedSteps, registerCheckpointAggregationCases, runAdmissionBackendContract, runStateStoreContract, serializeRunHistory };
|
package/dist/index.js
CHANGED
|
@@ -375,7 +375,50 @@ function createTestEngine() {
|
|
|
375
375
|
__name(createTestEngine, "createTestEngine");
|
|
376
376
|
|
|
377
377
|
// src/replay.ts
|
|
378
|
-
import { InMemoryStateStore as InMemoryStateStore2, WorkflowEngine as WorkflowEngine2 } from "@dudousxd/nestjs-durable-core";
|
|
378
|
+
import { InMemoryStateStore as InMemoryStateStore2, RUN_HISTORY_FIXTURE_VERSION, WorkflowEngine as WorkflowEngine2 } from "@dudousxd/nestjs-durable-core";
|
|
379
|
+
async function captureHistory(source, runId) {
|
|
380
|
+
const run2 = await source.getRun(runId);
|
|
381
|
+
if (!run2) return null;
|
|
382
|
+
return {
|
|
383
|
+
run: run2,
|
|
384
|
+
checkpoints: await source.listCheckpoints(runId)
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
__name(captureHistory, "captureHistory");
|
|
388
|
+
function serializeRunHistory(history) {
|
|
389
|
+
return `${JSON.stringify({
|
|
390
|
+
version: RUN_HISTORY_FIXTURE_VERSION,
|
|
391
|
+
...history
|
|
392
|
+
}, null, 2)}
|
|
393
|
+
`;
|
|
394
|
+
}
|
|
395
|
+
__name(serializeRunHistory, "serializeRunHistory");
|
|
396
|
+
function parseRunHistory(json) {
|
|
397
|
+
const raw = JSON.parse(json);
|
|
398
|
+
if (!raw || typeof raw !== "object" || !raw.run || !Array.isArray(raw.checkpoints)) {
|
|
399
|
+
throw new Error("not a run history fixture: expected { run, checkpoints }");
|
|
400
|
+
}
|
|
401
|
+
if (raw.version !== void 0 && raw.version !== RUN_HISTORY_FIXTURE_VERSION) {
|
|
402
|
+
throw new Error(`unsupported run history fixture version ${JSON.stringify(raw.version)} (this kit reads version ${RUN_HISTORY_FIXTURE_VERSION}); re-export the fixture or upgrade @dudousxd/nestjs-durable-testing`);
|
|
403
|
+
}
|
|
404
|
+
const date = /* @__PURE__ */ __name((v) => new Date(v), "date");
|
|
405
|
+
const run2 = {
|
|
406
|
+
...raw.run,
|
|
407
|
+
createdAt: date(raw.run.createdAt),
|
|
408
|
+
updatedAt: date(raw.run.updatedAt)
|
|
409
|
+
};
|
|
410
|
+
const checkpoints = raw.checkpoints.map((cp2) => ({
|
|
411
|
+
...cp2,
|
|
412
|
+
enqueuedAt: date(cp2.enqueuedAt),
|
|
413
|
+
startedAt: date(cp2.startedAt),
|
|
414
|
+
finishedAt: date(cp2.finishedAt)
|
|
415
|
+
}));
|
|
416
|
+
return {
|
|
417
|
+
run: run2,
|
|
418
|
+
checkpoints
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
__name(parseRunHistory, "parseRunHistory");
|
|
379
422
|
async function assertReplayable(register, history) {
|
|
380
423
|
const store = new InMemoryStateStore2();
|
|
381
424
|
await store.createRun({
|
|
@@ -399,7 +442,7 @@ async function assertReplayable(register, history) {
|
|
|
399
442
|
__name(assertReplayable, "assertReplayable");
|
|
400
443
|
|
|
401
444
|
// src/state-store-conformance.ts
|
|
402
|
-
import { SINGLETON_ADMITTED_TAG, WorkflowEngine as WorkflowEngine3 } from "@dudousxd/nestjs-durable-core";
|
|
445
|
+
import { SINGLETON_ADMITTED_TAG, SINGLETON_TRACKED_TAG, WorkflowEngine as WorkflowEngine3 } from "@dudousxd/nestjs-durable-core";
|
|
403
446
|
import { beforeEach, describe as describe2, expect as expect3, it as it2 } from "vitest";
|
|
404
447
|
|
|
405
448
|
// src/checkpoint-aggregation-contract.ts
|
|
@@ -932,7 +975,8 @@ function runStateStoreContract(name, makeStore) {
|
|
|
932
975
|
await store.createRun(run({
|
|
933
976
|
id,
|
|
934
977
|
tags: [
|
|
935
|
-
"singleton:k"
|
|
978
|
+
"singleton:k",
|
|
979
|
+
SINGLETON_TRACKED_TAG
|
|
936
980
|
]
|
|
937
981
|
}));
|
|
938
982
|
}
|
|
@@ -1021,6 +1065,134 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1021
1065
|
}));
|
|
1022
1066
|
expect3(await claim("other-workflow", "singleton:k", "other")).toBe(true);
|
|
1023
1067
|
});
|
|
1068
|
+
const created = /* @__PURE__ */ __name((offset) => new Date(at.getTime() + offset * 1e3), "created");
|
|
1069
|
+
t("singleton admission counts an untagged pre-0.73 holder (rolling upgrade)", async () => {
|
|
1070
|
+
const claim = /* @__PURE__ */ __name((id, wakeAt) => store.tryAdmitSingleton?.(id, "singleton:k", "checkout", 1, wakeAt), "claim");
|
|
1071
|
+
await store.createRun(run({
|
|
1072
|
+
id: "legacy",
|
|
1073
|
+
tags: [
|
|
1074
|
+
"singleton:k"
|
|
1075
|
+
],
|
|
1076
|
+
createdAt: created(1)
|
|
1077
|
+
}));
|
|
1078
|
+
await store.createRun(run({
|
|
1079
|
+
id: "new",
|
|
1080
|
+
tags: [
|
|
1081
|
+
"singleton:k",
|
|
1082
|
+
SINGLETON_TRACKED_TAG
|
|
1083
|
+
],
|
|
1084
|
+
createdAt: created(0)
|
|
1085
|
+
}));
|
|
1086
|
+
expect3(await claim("new", 12e3)).toBe(false);
|
|
1087
|
+
expect3(await store.getRun("new")).toMatchObject({
|
|
1088
|
+
status: "suspended",
|
|
1089
|
+
wakeAt: 12e3
|
|
1090
|
+
});
|
|
1091
|
+
expect3((await store.getRun("new"))?.tags).toContain(SINGLETON_TRACKED_TAG);
|
|
1092
|
+
await store.updateRun("legacy", {
|
|
1093
|
+
status: "suspended",
|
|
1094
|
+
wakeAt: 2e4
|
|
1095
|
+
});
|
|
1096
|
+
expect3(await claim("new")).toBe(false);
|
|
1097
|
+
await store.updateRun("legacy", {
|
|
1098
|
+
status: "completed"
|
|
1099
|
+
});
|
|
1100
|
+
expect3(await claim("new")).toBe(true);
|
|
1101
|
+
expect3((await store.getRun("new"))?.tags).toEqual([
|
|
1102
|
+
"singleton:k",
|
|
1103
|
+
SINGLETON_ADMITTED_TAG
|
|
1104
|
+
]);
|
|
1105
|
+
await store.updateRun("new", {
|
|
1106
|
+
status: "completed"
|
|
1107
|
+
});
|
|
1108
|
+
expect3((await store.getRun("new"))?.tags).toEqual([
|
|
1109
|
+
"singleton:k"
|
|
1110
|
+
]);
|
|
1111
|
+
});
|
|
1112
|
+
t("legacy singleton waiters keep their FIFO place across the upgrade", async () => {
|
|
1113
|
+
const claim = /* @__PURE__ */ __name((id) => store.tryAdmitSingleton?.(id, "singleton:k", "checkout", 1), "claim");
|
|
1114
|
+
await store.createRun(run({
|
|
1115
|
+
id: "holder",
|
|
1116
|
+
tags: [
|
|
1117
|
+
"singleton:k"
|
|
1118
|
+
],
|
|
1119
|
+
createdAt: created(0)
|
|
1120
|
+
}));
|
|
1121
|
+
await store.createRun(run({
|
|
1122
|
+
id: "old-waiter",
|
|
1123
|
+
status: "suspended",
|
|
1124
|
+
wakeAt: 5e3,
|
|
1125
|
+
tags: [
|
|
1126
|
+
"singleton:k"
|
|
1127
|
+
],
|
|
1128
|
+
createdAt: created(1)
|
|
1129
|
+
}));
|
|
1130
|
+
await store.createRun(run({
|
|
1131
|
+
id: "new-run",
|
|
1132
|
+
tags: [
|
|
1133
|
+
"singleton:k",
|
|
1134
|
+
SINGLETON_TRACKED_TAG
|
|
1135
|
+
],
|
|
1136
|
+
createdAt: created(2)
|
|
1137
|
+
}));
|
|
1138
|
+
expect3(await claim("new-run")).toBe(false);
|
|
1139
|
+
expect3(await claim("old-waiter")).toBe(false);
|
|
1140
|
+
expect3(await store.tryAdmitSingleton?.("old-waiter", "singleton:k", "checkout", 1, 6e3)).toBe(false);
|
|
1141
|
+
expect3((await store.getRun("old-waiter"))?.tags).toContain(SINGLETON_TRACKED_TAG);
|
|
1142
|
+
await store.updateRun("holder", {
|
|
1143
|
+
status: "completed"
|
|
1144
|
+
});
|
|
1145
|
+
expect3(await claim("new-run")).toBe(false);
|
|
1146
|
+
expect3(await claim("old-waiter")).toBe(true);
|
|
1147
|
+
expect3(await claim("new-run")).toBe(false);
|
|
1148
|
+
await store.updateRun("old-waiter", {
|
|
1149
|
+
status: "completed"
|
|
1150
|
+
});
|
|
1151
|
+
expect3(await claim("new-run")).toBe(true);
|
|
1152
|
+
});
|
|
1153
|
+
t("concurrent claims never over-admit beside an untagged legacy holder", async () => {
|
|
1154
|
+
await store.createRun(run({
|
|
1155
|
+
id: "legacy",
|
|
1156
|
+
tags: [
|
|
1157
|
+
"singleton:k"
|
|
1158
|
+
],
|
|
1159
|
+
createdAt: created(0)
|
|
1160
|
+
}));
|
|
1161
|
+
const ids2 = [
|
|
1162
|
+
"c1",
|
|
1163
|
+
"c2",
|
|
1164
|
+
"c3",
|
|
1165
|
+
"c4"
|
|
1166
|
+
];
|
|
1167
|
+
for (const [i, id] of ids2.entries()) {
|
|
1168
|
+
await store.createRun(run({
|
|
1169
|
+
id,
|
|
1170
|
+
tags: [
|
|
1171
|
+
"singleton:k",
|
|
1172
|
+
SINGLETON_TRACKED_TAG
|
|
1173
|
+
],
|
|
1174
|
+
createdAt: created(i + 1)
|
|
1175
|
+
}));
|
|
1176
|
+
}
|
|
1177
|
+
const results = await Promise.all([
|
|
1178
|
+
...ids2
|
|
1179
|
+
].reverse().map((id, i) => (i % 2 ? peerStore : store).tryAdmitSingleton?.(id, "singleton:k", "checkout", 2)));
|
|
1180
|
+
expect3(results).toEqual([
|
|
1181
|
+
false,
|
|
1182
|
+
false,
|
|
1183
|
+
false,
|
|
1184
|
+
true
|
|
1185
|
+
]);
|
|
1186
|
+
const active = await store.listRuns({
|
|
1187
|
+
workflow: "checkout",
|
|
1188
|
+
statuses: [
|
|
1189
|
+
"running"
|
|
1190
|
+
]
|
|
1191
|
+
});
|
|
1192
|
+
expect3(active.filter((r) => r.tags?.includes(SINGLETON_ADMITTED_TAG)).map((r) => r.id)).toEqual([
|
|
1193
|
+
"c1"
|
|
1194
|
+
]);
|
|
1195
|
+
});
|
|
1024
1196
|
t("creates a run and reads it back with its JSON input", async () => {
|
|
1025
1197
|
await store.createRun(run());
|
|
1026
1198
|
const loaded = await store.getRun("r1");
|
|
@@ -1501,40 +1673,40 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1501
1673
|
t("listOrphanedRuns returns incomplete runs with a free or expired lease, oldest first", async () => {
|
|
1502
1674
|
expect3(store.listOrphanedRuns).toBeDefined();
|
|
1503
1675
|
if (!store.listOrphanedRuns) return;
|
|
1504
|
-
const
|
|
1676
|
+
const created2 = /* @__PURE__ */ __name((offset) => new Date(at.getTime() + offset * 1e3), "created");
|
|
1505
1677
|
await store.createRun(run({
|
|
1506
1678
|
id: "held",
|
|
1507
|
-
createdAt:
|
|
1679
|
+
createdAt: created2(1)
|
|
1508
1680
|
}));
|
|
1509
1681
|
expect3(await store.tryLockRun("held", "live", NOW + 3e4, NOW)).toBe(true);
|
|
1510
1682
|
await store.createRun(run({
|
|
1511
1683
|
id: "expired",
|
|
1512
|
-
createdAt:
|
|
1684
|
+
createdAt: created2(3)
|
|
1513
1685
|
}));
|
|
1514
1686
|
expect3(await store.tryLockRun("expired", "dead", NOW, NOW - 1)).toBe(true);
|
|
1515
1687
|
await store.createRun(run({
|
|
1516
1688
|
id: "free",
|
|
1517
|
-
createdAt:
|
|
1689
|
+
createdAt: created2(2)
|
|
1518
1690
|
}));
|
|
1519
1691
|
await store.createRun(run({
|
|
1520
1692
|
id: "compensating",
|
|
1521
1693
|
status: "cancelling",
|
|
1522
|
-
createdAt:
|
|
1694
|
+
createdAt: created2(4)
|
|
1523
1695
|
}));
|
|
1524
1696
|
await store.createRun(run({
|
|
1525
1697
|
id: "parked",
|
|
1526
1698
|
status: "suspended",
|
|
1527
|
-
createdAt:
|
|
1699
|
+
createdAt: created2(0)
|
|
1528
1700
|
}));
|
|
1529
1701
|
await store.createRun(run({
|
|
1530
1702
|
id: "done",
|
|
1531
1703
|
status: "completed",
|
|
1532
|
-
createdAt:
|
|
1704
|
+
createdAt: created2(0)
|
|
1533
1705
|
}));
|
|
1534
1706
|
await store.createRun(run({
|
|
1535
1707
|
id: "tenant",
|
|
1536
1708
|
namespace: "beta",
|
|
1537
|
-
createdAt:
|
|
1709
|
+
createdAt: created2(5)
|
|
1538
1710
|
}));
|
|
1539
1711
|
expect3((await store.listOrphanedRuns(NOW, 10)).map((r) => r.id)).toEqual([
|
|
1540
1712
|
"free",
|
|
@@ -1552,6 +1724,50 @@ function runStateStoreContract(name, makeStore) {
|
|
|
1552
1724
|
expect3((await store.listOrphanedRuns(NOW + 3e4, 10)).map((r) => r.id)).toContain("held");
|
|
1553
1725
|
expect3(await store.listOrphanedRuns(NOW, 0)).toEqual([]);
|
|
1554
1726
|
});
|
|
1727
|
+
t("listOrphanedRuns also returns the owner's live leases taken before its cutoff", async () => {
|
|
1728
|
+
if (!store.listOrphanedRuns) return;
|
|
1729
|
+
const created2 = /* @__PURE__ */ __name((offset) => new Date(at.getTime() + offset * 1e3), "created");
|
|
1730
|
+
await store.createRun(run({
|
|
1731
|
+
id: "own-old",
|
|
1732
|
+
createdAt: created2(1)
|
|
1733
|
+
}));
|
|
1734
|
+
expect3(await store.tryLockRun("own-old", "pod-a", NOW + 1e4, NOW)).toBe(true);
|
|
1735
|
+
await store.createRun(run({
|
|
1736
|
+
id: "own-new",
|
|
1737
|
+
createdAt: created2(2)
|
|
1738
|
+
}));
|
|
1739
|
+
expect3(await store.tryLockRun("own-new", "pod-a", NOW + 3e4, NOW)).toBe(true);
|
|
1740
|
+
await store.createRun(run({
|
|
1741
|
+
id: "foreign",
|
|
1742
|
+
createdAt: created2(3)
|
|
1743
|
+
}));
|
|
1744
|
+
expect3(await store.tryLockRun("foreign", "pod-b", NOW + 1e4, NOW)).toBe(true);
|
|
1745
|
+
await store.createRun(run({
|
|
1746
|
+
id: "own-parked",
|
|
1747
|
+
status: "suspended",
|
|
1748
|
+
createdAt: created2(4)
|
|
1749
|
+
}));
|
|
1750
|
+
expect3(await store.tryLockRun("own-parked", "pod-a", NOW + 1e4, NOW)).toBe(true);
|
|
1751
|
+
await store.createRun(run({
|
|
1752
|
+
id: "free",
|
|
1753
|
+
createdAt: created2(5)
|
|
1754
|
+
}));
|
|
1755
|
+
const ownedBy = {
|
|
1756
|
+
owner: "pod-a",
|
|
1757
|
+
lockedBefore: NOW + 3e4
|
|
1758
|
+
};
|
|
1759
|
+
expect3((await store.listOrphanedRuns(NOW, 10)).map((r) => r.id)).toEqual([
|
|
1760
|
+
"free"
|
|
1761
|
+
]);
|
|
1762
|
+
expect3((await store.listOrphanedRuns(NOW, 10, void 0, ownedBy)).map((r) => r.id)).toEqual([
|
|
1763
|
+
"own-old",
|
|
1764
|
+
"free"
|
|
1765
|
+
]);
|
|
1766
|
+
expect3((await store.listOrphanedRuns(NOW, 1, void 0, ownedBy)).map((r) => r.id)).toEqual([
|
|
1767
|
+
"own-old"
|
|
1768
|
+
]);
|
|
1769
|
+
expect3((await store.listOrphanedRuns(NOW, 10, "beta", ownedBy)).map((r) => r.id)).toEqual([]);
|
|
1770
|
+
});
|
|
1555
1771
|
t("updateRunIf writes only over an expected status", async () => {
|
|
1556
1772
|
expect3(store.updateRunIf).toBeDefined();
|
|
1557
1773
|
if (!store.updateRunIf) return;
|
|
@@ -2141,6 +2357,18 @@ function runStateStoreContract(name, makeStore) {
|
|
|
2141
2357
|
await store.releaseRunLock("r1");
|
|
2142
2358
|
expect3(await store.tryLockRun("r1", "C", 9e3, 2600)).toBe(true);
|
|
2143
2359
|
});
|
|
2360
|
+
t("releaseRunLock with an owner clears only a lease that owner still holds", async () => {
|
|
2361
|
+
await store.createRun(run({
|
|
2362
|
+
id: "r1"
|
|
2363
|
+
}));
|
|
2364
|
+
expect3(await store.tryLockRun("r1", "A", 2e3, 1e3)).toBe(true);
|
|
2365
|
+
expect3(await store.tryLockRun("r1", "B", 4e3, 2500)).toBe(true);
|
|
2366
|
+
await store.releaseRunLock("r1", "A");
|
|
2367
|
+
expect3(await store.tryLockRun("r1", "C", 9e3, 3e3)).toBe(false);
|
|
2368
|
+
expect3((await store.getRun("r1"))?.lockedBy).toBe("B");
|
|
2369
|
+
await store.releaseRunLock("r1", "B");
|
|
2370
|
+
expect3(await store.tryLockRun("r1", "C", 9e3, 3e3)).toBe(true);
|
|
2371
|
+
});
|
|
2144
2372
|
t("renewRunLock only succeeds for the current owner", async () => {
|
|
2145
2373
|
await store.createRun(run({
|
|
2146
2374
|
id: "r1"
|
|
@@ -3894,6 +4122,77 @@ function runStateStoreContract(name, makeStore) {
|
|
|
3894
4122
|
})).rejects.toThrow(/vwf@9 is not registered/);
|
|
3895
4123
|
expect3(await store.getRun("ghost-run")).toBeNull();
|
|
3896
4124
|
});
|
|
4125
|
+
t("times out every unfinished run of its workflow and namespace, except one compensating", async () => {
|
|
4126
|
+
const engine = new WorkflowEngine3({
|
|
4127
|
+
store,
|
|
4128
|
+
namespace: "alpha"
|
|
4129
|
+
});
|
|
4130
|
+
engine.register("t-capped", "1", async () => void 0, {
|
|
4131
|
+
executionTimeout: "1h"
|
|
4132
|
+
});
|
|
4133
|
+
const old = {
|
|
4134
|
+
workflow: "t-capped",
|
|
4135
|
+
namespace: "alpha",
|
|
4136
|
+
createdAt: /* @__PURE__ */ new Date(1e3)
|
|
4137
|
+
};
|
|
4138
|
+
await store.createRun(run({
|
|
4139
|
+
...old,
|
|
4140
|
+
id: "tc-pending",
|
|
4141
|
+
status: "pending"
|
|
4142
|
+
}));
|
|
4143
|
+
await store.createRun(run({
|
|
4144
|
+
...old,
|
|
4145
|
+
id: "tc-running",
|
|
4146
|
+
status: "running"
|
|
4147
|
+
}));
|
|
4148
|
+
await store.createRun(run({
|
|
4149
|
+
...old,
|
|
4150
|
+
id: "tc-suspended",
|
|
4151
|
+
status: "suspended"
|
|
4152
|
+
}));
|
|
4153
|
+
await store.createRun(run({
|
|
4154
|
+
...old,
|
|
4155
|
+
id: "tc-blocked",
|
|
4156
|
+
status: "blocked"
|
|
4157
|
+
}));
|
|
4158
|
+
await store.createRun(run({
|
|
4159
|
+
...old,
|
|
4160
|
+
id: "tc-cancelling",
|
|
4161
|
+
status: "cancelling"
|
|
4162
|
+
}));
|
|
4163
|
+
await store.createRun(run({
|
|
4164
|
+
...old,
|
|
4165
|
+
id: "tc-done",
|
|
4166
|
+
status: "completed"
|
|
4167
|
+
}));
|
|
4168
|
+
await store.createRun(run({
|
|
4169
|
+
...old,
|
|
4170
|
+
id: "tc-other-tenant",
|
|
4171
|
+
status: "pending",
|
|
4172
|
+
namespace: "beta"
|
|
4173
|
+
}));
|
|
4174
|
+
await store.createRun(run({
|
|
4175
|
+
...old,
|
|
4176
|
+
id: "tc-other-workflow",
|
|
4177
|
+
status: "pending",
|
|
4178
|
+
workflow: "t-uncapped"
|
|
4179
|
+
}));
|
|
4180
|
+
await engine.sweepTimeouts(1e3 + 37e5);
|
|
4181
|
+
for (const id of [
|
|
4182
|
+
"tc-pending",
|
|
4183
|
+
"tc-running",
|
|
4184
|
+
"tc-suspended",
|
|
4185
|
+
"tc-blocked"
|
|
4186
|
+
]) {
|
|
4187
|
+
const swept = await store.getRun(id);
|
|
4188
|
+
expect3(swept?.status, id).toBe("cancelled");
|
|
4189
|
+
expect3(swept?.error?.code, id).toBe("execution_timeout");
|
|
4190
|
+
}
|
|
4191
|
+
expect3((await store.getRun("tc-cancelling"))?.status).toBe("cancelling");
|
|
4192
|
+
expect3((await store.getRun("tc-done"))?.status).toBe("completed");
|
|
4193
|
+
expect3((await store.getRun("tc-other-tenant"))?.status).toBe("pending");
|
|
4194
|
+
expect3((await store.getRun("tc-other-workflow"))?.status).toBe("pending");
|
|
4195
|
+
});
|
|
3897
4196
|
t("cancels a timed-out run\u2019s live child subtree, not just the run itself", async () => {
|
|
3898
4197
|
const engine = new WorkflowEngine3({
|
|
3899
4198
|
store
|
|
@@ -3995,6 +4294,7 @@ __name(assertTransportConformance, "assertTransportConformance");
|
|
|
3995
4294
|
export {
|
|
3996
4295
|
DURABLE_CANONICAL_COLUMNS,
|
|
3997
4296
|
MutableClock,
|
|
4297
|
+
RUN_HISTORY_FIXTURE_VERSION,
|
|
3998
4298
|
StateStoreUnavailableError,
|
|
3999
4299
|
aggregationFixture,
|
|
4000
4300
|
assertDurableColumns,
|
|
@@ -4004,13 +4304,16 @@ export {
|
|
|
4004
4304
|
assertStepAttempts,
|
|
4005
4305
|
assertStepsRan,
|
|
4006
4306
|
assertTransportConformance,
|
|
4307
|
+
captureHistory,
|
|
4007
4308
|
createTestEngine,
|
|
4008
4309
|
failOnce,
|
|
4009
4310
|
failTimes,
|
|
4311
|
+
parseRunHistory,
|
|
4010
4312
|
preserveColumnExpectation,
|
|
4011
4313
|
recordedSteps,
|
|
4012
4314
|
registerCheckpointAggregationCases,
|
|
4013
4315
|
runAdmissionBackendContract,
|
|
4014
|
-
runStateStoreContract
|
|
4316
|
+
runStateStoreContract,
|
|
4317
|
+
serializeRunHistory
|
|
4015
4318
|
};
|
|
4016
4319
|
//# sourceMappingURL=index.js.map
|