Skip to content

Commit 59c60b6

Browse files
authored
fix: Fail workflow on WorkflowExecutionAlreadyStartedError (#1068)
1 parent 5227ba9 commit 59c60b6

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

packages/common/src/errors.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { TemporalFailure } from './failure';
2+
13
/**
24
* Thrown from code that receives a value that is unexpected or that it's unable to handle.
35
*/
@@ -31,7 +33,7 @@ export class IllegalStateError extends Error {
3133
* - There is closed Workflow in the `Completed` state with the same Workflow Id and the {@link WorkflowOptions.workflowIdReusePolicy}
3234
* is `WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY`
3335
*/
34-
export class WorkflowExecutionAlreadyStartedError extends Error {
36+
export class WorkflowExecutionAlreadyStartedError extends TemporalFailure {
3537
public readonly name: string = 'WorkflowExecutionAlreadyStartedError';
3638

3739
constructor(message: string, public readonly workflowId: string, public readonly workflowType: string) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { randomUUID } from 'crypto';
2+
import { TestFn } from 'ava';
3+
import { WorkflowFailedError } from '@temporalio/client';
4+
import { TestWorkflowEnvironment } from '@temporalio/testing';
5+
import { bundleWorkflowCode } from '@temporalio/worker';
6+
import * as workflow from '@temporalio/workflow';
7+
import { test as anyTest, bundlerOptions, Worker } from './helpers';
8+
9+
interface Context {
10+
env: TestWorkflowEnvironment;
11+
taskQueue: string;
12+
createWorker(): Promise<Worker>;
13+
}
14+
15+
const test = anyTest as TestFn<Context>;
16+
17+
test.before(async (t) => {
18+
const env = await TestWorkflowEnvironment.createLocal();
19+
const taskQueue = 'test';
20+
const workflowBundle = await bundleWorkflowCode({
21+
...bundlerOptions,
22+
workflowsPath: __filename,
23+
});
24+
async function createWorker() {
25+
return await Worker.create({
26+
connection: env.nativeConnection,
27+
taskQueue,
28+
workflowBundle,
29+
});
30+
}
31+
t.context = {
32+
env,
33+
taskQueue,
34+
createWorker,
35+
};
36+
});
37+
38+
test.after.always(async (t) => {
39+
await t.context.env.teardown();
40+
});
41+
42+
export async function parent(): Promise<void> {
43+
await workflow.startChild(child, { workflowId: 'child' });
44+
await workflow.startChild(child, { workflowId: 'child' });
45+
}
46+
47+
export async function child(): Promise<void> {
48+
await workflow.CancellationScope.current().cancelRequested;
49+
}
50+
51+
test('Workflow fails if it tries to start a child with an existing workflow ID', async (t) => {
52+
const { createWorker, taskQueue, env } = t.context;
53+
const worker = await createWorker();
54+
await worker.runUntil(async () => {
55+
const err = await t.throwsAsync(env.client.workflow.execute(parent, { taskQueue, workflowId: randomUUID() }), {
56+
instanceOf: WorkflowFailedError,
57+
});
58+
t.true(
59+
err instanceof WorkflowFailedError &&
60+
err.cause?.name === 'TemporalFailure' &&
61+
err.cause?.message === 'Workflow execution already started'
62+
);
63+
});
64+
});

0 commit comments

Comments
 (0)