openworkflow 0.4.1 → 0.6.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/README.md +43 -345
- package/dist/backend-test/backend.testsuite.d.ts +20 -0
- package/dist/backend-test/backend.testsuite.d.ts.map +1 -0
- package/dist/{core → backend-test}/backend.testsuite.js +191 -59
- package/dist/backend-test/index.d.ts +2 -0
- package/dist/backend-test/index.d.ts.map +1 -0
- package/dist/backend-test/index.js +1 -0
- package/dist/{core/backend.d.ts → backend.d.ts} +7 -5
- package/dist/backend.d.ts.map +1 -0
- package/dist/{core/backend.js → backend.js} +0 -1
- package/dist/backend.testsuite.d.ts +20 -0
- package/dist/backend.testsuite.d.ts.map +1 -0
- package/dist/{core/backend-test-suite.js → backend.testsuite.js} +301 -171
- package/dist/bin/openworkflow.d.ts +3 -0
- package/dist/bin/openworkflow.d.ts.map +1 -0
- package/dist/bin/openworkflow.js +43 -0
- package/dist/chaos.test.d.ts +2 -0
- package/dist/chaos.test.d.ts.map +1 -0
- package/dist/chaos.test.js +88 -0
- package/dist/client.d.ts +141 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/{sdk/sdk.js → client.js} +43 -71
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/{sdk/sdk.test.js → client.test.js} +130 -14
- package/dist/core/duration.d.ts +4 -2
- package/dist/core/duration.d.ts.map +1 -1
- package/dist/core/duration.js +3 -2
- package/dist/core/duration.test.js +0 -1
- package/dist/core/error.d.ts +14 -0
- package/dist/core/error.d.ts.map +1 -0
- package/dist/core/error.js +17 -0
- package/dist/core/error.test.d.ts +2 -0
- package/dist/core/error.test.d.ts.map +1 -0
- package/dist/core/error.test.js +60 -0
- package/dist/core/json.js +0 -1
- package/dist/core/result.d.ts +14 -4
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +10 -1
- package/dist/core/result.test.js +2 -2
- package/dist/core/retry.d.ts +0 -9
- package/dist/core/retry.d.ts.map +1 -1
- package/dist/core/retry.js +0 -15
- package/dist/core/schema.js +0 -1
- package/dist/core/step.d.ts +1 -32
- package/dist/core/step.d.ts.map +1 -1
- package/dist/core/step.js +0 -36
- package/dist/core/step.test.js +1 -75
- package/dist/core/workflow.d.ts +2 -47
- package/dist/core/workflow.d.ts.map +1 -1
- package/dist/core/workflow.js +0 -45
- package/dist/core/workflow.test.js +1 -104
- package/dist/driver.d.ts +116 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +1 -0
- package/dist/{execution/execution.d.ts → execution.d.ts} +4 -26
- package/dist/execution.d.ts.map +1 -0
- package/dist/{execution/execution.js → execution.js} +4 -5
- package/dist/execution.test.d.ts.map +1 -0
- package/dist/{execution/execution.test.js → execution.test.js} +4 -5
- package/dist/factory.d.ts +74 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +72 -0
- package/dist/index.d.ts +6 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -5
- package/dist/internal.d.ts +7 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +2 -0
- package/dist/node-sqlite/backend.d.ts +52 -0
- package/dist/node-sqlite/backend.d.ts.map +1 -0
- package/dist/node-sqlite/backend.js +673 -0
- package/dist/node-sqlite/index.d.ts +11 -0
- package/dist/node-sqlite/index.d.ts.map +1 -0
- package/dist/node-sqlite/index.js +7 -0
- package/dist/node-sqlite/sqlite.d.ts +60 -0
- package/dist/node-sqlite/sqlite.d.ts.map +1 -0
- package/dist/{backend-sqlite → node-sqlite}/sqlite.js +20 -3
- package/dist/postgres/backend.d.ts +44 -0
- package/dist/postgres/backend.d.ts.map +1 -0
- package/dist/postgres/backend.js +534 -0
- package/dist/postgres/backend.test.d.ts +2 -0
- package/dist/postgres/backend.test.d.ts.map +1 -0
- package/dist/postgres/backend.test.js +19 -0
- package/dist/postgres/driver.d.ts +81 -0
- package/dist/postgres/driver.d.ts.map +1 -0
- package/dist/postgres/driver.js +63 -0
- package/dist/postgres/index.d.ts +11 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +7 -0
- package/dist/postgres/internal.d.ts +2 -0
- package/dist/postgres/internal.d.ts.map +1 -0
- package/dist/postgres/internal.js +1 -0
- package/dist/postgres/postgres.d.ts +42 -0
- package/dist/postgres/postgres.d.ts.map +1 -0
- package/dist/postgres/postgres.js +233 -0
- package/dist/postgres/postgres.test.d.ts +2 -0
- package/dist/postgres/postgres.test.d.ts.map +1 -0
- package/dist/postgres/postgres.test.js +45 -0
- package/dist/postgres/scripts/db-migrate.d.ts +2 -0
- package/dist/postgres/scripts/db-migrate.d.ts.map +1 -0
- package/dist/postgres/scripts/db-migrate.js +4 -0
- package/dist/postgres/scripts/db-reset.d.ts +2 -0
- package/dist/postgres/scripts/db-reset.d.ts.map +1 -0
- package/dist/postgres/scripts/db-reset.js +5 -0
- package/dist/postgres/scripts/squawk.d.ts +2 -0
- package/dist/postgres/scripts/squawk.d.ts.map +1 -0
- package/dist/postgres/scripts/squawk.js +16 -0
- package/dist/postgres/vitest.global-setup.d.ts +3 -0
- package/dist/postgres/vitest.global-setup.d.ts.map +1 -0
- package/dist/postgres/vitest.global-setup.js +7 -0
- package/dist/postgres.d.ts +2 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +1 -0
- package/dist/registry.d.ts +27 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +48 -0
- package/dist/registry.test.d.ts +2 -0
- package/dist/registry.test.d.ts.map +1 -0
- package/dist/registry.test.js +109 -0
- package/dist/{backend-sqlite → sqlite}/backend.d.ts +8 -4
- package/dist/sqlite/backend.d.ts.map +1 -0
- package/dist/{backend-sqlite → sqlite}/backend.js +35 -9
- package/dist/sqlite/backend.test.d.ts +2 -0
- package/dist/sqlite/backend.test.d.ts.map +1 -0
- package/dist/sqlite/backend.test.js +50 -0
- package/dist/sqlite/driver.d.ts +79 -0
- package/dist/sqlite/driver.d.ts.map +1 -0
- package/dist/sqlite/driver.js +62 -0
- package/dist/sqlite/index.d.ts +13 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +11 -0
- package/dist/sqlite/internal.d.ts +2 -0
- package/dist/sqlite/internal.d.ts.map +1 -0
- package/dist/sqlite/internal.js +1 -0
- package/dist/{backend-sqlite → sqlite}/sqlite.d.ts +18 -2
- package/dist/sqlite/sqlite.d.ts.map +1 -0
- package/dist/sqlite/sqlite.js +246 -0
- package/dist/sqlite/sqlite.test.d.ts +2 -0
- package/dist/sqlite/sqlite.test.d.ts.map +1 -0
- package/dist/sqlite/sqlite.test.js +171 -0
- package/dist/sqlite.d.ts +2 -0
- package/dist/sqlite.d.ts.map +1 -0
- package/dist/sqlite.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/{worker/worker.d.ts → worker.d.ts} +11 -4
- package/dist/worker.d.ts.map +1 -0
- package/dist/{worker/worker.js → worker.js} +20 -11
- package/dist/{worker/worker.test.d.ts.map → worker.test.d.ts.map} +1 -1
- package/dist/{worker/worker.test.js → worker.test.js} +136 -22
- package/dist/workflow.d.ts +60 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +48 -0
- package/dist/workflow.test.d.ts +2 -0
- package/dist/workflow.test.d.ts.map +1 -0
- package/dist/workflow.test.js +84 -0
- package/package.json +28 -4
- package/dist/backend-sqlite/backend.d.ts.map +0 -1
- package/dist/backend-sqlite/backend.js.map +0 -1
- package/dist/backend-sqlite/index.d.ts +0 -2
- package/dist/backend-sqlite/index.d.ts.map +0 -1
- package/dist/backend-sqlite/index.js +0 -2
- package/dist/backend-sqlite/index.js.map +0 -1
- package/dist/backend-sqlite/sqlite.d.ts.map +0 -1
- package/dist/backend-sqlite/sqlite.js.map +0 -1
- package/dist/config/config.d.ts +0 -102
- package/dist/config/config.d.ts.map +0 -1
- package/dist/config/config.js +0 -29
- package/dist/config/config.js.map +0 -1
- package/dist/config/index.d.ts +0 -3
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -2
- package/dist/config/index.js.map +0 -1
- package/dist/config.d.ts +0 -28
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -41
- package/dist/config.js.map +0 -1
- package/dist/core/backend-test-suite.d.ts +0 -22
- package/dist/core/backend-test-suite.d.ts.map +0 -1
- package/dist/core/backend-test-suite.js.map +0 -1
- package/dist/core/backend.d.ts.map +0 -1
- package/dist/core/backend.js.map +0 -1
- package/dist/core/backend.testsuite.d.ts +0 -21
- package/dist/core/backend.testsuite.d.ts.map +0 -1
- package/dist/core/backend.testsuite.js.map +0 -1
- package/dist/core/duration.js.map +0 -1
- package/dist/core/duration.test.js.map +0 -1
- package/dist/core/json.js.map +0 -1
- package/dist/core/result.js.map +0 -1
- package/dist/core/result.test.js.map +0 -1
- package/dist/core/retry.js.map +0 -1
- package/dist/core/retry.test.d.ts +0 -2
- package/dist/core/retry.test.d.ts.map +0 -1
- package/dist/core/retry.test.js +0 -36
- package/dist/core/retry.test.js.map +0 -1
- package/dist/core/schema.js.map +0 -1
- package/dist/core/step.js.map +0 -1
- package/dist/core/step.test.js.map +0 -1
- package/dist/core/workflow.js.map +0 -1
- package/dist/core/workflow.test.js.map +0 -1
- package/dist/execution/execution.d.ts.map +0 -1
- package/dist/execution/execution.js.map +0 -1
- package/dist/execution/execution.test.d.ts.map +0 -1
- package/dist/execution/execution.test.js.map +0 -1
- package/dist/global.d.ts +0 -62
- package/dist/global.d.ts.map +0 -1
- package/dist/global.js +0 -78
- package/dist/global.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/sdk/sdk.d.ts +0 -182
- package/dist/sdk/sdk.d.ts.map +0 -1
- package/dist/sdk/sdk.js.map +0 -1
- package/dist/sdk/sdk.test.d.ts +0 -2
- package/dist/sdk/sdk.test.d.ts.map +0 -1
- package/dist/sdk/sdk.test.js.map +0 -1
- package/dist/worker/worker.d.ts.map +0 -1
- package/dist/worker/worker.js.map +0 -1
- package/dist/worker/worker.test.js.map +0 -1
- /package/dist/{execution/execution.test.d.ts → execution.test.d.ts} +0 -0
- /package/dist/{worker/worker.test.d.ts → worker.test.d.ts} +0 -0
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
3
3
|
/**
|
|
4
|
-
* Runs the Backend
|
|
5
|
-
*
|
|
6
|
-
* This function wraps all the tests that verify a Backend implementation
|
|
7
|
-
* conforms to the Backend interface contract. It uses the factory to create
|
|
8
|
-
* backend instances and the teardown function to clean them up.
|
|
9
|
-
*
|
|
10
|
-
* @param factory - Creates a new isolated Backend instance
|
|
11
|
-
* @param teardown - Cleans up a Backend instance
|
|
4
|
+
* Runs the Backend test suite.
|
|
5
|
+
* @param options - Test suite options
|
|
12
6
|
*/
|
|
13
|
-
export function testBackend(
|
|
7
|
+
export function testBackend(options) {
|
|
8
|
+
const { setup, teardown } = options;
|
|
14
9
|
describe("Backend", () => {
|
|
15
10
|
let backend;
|
|
16
11
|
beforeAll(async () => {
|
|
17
|
-
backend = await
|
|
12
|
+
backend = await setup();
|
|
18
13
|
});
|
|
19
14
|
afterAll(async () => {
|
|
20
15
|
await teardown(backend);
|
|
@@ -87,77 +82,119 @@ export function testBackend(factory, teardown) {
|
|
|
87
82
|
});
|
|
88
83
|
describe("listWorkflowRuns()", () => {
|
|
89
84
|
test("lists workflow runs ordered by creation time", async () => {
|
|
90
|
-
const
|
|
91
|
-
const first = await createPendingWorkflowRun(
|
|
85
|
+
const backend = await setup();
|
|
86
|
+
const first = await createPendingWorkflowRun(backend);
|
|
92
87
|
await sleep(10); // ensure timestamp difference
|
|
93
|
-
const second = await createPendingWorkflowRun(
|
|
94
|
-
const listed = await
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
const second = await createPendingWorkflowRun(backend);
|
|
89
|
+
const listed = await backend.listWorkflowRuns({});
|
|
90
|
+
const listedIds = listed.data.map((run) => run.id);
|
|
91
|
+
expect(listedIds).toEqual([second.id, first.id]);
|
|
92
|
+
await teardown(backend);
|
|
97
93
|
});
|
|
98
94
|
test("paginates workflow runs", async () => {
|
|
99
|
-
const
|
|
95
|
+
const backend = await setup();
|
|
100
96
|
const runs = [];
|
|
101
97
|
for (let i = 0; i < 5; i++) {
|
|
102
|
-
runs.push(await createPendingWorkflowRun(
|
|
98
|
+
runs.push(await createPendingWorkflowRun(backend));
|
|
103
99
|
await sleep(10);
|
|
104
100
|
}
|
|
105
101
|
// p1
|
|
106
|
-
const page1 = await
|
|
102
|
+
const page1 = await backend.listWorkflowRuns({ limit: 2 });
|
|
107
103
|
expect(page1.data).toHaveLength(2);
|
|
108
|
-
expect(page1.data[0]?.id).toBe(runs[
|
|
109
|
-
expect(page1.data[1]?.id).toBe(runs[
|
|
104
|
+
expect(page1.data[0]?.id).toBe(runs[4]?.id);
|
|
105
|
+
expect(page1.data[1]?.id).toBe(runs[3]?.id);
|
|
110
106
|
expect(page1.pagination.next).not.toBeNull();
|
|
111
107
|
expect(page1.pagination.prev).toBeNull();
|
|
112
108
|
// p2
|
|
113
|
-
const page2 = await
|
|
109
|
+
const page2 = await backend.listWorkflowRuns({
|
|
114
110
|
limit: 2,
|
|
115
111
|
after: page1.pagination.next, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
116
112
|
});
|
|
117
113
|
expect(page2.data).toHaveLength(2);
|
|
118
114
|
expect(page2.data[0]?.id).toBe(runs[2]?.id);
|
|
119
|
-
expect(page2.data[1]?.id).toBe(runs[
|
|
115
|
+
expect(page2.data[1]?.id).toBe(runs[1]?.id);
|
|
120
116
|
expect(page2.pagination.next).not.toBeNull();
|
|
121
117
|
expect(page2.pagination.prev).not.toBeNull();
|
|
122
118
|
// p3
|
|
123
|
-
const page3 = await
|
|
119
|
+
const page3 = await backend.listWorkflowRuns({
|
|
124
120
|
limit: 2,
|
|
125
121
|
after: page2.pagination.next, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
126
122
|
});
|
|
127
123
|
expect(page3.data).toHaveLength(1);
|
|
128
|
-
expect(page3.data[0]?.id).toBe(runs[
|
|
124
|
+
expect(page3.data[0]?.id).toBe(runs[0]?.id);
|
|
129
125
|
expect(page3.pagination.next).toBeNull();
|
|
130
126
|
expect(page3.pagination.prev).not.toBeNull();
|
|
131
127
|
// p2 again
|
|
132
|
-
const page2Back = await
|
|
128
|
+
const page2Back = await backend.listWorkflowRuns({
|
|
133
129
|
limit: 2,
|
|
134
130
|
before: page3.pagination.prev, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
135
131
|
});
|
|
136
132
|
expect(page2Back.data).toHaveLength(2);
|
|
137
133
|
expect(page2Back.data[0]?.id).toBe(runs[2]?.id);
|
|
138
|
-
expect(page2Back.data[1]?.id).toBe(runs[
|
|
134
|
+
expect(page2Back.data[1]?.id).toBe(runs[1]?.id);
|
|
139
135
|
expect(page2Back.pagination.next).toEqual(page2.pagination.next);
|
|
140
136
|
expect(page2Back.pagination.prev).toEqual(page2.pagination.prev);
|
|
141
|
-
await teardown(
|
|
137
|
+
await teardown(backend);
|
|
142
138
|
});
|
|
143
139
|
test("handles empty results", async () => {
|
|
144
|
-
const
|
|
145
|
-
const listed = await
|
|
140
|
+
const backend = await setup();
|
|
141
|
+
const listed = await backend.listWorkflowRuns({});
|
|
146
142
|
expect(listed.data).toHaveLength(0);
|
|
147
143
|
expect(listed.pagination.next).toBeNull();
|
|
148
144
|
expect(listed.pagination.prev).toBeNull();
|
|
149
|
-
await teardown(
|
|
145
|
+
await teardown(backend);
|
|
146
|
+
});
|
|
147
|
+
test("paginates correctly with id as tiebreaker when multiple items have the same created_at timestamp", async () => {
|
|
148
|
+
const backend = await setup();
|
|
149
|
+
const runs = [];
|
|
150
|
+
for (let i = 0; i < 5; i++) {
|
|
151
|
+
runs.push(await createPendingWorkflowRun(backend));
|
|
152
|
+
}
|
|
153
|
+
runs.sort((a, b) => {
|
|
154
|
+
const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();
|
|
155
|
+
if (timeDiff !== 0)
|
|
156
|
+
return timeDiff;
|
|
157
|
+
return b.id.localeCompare(a.id);
|
|
158
|
+
});
|
|
159
|
+
const page1 = await backend.listWorkflowRuns({ limit: 2 });
|
|
160
|
+
expect(page1.data).toHaveLength(2);
|
|
161
|
+
expect(page1.data[0]?.id).toBe(runs[0]?.id);
|
|
162
|
+
expect(page1.data[1]?.id).toBe(runs[1]?.id);
|
|
163
|
+
expect(page1.pagination.next).not.toBeNull();
|
|
164
|
+
const page2 = await backend.listWorkflowRuns({
|
|
165
|
+
limit: 2,
|
|
166
|
+
after: page1.pagination.next, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
167
|
+
});
|
|
168
|
+
expect(page2.data).toHaveLength(2);
|
|
169
|
+
expect(page2.data[0]?.id).toBe(runs[2]?.id);
|
|
170
|
+
expect(page2.data[1]?.id).toBe(runs[3]?.id);
|
|
171
|
+
expect(page2.pagination.next).not.toBeNull();
|
|
172
|
+
const page3 = await backend.listWorkflowRuns({
|
|
173
|
+
limit: 2,
|
|
174
|
+
after: page2.pagination.next, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
175
|
+
});
|
|
176
|
+
expect(page3.data).toHaveLength(1);
|
|
177
|
+
expect(page3.data[0]?.id).toBe(runs[4]?.id);
|
|
178
|
+
expect(page3.pagination.next).toBeNull();
|
|
179
|
+
const page2Back = await backend.listWorkflowRuns({
|
|
180
|
+
limit: 2,
|
|
181
|
+
before: page3.pagination.prev, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
182
|
+
});
|
|
183
|
+
expect(page2Back.data).toHaveLength(2);
|
|
184
|
+
expect(page2Back.data[0]?.id).toBe(runs[2]?.id);
|
|
185
|
+
expect(page2Back.data[1]?.id).toBe(runs[3]?.id);
|
|
186
|
+
await teardown(backend);
|
|
150
187
|
});
|
|
151
188
|
});
|
|
152
189
|
describe("claimWorkflowRun()", () => {
|
|
153
190
|
// because claims involve timing and leases, we create and teardown a new
|
|
154
191
|
// namespaced backend instance for each test
|
|
155
192
|
test("claims workflow runs and respects leases, reclaiming if lease expires", async () => {
|
|
156
|
-
const
|
|
157
|
-
await createPendingWorkflowRun(
|
|
193
|
+
const backend = await setup();
|
|
194
|
+
await createPendingWorkflowRun(backend);
|
|
158
195
|
const firstLeaseMs = 30;
|
|
159
196
|
const firstWorker = randomUUID();
|
|
160
|
-
const claimed = await
|
|
197
|
+
const claimed = await backend.claimWorkflowRun({
|
|
161
198
|
workerId: firstWorker,
|
|
162
199
|
leaseDurationMs: firstLeaseMs,
|
|
163
200
|
});
|
|
@@ -166,13 +203,13 @@ export function testBackend(factory, teardown) {
|
|
|
166
203
|
expect(claimed?.attempts).toBe(1);
|
|
167
204
|
expect(claimed?.startedAt).not.toBeNull();
|
|
168
205
|
const secondWorker = randomUUID();
|
|
169
|
-
const blocked = await
|
|
206
|
+
const blocked = await backend.claimWorkflowRun({
|
|
170
207
|
workerId: secondWorker,
|
|
171
208
|
leaseDurationMs: 10,
|
|
172
209
|
});
|
|
173
210
|
expect(blocked).toBeNull();
|
|
174
|
-
await sleep(firstLeaseMs);
|
|
175
|
-
const reclaimed = await
|
|
211
|
+
await sleep(firstLeaseMs + 5); // small buffer for timing variability
|
|
212
|
+
const reclaimed = await backend.claimWorkflowRun({
|
|
176
213
|
workerId: secondWorker,
|
|
177
214
|
leaseDurationMs: 10,
|
|
178
215
|
});
|
|
@@ -180,12 +217,12 @@ export function testBackend(factory, teardown) {
|
|
|
180
217
|
expect(reclaimed?.attempts).toBe(2);
|
|
181
218
|
expect(reclaimed?.workerId).toBe(secondWorker);
|
|
182
219
|
expect(reclaimed?.startedAt?.getTime()).toBe(claimed?.startedAt?.getTime());
|
|
183
|
-
await teardown(
|
|
220
|
+
await teardown(backend);
|
|
184
221
|
});
|
|
185
222
|
test("prioritizes pending workflow runs over expired running ones", async () => {
|
|
186
|
-
const
|
|
187
|
-
const running = await createPendingWorkflowRun(
|
|
188
|
-
const runningClaim = await
|
|
223
|
+
const backend = await setup();
|
|
224
|
+
const running = await createPendingWorkflowRun(backend);
|
|
225
|
+
const runningClaim = await backend.claimWorkflowRun({
|
|
189
226
|
workerId: "worker-running",
|
|
190
227
|
leaseDurationMs: 5,
|
|
191
228
|
});
|
|
@@ -194,28 +231,28 @@ export function testBackend(factory, teardown) {
|
|
|
194
231
|
expect(runningClaim.id).toBe(running.id);
|
|
195
232
|
await sleep(10); // wait for running's lease to expire
|
|
196
233
|
// pending claimed first, even though running expired
|
|
197
|
-
const pending = await createPendingWorkflowRun(
|
|
198
|
-
const claimedFirst = await
|
|
234
|
+
const pending = await createPendingWorkflowRun(backend);
|
|
235
|
+
const claimedFirst = await backend.claimWorkflowRun({
|
|
199
236
|
workerId: "worker-second",
|
|
200
237
|
leaseDurationMs: 100,
|
|
201
238
|
});
|
|
202
239
|
expect(claimedFirst?.id).toBe(pending.id);
|
|
203
240
|
// running claimed second
|
|
204
|
-
const claimedSecond = await
|
|
241
|
+
const claimedSecond = await backend.claimWorkflowRun({
|
|
205
242
|
workerId: "worker-third",
|
|
206
243
|
leaseDurationMs: 100,
|
|
207
244
|
});
|
|
208
245
|
expect(claimedSecond?.id).toBe(running.id);
|
|
209
|
-
await teardown(
|
|
246
|
+
await teardown(backend);
|
|
210
247
|
});
|
|
211
248
|
test("returns null when no workflow runs are available", async () => {
|
|
212
|
-
const
|
|
213
|
-
const claimed = await
|
|
249
|
+
const backend = await setup();
|
|
250
|
+
const claimed = await backend.claimWorkflowRun({
|
|
214
251
|
workerId: randomUUID(),
|
|
215
252
|
leaseDurationMs: 10,
|
|
216
253
|
});
|
|
217
254
|
expect(claimed).toBeNull();
|
|
218
|
-
await teardown(
|
|
255
|
+
await teardown(backend);
|
|
219
256
|
});
|
|
220
257
|
});
|
|
221
258
|
describe("extendWorkflowRunLease()", () => {
|
|
@@ -262,42 +299,42 @@ export function testBackend(factory, teardown) {
|
|
|
262
299
|
expect(fetched?.status).toBe("sleeping");
|
|
263
300
|
});
|
|
264
301
|
test("fails when trying to sleep a canceled workflow", async () => {
|
|
265
|
-
const
|
|
302
|
+
const backend = await setup();
|
|
266
303
|
// completed run
|
|
267
|
-
let claimed = await createClaimedWorkflowRun(
|
|
268
|
-
await
|
|
304
|
+
let claimed = await createClaimedWorkflowRun(backend);
|
|
305
|
+
await backend.completeWorkflowRun({
|
|
269
306
|
workflowRunId: claimed.id,
|
|
270
307
|
workerId: claimed.workerId ?? "",
|
|
271
308
|
output: null,
|
|
272
309
|
});
|
|
273
|
-
await expect(
|
|
310
|
+
await expect(backend.sleepWorkflowRun({
|
|
274
311
|
workflowRunId: claimed.id,
|
|
275
312
|
workerId: claimed.workerId ?? "",
|
|
276
313
|
availableAt: new Date(Date.now() + 60_000),
|
|
277
314
|
})).rejects.toThrow("Failed to sleep workflow run");
|
|
278
315
|
// failed run
|
|
279
|
-
claimed = await createClaimedWorkflowRun(
|
|
280
|
-
await
|
|
316
|
+
claimed = await createClaimedWorkflowRun(backend);
|
|
317
|
+
await backend.failWorkflowRun({
|
|
281
318
|
workflowRunId: claimed.id,
|
|
282
319
|
workerId: claimed.workerId ?? "",
|
|
283
|
-
error:
|
|
320
|
+
error: { message: "failed" },
|
|
284
321
|
});
|
|
285
|
-
await expect(
|
|
322
|
+
await expect(backend.sleepWorkflowRun({
|
|
286
323
|
workflowRunId: claimed.id,
|
|
287
324
|
workerId: claimed.workerId ?? "",
|
|
288
325
|
availableAt: new Date(Date.now() + 60_000),
|
|
289
326
|
})).rejects.toThrow("Failed to sleep workflow run");
|
|
290
327
|
// canceled run
|
|
291
|
-
claimed = await createClaimedWorkflowRun(
|
|
292
|
-
await
|
|
328
|
+
claimed = await createClaimedWorkflowRun(backend);
|
|
329
|
+
await backend.cancelWorkflowRun({
|
|
293
330
|
workflowRunId: claimed.id,
|
|
294
331
|
});
|
|
295
|
-
await expect(
|
|
332
|
+
await expect(backend.sleepWorkflowRun({
|
|
296
333
|
workflowRunId: claimed.id,
|
|
297
334
|
workerId: claimed.workerId ?? "",
|
|
298
335
|
availableAt: new Date(Date.now() + 60_000),
|
|
299
336
|
})).rejects.toThrow("Failed to sleep workflow run");
|
|
300
|
-
await teardown(
|
|
337
|
+
await teardown(backend);
|
|
301
338
|
});
|
|
302
339
|
});
|
|
303
340
|
describe("completeWorkflowRun()", () => {
|
|
@@ -346,6 +383,7 @@ export function testBackend(factory, teardown) {
|
|
|
346
383
|
expect(failed.output).toBeNull();
|
|
347
384
|
expect(failed.finishedAt).toBeNull();
|
|
348
385
|
expect(failed.workerId).toBeNull();
|
|
386
|
+
expect(failed.startedAt).toBeNull(); // cleared on failure for retry
|
|
349
387
|
expect(failed.availableAt).not.toBeNull();
|
|
350
388
|
if (!failed.availableAt)
|
|
351
389
|
throw new Error("Expected availableAt");
|
|
@@ -353,20 +391,20 @@ export function testBackend(factory, teardown) {
|
|
|
353
391
|
expect(delayMs).toBeGreaterThanOrEqual(900); // ~1s with some tolerance
|
|
354
392
|
expect(delayMs).toBeLessThan(1500);
|
|
355
393
|
});
|
|
356
|
-
test("reschedules with increasing backoff on multiple failures
|
|
394
|
+
test("reschedules with increasing backoff on multiple failures", async () => {
|
|
357
395
|
// this test needs isolated namespace
|
|
358
|
-
const
|
|
359
|
-
await createPendingWorkflowRun(
|
|
396
|
+
const backend = await setup();
|
|
397
|
+
await createPendingWorkflowRun(backend);
|
|
360
398
|
// fail first attempt
|
|
361
399
|
let workerId = randomUUID();
|
|
362
|
-
let claimed = await
|
|
400
|
+
let claimed = await backend.claimWorkflowRun({
|
|
363
401
|
workerId,
|
|
364
402
|
leaseDurationMs: 20,
|
|
365
403
|
});
|
|
366
404
|
if (!claimed)
|
|
367
405
|
throw new Error("Expected workflow run to be claimed");
|
|
368
406
|
expect(claimed.attempts).toBe(1);
|
|
369
|
-
const firstFailed = await
|
|
407
|
+
const firstFailed = await backend.failWorkflowRun({
|
|
370
408
|
workflowRunId: claimed.id,
|
|
371
409
|
workerId,
|
|
372
410
|
error: { message: "first failure" },
|
|
@@ -375,7 +413,7 @@ export function testBackend(factory, teardown) {
|
|
|
375
413
|
await sleep(1100); // wait for first backoff (~1s)
|
|
376
414
|
// fail second attempt
|
|
377
415
|
workerId = randomUUID();
|
|
378
|
-
claimed = await
|
|
416
|
+
claimed = await backend.claimWorkflowRun({
|
|
379
417
|
workerId,
|
|
380
418
|
leaseDurationMs: 20,
|
|
381
419
|
});
|
|
@@ -383,7 +421,7 @@ export function testBackend(factory, teardown) {
|
|
|
383
421
|
throw new Error("Expected workflow run to be claimed");
|
|
384
422
|
expect(claimed.attempts).toBe(2);
|
|
385
423
|
const beforeSecondFail = Date.now();
|
|
386
|
-
const secondFailed = await
|
|
424
|
+
const secondFailed = await backend.failWorkflowRun({
|
|
387
425
|
workflowRunId: claimed.id,
|
|
388
426
|
workerId,
|
|
389
427
|
error: { message: "second failure" },
|
|
@@ -395,7 +433,7 @@ export function testBackend(factory, teardown) {
|
|
|
395
433
|
const delayMs = secondFailed.availableAt.getTime() - beforeSecondFail;
|
|
396
434
|
expect(delayMs).toBeGreaterThanOrEqual(1900); // ~2s with some tolerance
|
|
397
435
|
expect(delayMs).toBeLessThan(2500);
|
|
398
|
-
await teardown(
|
|
436
|
+
await teardown(backend);
|
|
399
437
|
});
|
|
400
438
|
});
|
|
401
439
|
describe("createStepAttempt()", () => {
|
|
@@ -472,6 +510,7 @@ export function testBackend(factory, teardown) {
|
|
|
472
510
|
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion,
|
|
473
511
|
output: { ok: true },
|
|
474
512
|
});
|
|
513
|
+
await sleep(10); // ensure timestamp difference
|
|
475
514
|
const second = await backend.createStepAttempt({
|
|
476
515
|
workflowRunId: claimed.id,
|
|
477
516
|
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
@@ -483,10 +522,8 @@ export function testBackend(factory, teardown) {
|
|
|
483
522
|
const listed = await backend.listStepAttempts({
|
|
484
523
|
workflowRunId: claimed.id,
|
|
485
524
|
});
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
second.stepName,
|
|
489
|
-
]);
|
|
525
|
+
const listedStepNames = listed.data.map((step) => step.stepName);
|
|
526
|
+
expect(listedStepNames).toEqual([first.stepName, second.stepName]);
|
|
490
527
|
});
|
|
491
528
|
test("paginates step attempts", async () => {
|
|
492
529
|
const claimed = await createClaimedWorkflowRun(backend);
|
|
@@ -572,23 +609,6 @@ export function testBackend(factory, teardown) {
|
|
|
572
609
|
expect(listed.pagination.prev).toBeNull();
|
|
573
610
|
});
|
|
574
611
|
});
|
|
575
|
-
describe("getStepAttempt() duplicate", () => {
|
|
576
|
-
test("returns a persisted step attempt", async () => {
|
|
577
|
-
const claimed = await createClaimedWorkflowRun(backend);
|
|
578
|
-
const created = await backend.createStepAttempt({
|
|
579
|
-
workflowRunId: claimed.id,
|
|
580
|
-
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
581
|
-
stepName: randomUUID(),
|
|
582
|
-
kind: "function",
|
|
583
|
-
config: {},
|
|
584
|
-
context: null,
|
|
585
|
-
});
|
|
586
|
-
const got = await backend.getStepAttempt({
|
|
587
|
-
stepAttemptId: created.id,
|
|
588
|
-
});
|
|
589
|
-
expect(got).toEqual(created);
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
612
|
describe("completeStepAttempt()", () => {
|
|
593
613
|
test("marks running step attempts as completed", async () => {
|
|
594
614
|
const claimed = await createClaimedWorkflowRun(backend);
|
|
@@ -619,6 +639,50 @@ export function testBackend(factory, teardown) {
|
|
|
619
639
|
expect(fetched?.error).toBeNull();
|
|
620
640
|
expect(fetched?.finishedAt).not.toBeNull();
|
|
621
641
|
});
|
|
642
|
+
test("throws when workflow is not running", async () => {
|
|
643
|
+
const backend = await setup();
|
|
644
|
+
await createPendingWorkflowRun(backend);
|
|
645
|
+
// create a step attempt by first claiming the workflow
|
|
646
|
+
const claimed = await backend.claimWorkflowRun({
|
|
647
|
+
workerId: randomUUID(),
|
|
648
|
+
leaseDurationMs: 100,
|
|
649
|
+
});
|
|
650
|
+
if (!claimed)
|
|
651
|
+
throw new Error("Failed to claim workflow run");
|
|
652
|
+
const stepAttempt = await backend.createStepAttempt({
|
|
653
|
+
workflowRunId: claimed.id,
|
|
654
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
655
|
+
stepName: randomUUID(),
|
|
656
|
+
kind: "function",
|
|
657
|
+
config: {},
|
|
658
|
+
context: null,
|
|
659
|
+
});
|
|
660
|
+
// complete the workflow so it's no longer running
|
|
661
|
+
await backend.completeWorkflowRun({
|
|
662
|
+
workflowRunId: claimed.id,
|
|
663
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
664
|
+
output: null,
|
|
665
|
+
});
|
|
666
|
+
// try to complete the step attempt
|
|
667
|
+
await expect(backend.completeStepAttempt({
|
|
668
|
+
workflowRunId: claimed.id,
|
|
669
|
+
stepAttemptId: stepAttempt.id,
|
|
670
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
671
|
+
output: { foo: "bar" },
|
|
672
|
+
})).rejects.toThrow("Failed to mark step attempt completed");
|
|
673
|
+
await teardown(backend);
|
|
674
|
+
});
|
|
675
|
+
test("throws when step attempt does not exist", async () => {
|
|
676
|
+
const backend = await setup();
|
|
677
|
+
const claimed = await createClaimedWorkflowRun(backend);
|
|
678
|
+
await expect(backend.completeStepAttempt({
|
|
679
|
+
workflowRunId: claimed.id,
|
|
680
|
+
stepAttemptId: randomUUID(),
|
|
681
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
682
|
+
output: { foo: "bar" },
|
|
683
|
+
})).rejects.toThrow("Failed to mark step attempt completed");
|
|
684
|
+
await teardown(backend);
|
|
685
|
+
});
|
|
622
686
|
});
|
|
623
687
|
describe("failStepAttempt()", () => {
|
|
624
688
|
test("marks running step attempts as failed", async () => {
|
|
@@ -650,6 +714,50 @@ export function testBackend(factory, teardown) {
|
|
|
650
714
|
expect(fetched?.output).toBeNull();
|
|
651
715
|
expect(fetched?.finishedAt).not.toBeNull();
|
|
652
716
|
});
|
|
717
|
+
test("throws when workflow is not running", async () => {
|
|
718
|
+
const backend = await setup();
|
|
719
|
+
await createPendingWorkflowRun(backend);
|
|
720
|
+
// create a step attempt by first claiming the workflow
|
|
721
|
+
const claimed = await backend.claimWorkflowRun({
|
|
722
|
+
workerId: randomUUID(),
|
|
723
|
+
leaseDurationMs: 100,
|
|
724
|
+
});
|
|
725
|
+
if (!claimed)
|
|
726
|
+
throw new Error("Failed to claim workflow run");
|
|
727
|
+
const stepAttempt = await backend.createStepAttempt({
|
|
728
|
+
workflowRunId: claimed.id,
|
|
729
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
730
|
+
stepName: randomUUID(),
|
|
731
|
+
kind: "function",
|
|
732
|
+
config: {},
|
|
733
|
+
context: null,
|
|
734
|
+
});
|
|
735
|
+
// complete the workflow so it's no longer running
|
|
736
|
+
await backend.completeWorkflowRun({
|
|
737
|
+
workflowRunId: claimed.id,
|
|
738
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
739
|
+
output: null,
|
|
740
|
+
});
|
|
741
|
+
// try to fail the step attempt
|
|
742
|
+
await expect(backend.failStepAttempt({
|
|
743
|
+
workflowRunId: claimed.id,
|
|
744
|
+
stepAttemptId: stepAttempt.id,
|
|
745
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
746
|
+
error: { message: "nope" },
|
|
747
|
+
})).rejects.toThrow("Failed to mark step attempt failed");
|
|
748
|
+
await teardown(backend);
|
|
749
|
+
});
|
|
750
|
+
test("throws when step attempt does not exist", async () => {
|
|
751
|
+
const backend = await setup();
|
|
752
|
+
const claimed = await createClaimedWorkflowRun(backend);
|
|
753
|
+
await expect(backend.failStepAttempt({
|
|
754
|
+
workflowRunId: claimed.id,
|
|
755
|
+
stepAttemptId: randomUUID(),
|
|
756
|
+
workerId: claimed.workerId, // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
757
|
+
error: { message: "nope" },
|
|
758
|
+
})).rejects.toThrow("Failed to mark step attempt failed");
|
|
759
|
+
await teardown(backend);
|
|
760
|
+
});
|
|
653
761
|
});
|
|
654
762
|
describe("deadline_at", () => {
|
|
655
763
|
test("creates a workflow run with a deadline", async () => {
|
|
@@ -668,9 +776,9 @@ export function testBackend(factory, teardown) {
|
|
|
668
776
|
expect(created.deadlineAt?.getTime()).toBe(deadline.getTime());
|
|
669
777
|
});
|
|
670
778
|
test("does not claim workflow runs past their deadline", async () => {
|
|
671
|
-
const
|
|
779
|
+
const backend = await setup();
|
|
672
780
|
const pastDeadline = new Date(Date.now() - 1000);
|
|
673
|
-
await
|
|
781
|
+
await backend.createWorkflowRun({
|
|
674
782
|
workflowName: randomUUID(),
|
|
675
783
|
version: null,
|
|
676
784
|
idempotencyKey: null,
|
|
@@ -680,17 +788,17 @@ export function testBackend(factory, teardown) {
|
|
|
680
788
|
availableAt: null,
|
|
681
789
|
deadlineAt: pastDeadline,
|
|
682
790
|
});
|
|
683
|
-
const claimed = await
|
|
791
|
+
const claimed = await backend.claimWorkflowRun({
|
|
684
792
|
workerId: randomUUID(),
|
|
685
793
|
leaseDurationMs: 1000,
|
|
686
794
|
});
|
|
687
795
|
expect(claimed).toBeNull();
|
|
688
|
-
await teardown(
|
|
796
|
+
await teardown(backend);
|
|
689
797
|
});
|
|
690
798
|
test("marks deadline-expired workflow runs as failed when claiming", async () => {
|
|
691
|
-
const
|
|
799
|
+
const backend = await setup();
|
|
692
800
|
const pastDeadline = new Date(Date.now() - 1000);
|
|
693
|
-
const created = await
|
|
801
|
+
const created = await backend.createWorkflowRun({
|
|
694
802
|
workflowName: randomUUID(),
|
|
695
803
|
version: null,
|
|
696
804
|
idempotencyKey: null,
|
|
@@ -701,13 +809,13 @@ export function testBackend(factory, teardown) {
|
|
|
701
809
|
deadlineAt: pastDeadline,
|
|
702
810
|
});
|
|
703
811
|
// attempt to claim triggers deadline check
|
|
704
|
-
const claimed = await
|
|
812
|
+
const claimed = await backend.claimWorkflowRun({
|
|
705
813
|
workerId: randomUUID(),
|
|
706
814
|
leaseDurationMs: 1000,
|
|
707
815
|
});
|
|
708
816
|
expect(claimed).toBeNull();
|
|
709
817
|
// verify it was marked as failed
|
|
710
|
-
const failed = await
|
|
818
|
+
const failed = await backend.getWorkflowRun({
|
|
711
819
|
workflowRunId: created.id,
|
|
712
820
|
});
|
|
713
821
|
expect(failed?.status).toBe("failed");
|
|
@@ -716,12 +824,12 @@ export function testBackend(factory, teardown) {
|
|
|
716
824
|
});
|
|
717
825
|
expect(failed?.finishedAt).not.toBeNull();
|
|
718
826
|
expect(failed?.availableAt).toBeNull();
|
|
719
|
-
await teardown(
|
|
827
|
+
await teardown(backend);
|
|
720
828
|
});
|
|
721
829
|
test("does not reschedule failed workflow runs if next retry would exceed deadline", async () => {
|
|
722
|
-
const
|
|
830
|
+
const backend = await setup();
|
|
723
831
|
const deadline = new Date(Date.now() + 500); // 500ms from now
|
|
724
|
-
const created = await
|
|
832
|
+
const created = await backend.createWorkflowRun({
|
|
725
833
|
workflowName: randomUUID(),
|
|
726
834
|
version: null,
|
|
727
835
|
idempotencyKey: null,
|
|
@@ -732,13 +840,13 @@ export function testBackend(factory, teardown) {
|
|
|
732
840
|
deadlineAt: deadline,
|
|
733
841
|
});
|
|
734
842
|
const workerId = randomUUID();
|
|
735
|
-
const claimed = await
|
|
843
|
+
const claimed = await backend.claimWorkflowRun({
|
|
736
844
|
workerId,
|
|
737
845
|
leaseDurationMs: 100,
|
|
738
846
|
});
|
|
739
847
|
expect(claimed).not.toBeNull();
|
|
740
848
|
// should mark as permanently failed since retry backoff (1s) would exceed deadline (500ms)
|
|
741
|
-
const failed = await
|
|
849
|
+
const failed = await backend.failWorkflowRun({
|
|
742
850
|
workflowRunId: created.id,
|
|
743
851
|
workerId,
|
|
744
852
|
error: { message: "test error" },
|
|
@@ -746,12 +854,13 @@ export function testBackend(factory, teardown) {
|
|
|
746
854
|
expect(failed.status).toBe("failed");
|
|
747
855
|
expect(failed.availableAt).toBeNull();
|
|
748
856
|
expect(failed.finishedAt).not.toBeNull();
|
|
749
|
-
|
|
857
|
+
expect(failed.startedAt).toBeNull(); // cleared on permanent failure
|
|
858
|
+
await teardown(backend);
|
|
750
859
|
});
|
|
751
860
|
test("reschedules failed workflow runs if retry would complete before deadline", async () => {
|
|
752
|
-
const
|
|
861
|
+
const backend = await setup();
|
|
753
862
|
const deadline = new Date(Date.now() + 5000); // in 5 seconds
|
|
754
|
-
const created = await
|
|
863
|
+
const created = await backend.createWorkflowRun({
|
|
755
864
|
workflowName: randomUUID(),
|
|
756
865
|
version: null,
|
|
757
866
|
idempotencyKey: null,
|
|
@@ -762,13 +871,13 @@ export function testBackend(factory, teardown) {
|
|
|
762
871
|
deadlineAt: deadline,
|
|
763
872
|
});
|
|
764
873
|
const workerId = randomUUID();
|
|
765
|
-
const claimed = await
|
|
874
|
+
const claimed = await backend.claimWorkflowRun({
|
|
766
875
|
workerId,
|
|
767
876
|
leaseDurationMs: 100,
|
|
768
877
|
});
|
|
769
878
|
expect(claimed).not.toBeNull();
|
|
770
879
|
// should reschedule since retry backoff (1s) is before deadline (5s
|
|
771
|
-
const failed = await
|
|
880
|
+
const failed = await backend.failWorkflowRun({
|
|
772
881
|
workflowRunId: created.id,
|
|
773
882
|
workerId,
|
|
774
883
|
error: { message: "test error" },
|
|
@@ -776,15 +885,15 @@ export function testBackend(factory, teardown) {
|
|
|
776
885
|
expect(failed.status).toBe("pending");
|
|
777
886
|
expect(failed.availableAt).not.toBeNull();
|
|
778
887
|
expect(failed.finishedAt).toBeNull();
|
|
779
|
-
await teardown(
|
|
888
|
+
await teardown(backend);
|
|
780
889
|
});
|
|
781
890
|
});
|
|
782
891
|
describe("cancelWorkflowRun()", () => {
|
|
783
892
|
test("cancels a pending workflow run", async () => {
|
|
784
|
-
const
|
|
785
|
-
const created = await createPendingWorkflowRun(
|
|
893
|
+
const backend = await setup();
|
|
894
|
+
const created = await createPendingWorkflowRun(backend);
|
|
786
895
|
expect(created.status).toBe("pending");
|
|
787
|
-
const canceled = await
|
|
896
|
+
const canceled = await backend.cancelWorkflowRun({
|
|
788
897
|
workflowRunId: created.id,
|
|
789
898
|
});
|
|
790
899
|
expect(canceled.status).toBe("canceled");
|
|
@@ -792,60 +901,60 @@ export function testBackend(factory, teardown) {
|
|
|
792
901
|
expect(canceled.availableAt).toBeNull();
|
|
793
902
|
expect(canceled.finishedAt).not.toBeNull();
|
|
794
903
|
expect(deltaSeconds(canceled.finishedAt)).toBeLessThan(1);
|
|
795
|
-
await teardown(
|
|
904
|
+
await teardown(backend);
|
|
796
905
|
});
|
|
797
906
|
test("cancels a running workflow run", async () => {
|
|
798
|
-
const
|
|
799
|
-
const created = await createClaimedWorkflowRun(
|
|
907
|
+
const backend = await setup();
|
|
908
|
+
const created = await createClaimedWorkflowRun(backend);
|
|
800
909
|
expect(created.status).toBe("running");
|
|
801
910
|
expect(created.workerId).not.toBeNull();
|
|
802
|
-
const canceled = await
|
|
911
|
+
const canceled = await backend.cancelWorkflowRun({
|
|
803
912
|
workflowRunId: created.id,
|
|
804
913
|
});
|
|
805
914
|
expect(canceled.status).toBe("canceled");
|
|
806
915
|
expect(canceled.workerId).toBeNull();
|
|
807
916
|
expect(canceled.availableAt).toBeNull();
|
|
808
917
|
expect(canceled.finishedAt).not.toBeNull();
|
|
809
|
-
await teardown(
|
|
918
|
+
await teardown(backend);
|
|
810
919
|
});
|
|
811
920
|
test("cancels a sleeping workflow run", async () => {
|
|
812
|
-
const
|
|
813
|
-
const claimed = await createClaimedWorkflowRun(
|
|
921
|
+
const backend = await setup();
|
|
922
|
+
const claimed = await createClaimedWorkflowRun(backend);
|
|
814
923
|
// put workflow to sleep
|
|
815
924
|
const sleepUntil = new Date(Date.now() + 60_000); // 1 minute from now
|
|
816
|
-
const sleeping = await
|
|
925
|
+
const sleeping = await backend.sleepWorkflowRun({
|
|
817
926
|
workflowRunId: claimed.id,
|
|
818
927
|
workerId: claimed.workerId ?? "",
|
|
819
928
|
availableAt: sleepUntil,
|
|
820
929
|
});
|
|
821
930
|
expect(sleeping.status).toBe("sleeping");
|
|
822
|
-
const canceled = await
|
|
931
|
+
const canceled = await backend.cancelWorkflowRun({
|
|
823
932
|
workflowRunId: sleeping.id,
|
|
824
933
|
});
|
|
825
934
|
expect(canceled.status).toBe("canceled");
|
|
826
935
|
expect(canceled.workerId).toBeNull();
|
|
827
936
|
expect(canceled.availableAt).toBeNull();
|
|
828
937
|
expect(canceled.finishedAt).not.toBeNull();
|
|
829
|
-
await teardown(
|
|
938
|
+
await teardown(backend);
|
|
830
939
|
});
|
|
831
940
|
test("throws error when canceling a completed workflow run", async () => {
|
|
832
|
-
const
|
|
833
|
-
const claimed = await createClaimedWorkflowRun(
|
|
941
|
+
const backend = await setup();
|
|
942
|
+
const claimed = await createClaimedWorkflowRun(backend);
|
|
834
943
|
// mark as completed
|
|
835
|
-
await
|
|
944
|
+
await backend.completeWorkflowRun({
|
|
836
945
|
workflowRunId: claimed.id,
|
|
837
946
|
workerId: claimed.workerId ?? "",
|
|
838
947
|
output: { result: "success" },
|
|
839
948
|
});
|
|
840
|
-
await expect(
|
|
949
|
+
await expect(backend.cancelWorkflowRun({
|
|
841
950
|
workflowRunId: claimed.id,
|
|
842
951
|
})).rejects.toThrow(/Cannot cancel workflow run .* with status completed/);
|
|
843
|
-
await teardown(
|
|
952
|
+
await teardown(backend);
|
|
844
953
|
});
|
|
845
954
|
test("throws error when canceling a failed workflow run", async () => {
|
|
846
|
-
const
|
|
955
|
+
const backend = await setup();
|
|
847
956
|
// create with deadline that's already passed to make it fail
|
|
848
|
-
const workflowWithDeadline = await
|
|
957
|
+
const workflowWithDeadline = await backend.createWorkflowRun({
|
|
849
958
|
workflowName: randomUUID(),
|
|
850
959
|
version: null,
|
|
851
960
|
idempotencyKey: null,
|
|
@@ -856,105 +965,126 @@ export function testBackend(factory, teardown) {
|
|
|
856
965
|
deadlineAt: new Date(Date.now() - 1000), // deadline in the past
|
|
857
966
|
});
|
|
858
967
|
// try to claim it, which should mark it as failed due to deadline
|
|
859
|
-
const claimed = await
|
|
968
|
+
const claimed = await backend.claimWorkflowRun({
|
|
860
969
|
workerId: randomUUID(),
|
|
861
970
|
leaseDurationMs: 100,
|
|
862
971
|
});
|
|
863
972
|
// if claim succeeds, manually fail it
|
|
864
973
|
if (claimed?.workerId) {
|
|
865
|
-
await
|
|
974
|
+
await backend.failWorkflowRun({
|
|
866
975
|
workflowRunId: claimed.id,
|
|
867
976
|
workerId: claimed.workerId,
|
|
868
977
|
error: { message: "test error" },
|
|
869
978
|
});
|
|
870
979
|
}
|
|
871
980
|
// get a workflow that's definitely failed
|
|
872
|
-
const failedRun = await
|
|
981
|
+
const failedRun = await backend.getWorkflowRun({
|
|
873
982
|
workflowRunId: workflowWithDeadline.id,
|
|
874
983
|
});
|
|
875
984
|
if (failedRun?.status === "failed") {
|
|
876
|
-
await expect(
|
|
985
|
+
await expect(backend.cancelWorkflowRun({
|
|
877
986
|
workflowRunId: failedRun.id,
|
|
878
987
|
})).rejects.toThrow(/Cannot cancel workflow run .* with status failed/);
|
|
879
988
|
}
|
|
880
|
-
await teardown(
|
|
989
|
+
await teardown(backend);
|
|
881
990
|
});
|
|
882
991
|
test("is idempotent when canceling an already canceled workflow run", async () => {
|
|
883
|
-
const
|
|
884
|
-
const created = await createPendingWorkflowRun(
|
|
885
|
-
const firstCancel = await
|
|
992
|
+
const backend = await setup();
|
|
993
|
+
const created = await createPendingWorkflowRun(backend);
|
|
994
|
+
const firstCancel = await backend.cancelWorkflowRun({
|
|
886
995
|
workflowRunId: created.id,
|
|
887
996
|
});
|
|
888
997
|
expect(firstCancel.status).toBe("canceled");
|
|
889
|
-
const secondCancel = await
|
|
998
|
+
const secondCancel = await backend.cancelWorkflowRun({
|
|
890
999
|
workflowRunId: created.id,
|
|
891
1000
|
});
|
|
892
1001
|
expect(secondCancel.status).toBe("canceled");
|
|
893
1002
|
expect(secondCancel.id).toBe(firstCancel.id);
|
|
894
|
-
await teardown(
|
|
1003
|
+
await teardown(backend);
|
|
895
1004
|
});
|
|
896
1005
|
test("throws error when canceling a non-existent workflow run", async () => {
|
|
897
|
-
const
|
|
1006
|
+
const backend = await setup();
|
|
898
1007
|
const nonExistentId = randomUUID();
|
|
899
|
-
await expect(
|
|
1008
|
+
await expect(backend.cancelWorkflowRun({
|
|
900
1009
|
workflowRunId: nonExistentId,
|
|
901
1010
|
})).rejects.toThrow(`Workflow run ${nonExistentId} does not exist`);
|
|
902
|
-
await teardown(
|
|
1011
|
+
await teardown(backend);
|
|
903
1012
|
});
|
|
904
1013
|
test("canceled workflow is not claimed by workers", async () => {
|
|
905
|
-
const
|
|
906
|
-
const created = await createPendingWorkflowRun(
|
|
1014
|
+
const backend = await setup();
|
|
1015
|
+
const created = await createPendingWorkflowRun(backend);
|
|
907
1016
|
// cancel the workflow
|
|
908
|
-
await
|
|
1017
|
+
await backend.cancelWorkflowRun({
|
|
909
1018
|
workflowRunId: created.id,
|
|
910
1019
|
});
|
|
911
1020
|
// try to claim work
|
|
912
|
-
const claimed = await
|
|
1021
|
+
const claimed = await backend.claimWorkflowRun({
|
|
913
1022
|
workerId: randomUUID(),
|
|
914
1023
|
leaseDurationMs: 100,
|
|
915
1024
|
});
|
|
916
1025
|
// should not claim the canceled workflow
|
|
917
1026
|
expect(claimed).toBeNull();
|
|
918
|
-
await teardown(
|
|
1027
|
+
await teardown(backend);
|
|
919
1028
|
});
|
|
920
1029
|
});
|
|
921
|
-
// Helper function for creating workflow runs that uses the shared backend
|
|
922
|
-
async function createPendingWorkflowRun(b) {
|
|
923
|
-
return await b.createWorkflowRun({
|
|
924
|
-
workflowName: randomUUID(),
|
|
925
|
-
version: null,
|
|
926
|
-
idempotencyKey: null,
|
|
927
|
-
input: null,
|
|
928
|
-
config: {},
|
|
929
|
-
context: null,
|
|
930
|
-
availableAt: null,
|
|
931
|
-
deadlineAt: null,
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
async function createClaimedWorkflowRun(b) {
|
|
935
|
-
await createPendingWorkflowRun(b);
|
|
936
|
-
const claimed = await b.claimWorkflowRun({
|
|
937
|
-
workerId: randomUUID(),
|
|
938
|
-
leaseDurationMs: 100,
|
|
939
|
-
});
|
|
940
|
-
if (!claimed)
|
|
941
|
-
throw new Error("Failed to claim workflow run");
|
|
942
|
-
return claimed;
|
|
943
|
-
}
|
|
944
1030
|
});
|
|
945
1031
|
}
|
|
946
|
-
|
|
1032
|
+
/**
|
|
1033
|
+
* Create a pending workflow run for tests.
|
|
1034
|
+
* @param b - Backend
|
|
1035
|
+
* @returns Created workflow run
|
|
1036
|
+
*/
|
|
1037
|
+
async function createPendingWorkflowRun(b) {
|
|
1038
|
+
return await b.createWorkflowRun({
|
|
1039
|
+
workflowName: randomUUID(),
|
|
1040
|
+
version: null,
|
|
1041
|
+
idempotencyKey: null,
|
|
1042
|
+
input: null,
|
|
1043
|
+
config: {},
|
|
1044
|
+
context: null,
|
|
1045
|
+
availableAt: null,
|
|
1046
|
+
deadlineAt: null,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Create and claim a workflow run for tests.
|
|
1051
|
+
* @param b - Backend
|
|
1052
|
+
* @returns Claimed workflow run
|
|
1053
|
+
*/
|
|
1054
|
+
async function createClaimedWorkflowRun(b) {
|
|
1055
|
+
await createPendingWorkflowRun(b);
|
|
1056
|
+
const claimed = await b.claimWorkflowRun({
|
|
1057
|
+
workerId: randomUUID(),
|
|
1058
|
+
leaseDurationMs: 100,
|
|
1059
|
+
});
|
|
1060
|
+
if (!claimed)
|
|
1061
|
+
throw new Error("Failed to claim workflow run");
|
|
1062
|
+
return claimed;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Get delta in seconds from now.
|
|
1066
|
+
* @param date - Date to compare
|
|
1067
|
+
* @returns Delta in seconds
|
|
1068
|
+
*/
|
|
947
1069
|
function deltaSeconds(date) {
|
|
948
1070
|
if (!date)
|
|
949
1071
|
return Infinity;
|
|
950
1072
|
return Math.abs((Date.now() - date.getTime()) / 1000);
|
|
951
1073
|
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Create a Date one year in the future.
|
|
1076
|
+
* @returns Future Date
|
|
1077
|
+
*/
|
|
952
1078
|
function newDateInOneYear() {
|
|
953
1079
|
const d = new Date();
|
|
954
1080
|
d.setFullYear(d.getFullYear() + 1);
|
|
955
1081
|
return d;
|
|
956
1082
|
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Sleep for a given duration.
|
|
1085
|
+
* @param ms - Milliseconds to sleep
|
|
1086
|
+
* @returns Promise resolved after sleeping
|
|
1087
|
+
*/
|
|
957
1088
|
function sleep(ms) {
|
|
958
1089
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
959
1090
|
}
|
|
960
|
-
//# sourceMappingURL=backend-test-suite.js.map
|