@@ -7,9 +7,9 @@ import { msToNumber, tsToMs } from '@temporalio/common/lib/time';
77import { TestWorkflowEnvironment } from '@temporalio/testing' ;
88import { CancelReason } from '@temporalio/worker/lib/activity' ;
99import * as workflow from '@temporalio/workflow' ;
10- import { defineQuery , defineSignal } from '@temporalio/workflow' ;
10+ import { defineQuery , defineSignal , WorkflowIdConflictPolicy } from '@temporalio/workflow' ;
1111import { SdkFlags } from '@temporalio/workflow/lib/flags' ;
12- import { ActivityCancellationType , ApplicationFailure } from '@temporalio/common' ;
12+ import { ActivityCancellationType , ApplicationFailure , WorkflowExecutionAlreadyStartedError } from '@temporalio/common' ;
1313import { signalSchedulingWorkflow } from './activities/helpers' ;
1414import { activityStartedSignal } from './workflows/definitions' ;
1515import * as workflows from './workflows' ;
@@ -216,6 +216,122 @@ test('Start of workflow is delayed', async (t) => {
216216 t . is ( tsToMs ( startDelay ) , 5678000 ) ;
217217} ) ;
218218
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+
219335test ( 'Start of workflow with signal is delayed' , async ( t ) => {
220336 const { taskQueue } = helpers ( t ) ;
221337 // This workflow never runs
0 commit comments