@@ -7,9 +7,9 @@ import { msToNumber, tsToMs } from '@temporalio/common/lib/time';
7
7
import { TestWorkflowEnvironment } from '@temporalio/testing' ;
8
8
import { CancelReason } from '@temporalio/worker/lib/activity' ;
9
9
import * as workflow from '@temporalio/workflow' ;
10
- import { defineQuery , defineSignal } from '@temporalio/workflow' ;
10
+ import { defineQuery , defineSignal , WorkflowIdConflictPolicy } from '@temporalio/workflow' ;
11
11
import { SdkFlags } from '@temporalio/workflow/lib/flags' ;
12
- import { ActivityCancellationType , ApplicationFailure } from '@temporalio/common' ;
12
+ import { ActivityCancellationType , ApplicationFailure , WorkflowExecutionAlreadyStartedError } from '@temporalio/common' ;
13
13
import { signalSchedulingWorkflow } from './activities/helpers' ;
14
14
import { activityStartedSignal } from './workflows/definitions' ;
15
15
import * as workflows from './workflows' ;
@@ -216,6 +216,122 @@ test('Start of workflow is delayed', async (t) => {
216
216
t . is ( tsToMs ( startDelay ) , 5678000 ) ;
217
217
} ) ;
218
218
219
+ export async function conflictId ( ) : Promise < void > {
220
+ await workflow . condition ( ( ) => false ) ;
221
+ }
222
+
223
+ test ( 'Start of workflow respects workflow id conflict policy' , async ( t ) => {
224
+ const { createWorker, taskQueue } = helpers ( t ) ;
225
+ const wfid = `${ taskQueue } -` + randomUUID ( ) ;
226
+ const client = t . context . env . client ;
227
+
228
+ const worker = await createWorker ( ) ;
229
+ await worker . runUntil ( async ( ) => {
230
+ const handle = await client . workflow . start ( conflictId , {
231
+ taskQueue,
232
+ workflowId : wfid ,
233
+ } ) ;
234
+ const handleWithRunId = client . workflow . getHandle ( handle . workflowId , handle . firstExecutionRunId ) ;
235
+
236
+ // Confirm another fails by default
237
+ const err = await t . throwsAsync (
238
+ client . workflow . start ( conflictId , {
239
+ taskQueue,
240
+ workflowId : wfid ,
241
+ } ) ,
242
+ {
243
+ instanceOf : WorkflowExecutionAlreadyStartedError ,
244
+ }
245
+ ) ;
246
+
247
+ t . true ( err instanceof WorkflowExecutionAlreadyStartedError ) ;
248
+
249
+ // Confirm fails with explicit option
250
+ const err1 = await t . throwsAsync (
251
+ client . workflow . start ( conflictId , {
252
+ taskQueue,
253
+ workflowId : wfid ,
254
+ workflowIdConflictPolicy : WorkflowIdConflictPolicy . WORKFLOW_ID_CONFLICT_POLICY_FAIL ,
255
+ } ) ,
256
+ {
257
+ instanceOf : WorkflowExecutionAlreadyStartedError ,
258
+ }
259
+ ) ;
260
+
261
+ t . true ( err1 instanceof WorkflowExecutionAlreadyStartedError ) ;
262
+
263
+ // Confirm gives back same handle
264
+ const handle2 = await client . workflow . start ( conflictId , {
265
+ taskQueue,
266
+ workflowId : wfid ,
267
+ workflowIdConflictPolicy : WorkflowIdConflictPolicy . WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING ,
268
+ } ) ;
269
+
270
+ const desc = await handleWithRunId . describe ( ) ;
271
+ const desc2 = await handle2 . describe ( ) ;
272
+
273
+ t . is ( desc . runId , desc2 . runId ) ;
274
+ t . is ( desc . status . name , 'RUNNING' ) ;
275
+ t . is ( desc2 . status . name , 'RUNNING' ) ;
276
+
277
+ // Confirm terminates and starts new
278
+ const handle3 = await client . workflow . start ( conflictId , {
279
+ taskQueue,
280
+ workflowId : wfid ,
281
+ workflowIdConflictPolicy : WorkflowIdConflictPolicy . WORKFLOW_ID_CONFLICT_POLICY_TERMINATE_EXISTING ,
282
+ } ) ;
283
+
284
+ const descWithRunId = await handleWithRunId . describe ( ) ;
285
+ const desc3 = await handle3 . describe ( ) ;
286
+ t . not ( descWithRunId . runId , desc3 . runId ) ;
287
+ t . is ( descWithRunId . status . name , 'TERMINATED' ) ;
288
+ t . is ( desc3 . status . name , 'RUNNING' ) ;
289
+ } ) ;
290
+ } ) ;
291
+
292
+ test ( 'Start of workflow with signal respects conflict id policy' , async ( t ) => {
293
+ const { createWorker, taskQueue } = helpers ( t ) ;
294
+ const wfid = `${ taskQueue } -` + randomUUID ( ) ;
295
+ const client = t . context . env . client ;
296
+ const worker = await createWorker ( ) ;
297
+ await worker . runUntil ( async ( ) => {
298
+ const handle = await client . workflow . start ( workflows . signalTarget , {
299
+ taskQueue,
300
+ workflowId : wfid ,
301
+ } ) ;
302
+ const handleWithRunId = client . workflow . getHandle ( handle . workflowId , handle . firstExecutionRunId ) ;
303
+
304
+ // Confirm gives back same handle is the default policy
305
+ const handle2 = await t . context . env . client . workflow . signalWithStart ( workflows . signalTarget , {
306
+ taskQueue,
307
+ workflowId : wfid ,
308
+ signal : workflows . argsTestSignal ,
309
+ signalArgs : [ 123 , 'kid' ] ,
310
+ } ) ;
311
+ const desc = await handleWithRunId . describe ( ) ;
312
+ const desc2 = await handle2 . describe ( ) ;
313
+
314
+ t . deepEqual ( desc . runId , desc2 . runId ) ;
315
+ t . deepEqual ( desc . status . name , 'RUNNING' ) ;
316
+ t . deepEqual ( desc2 . status . name , 'RUNNING' ) ;
317
+
318
+ // Confirm terminates and starts new
319
+ const handle3 = await t . context . env . client . workflow . signalWithStart ( workflows . signalTarget , {
320
+ taskQueue,
321
+ workflowId : wfid ,
322
+ signal : workflows . argsTestSignal ,
323
+ signalArgs : [ 123 , 'kid' ] ,
324
+ workflowIdConflictPolicy : WorkflowIdConflictPolicy . WORKFLOW_ID_CONFLICT_POLICY_TERMINATE_EXISTING ,
325
+ } ) ;
326
+
327
+ const descWithRunId = await handleWithRunId . describe ( ) ;
328
+ const desc3 = await handle3 . describe ( ) ;
329
+ t . true ( descWithRunId . runId !== desc3 . runId ) ;
330
+ t . deepEqual ( descWithRunId . status . name , 'TERMINATED' ) ;
331
+ t . deepEqual ( desc3 . status . name , 'RUNNING' ) ;
332
+ } ) ;
333
+ } ) ;
334
+
219
335
test ( 'Start of workflow with signal is delayed' , async ( t ) => {
220
336
const { taskQueue } = helpers ( t ) ;
221
337
// This workflow never runs
0 commit comments