@@ -30,14 +30,15 @@ import * as workflow from '@temporalio/workflow';
3030import { temporal } from '@temporalio/proto' ;
3131import { defineSearchAttributeKey , SearchAttributeType } from '@temporalio/common/lib/search-attributes' ;
3232import { ConnectionInjectorInterceptor } from './activities/interceptors' ;
33- import { Worker , TestWorkflowEnvironment , test as anyTest , bundlerOptions } from './helpers' ;
33+ import { Worker , TestWorkflowEnvironment , test as anyTest , bundlerOptions , waitUntil } from './helpers' ;
3434
3535export interface Context {
3636 env : TestWorkflowEnvironment ;
3737 workflowBundle : WorkflowBundle ;
3838}
3939
4040const defaultDynamicConfigOptions = [
41+ 'frontend.activityAPIsEnabled=true' ,
4142 'frontend.enableExecuteMultiOperation=true' ,
4243 'frontend.workerVersioningDataAPIs=true' ,
4344 'frontend.workerVersioningWorkflowAPIs=true' ,
@@ -284,6 +285,57 @@ export function configurableHelpers<T>(
284285 } ;
285286}
286287
288+ export async function setActivityPauseState ( handle : WorkflowHandle , activityId : string , pause : boolean ) : Promise < void > {
289+ const desc = await handle . describe ( ) ;
290+ const req = {
291+ namespace : handle . client . options . namespace ,
292+ execution : {
293+ workflowId : desc . raw . workflowExecutionInfo ?. execution ?. workflowId ,
294+ runId : desc . raw . workflowExecutionInfo ?. execution ?. runId ,
295+ } ,
296+ id : activityId ,
297+ } ;
298+ if ( pause ) {
299+ await handle . client . workflowService . pauseActivity ( req ) ;
300+ } else {
301+ await handle . client . workflowService . unpauseActivity ( req ) ;
302+ }
303+ await waitUntil ( async ( ) => {
304+ const { raw } = await handle . describe ( ) ;
305+ const activityInfo = raw . pendingActivities ?. find ( ( act ) => act . activityId === activityId ) ;
306+ // If we are pausing: success when either
307+ // • paused flag is true OR
308+ // • the activity vanished (it completed / retried)
309+ if ( pause ) return activityInfo ? activityInfo . paused ?? false : true ;
310+ // If we are unpausing: success when either
311+ // • paused flag is false OR
312+ // • the activity vanished (already completed)
313+ return activityInfo ? ! activityInfo . paused : true ;
314+ } , 15000 ) ;
315+ }
316+
317+ // Helper function to check if an activity has heartbeated
318+ export async function hasActivityHeartbeat (
319+ handle : WorkflowHandle ,
320+ activityId : string ,
321+ expectedContent ?: string
322+ ) : Promise < boolean > {
323+ const { raw } = await handle . describe ( ) ;
324+ const activityInfo = raw . pendingActivities ?. find ( ( act ) => act . activityId === activityId ) ;
325+ const heartbeatData = activityInfo ?. heartbeatDetails ?. payloads ?. [ 0 ] ?. data ;
326+ if ( ! heartbeatData ) return false ;
327+
328+ // If no expected content specified, just check that heartbeat data exists
329+ if ( ! expectedContent ) return true ;
330+
331+ try {
332+ const decoded = Buffer . from ( heartbeatData ) . toString ( ) ;
333+ return decoded . includes ( expectedContent ) ;
334+ } catch {
335+ return false ;
336+ }
337+ }
338+
287339export function helpers ( t : ExecutionContext < Context > , testEnv : TestWorkflowEnvironment = t . context . env ) : Helpers {
288340 return configurableHelpers ( t , t . context . workflowBundle , testEnv ) ;
289341}
0 commit comments