@@ -2,7 +2,7 @@ import { setTimeout as setTimeoutPromise } from 'timers/promises';
2
2
import { randomUUID } from 'crypto' ;
3
3
import { ExecutionContext } from 'ava' ;
4
4
import { firstValueFrom , Subject } from 'rxjs' ;
5
- import { WorkflowFailedError } from '@temporalio/client' ;
5
+ import { WorkflowFailedError , WorkflowHandle } from '@temporalio/client' ;
6
6
import * as activity from '@temporalio/activity' ;
7
7
import { msToNumber , tsToMs } from '@temporalio/common/lib/time' ;
8
8
import { TestWorkflowEnvironment } from '@temporalio/testing' ;
@@ -11,6 +11,7 @@ import * as workflow from '@temporalio/workflow';
11
11
import { defineQuery , defineSignal } from '@temporalio/workflow' ;
12
12
import { SdkFlags } from '@temporalio/workflow/lib/flags' ;
13
13
import {
14
+ ActivityCancellationDetails ,
14
15
ActivityCancellationType ,
15
16
ApplicationFailure ,
16
17
defineSearchAttributeKey ,
@@ -22,9 +23,17 @@ import {
22
23
import { signalSchedulingWorkflow } from './activities/helpers' ;
23
24
import { activityStartedSignal } from './workflows/definitions' ;
24
25
import * as workflows from './workflows' ;
25
- import { Context , createLocalTestEnvironment , helpers , makeTestFunction } from './helpers-integration' ;
26
+ import {
27
+ assertPendingActivityExistsEventually ,
28
+ Context ,
29
+ createLocalTestEnvironment ,
30
+ helpers ,
31
+ makeTestFunction ,
32
+ setActivityPauseState ,
33
+ } from './helpers-integration' ;
26
34
import { overrideSdkInternalFlag } from './mock-internal-flags' ;
27
35
import { asSdkLoggerSink , loadHistory , RUN_TIME_SKIPPING_TESTS , waitUntil } from './helpers' ;
36
+ import { heartbeatCancellationDetailsActivity } from './activities/heartbeat-cancellation-details' ;
28
37
29
38
const test = makeTestFunction ( {
30
39
workflowsPath : __filename ,
@@ -1414,3 +1423,116 @@ test('Workflow can return root workflow', async (t) => {
1414
1423
t . deepEqual ( result , 'empty test-root-workflow-length' ) ;
1415
1424
} ) ;
1416
1425
} ) ;
1426
+
1427
+ export async function heartbeatPauseWorkflow (
1428
+ activityId : string ,
1429
+ catchErr : boolean ,
1430
+ maximumAttempts : number
1431
+ ) : Promise < Array < ActivityCancellationDetails | undefined > > {
1432
+ const { heartbeatCancellationDetailsActivity } = workflow . proxyActivities ( {
1433
+ startToCloseTimeout : '5s' ,
1434
+ activityId,
1435
+ retry : {
1436
+ maximumAttempts,
1437
+ } ,
1438
+ heartbeatTimeout : '1s' ,
1439
+ } ) ;
1440
+ const { heartbeatCancellationDetailsActivity2 } = workflow . proxyActivities ( {
1441
+ startToCloseTimeout : '5s' ,
1442
+ activityId : `${ activityId } -2` ,
1443
+ retry : {
1444
+ maximumAttempts,
1445
+ } ,
1446
+ heartbeatTimeout : '1s' ,
1447
+ } ) ;
1448
+ const results = [ ] ;
1449
+ results . push (
1450
+ await heartbeatCancellationDetailsActivity ( catchErr ) ,
1451
+ await heartbeatCancellationDetailsActivity2 ( catchErr )
1452
+ ) ;
1453
+ return results ;
1454
+ }
1455
+
1456
+ test ( 'Activity pause returns expected cancellation details' , async ( t ) => {
1457
+ const { createWorker, startWorkflow } = helpers ( t ) ;
1458
+ const worker = await createWorker ( {
1459
+ activities : {
1460
+ heartbeatCancellationDetailsActivity,
1461
+ heartbeatCancellationDetailsActivity2 : heartbeatCancellationDetailsActivity ,
1462
+ } ,
1463
+ } ) ;
1464
+
1465
+ await worker . runUntil ( async ( ) => {
1466
+ const testActivityId = randomUUID ( ) ;
1467
+ const handle = await startWorkflow ( heartbeatPauseWorkflow , { args : [ testActivityId , true , 1 ] } ) ;
1468
+
1469
+ const activityInfo = await assertPendingActivityExistsEventually ( handle , testActivityId , 5000 ) ;
1470
+ t . true ( activityInfo . paused === false ) ;
1471
+ await setActivityPauseState ( handle , testActivityId , true ) ;
1472
+ const activityInfo2 = await assertPendingActivityExistsEventually ( handle , `${ testActivityId } -2` , 5000 ) ;
1473
+ t . true ( activityInfo2 . paused === false ) ;
1474
+ await setActivityPauseState ( handle , `${ testActivityId } -2` , true ) ;
1475
+ const result = await handle . result ( ) ;
1476
+ t . deepEqual ( result [ 0 ] , {
1477
+ cancelRequested : false ,
1478
+ notFound : false ,
1479
+ paused : true ,
1480
+ timedOut : false ,
1481
+ workerShutdown : false ,
1482
+ reset : false ,
1483
+ } ) ;
1484
+ t . deepEqual ( result [ 1 ] , {
1485
+ cancelRequested : false ,
1486
+ notFound : false ,
1487
+ paused : true ,
1488
+ timedOut : false ,
1489
+ workerShutdown : false ,
1490
+ reset : false ,
1491
+ } ) ;
1492
+ } ) ;
1493
+ } ) ;
1494
+
1495
+ test ( 'Activity can pause and unpause' , async ( t ) => {
1496
+ const { createWorker, startWorkflow } = helpers ( t ) ;
1497
+ async function checkHeartbeatDetailsExist ( handle : WorkflowHandle , activityId : string ) {
1498
+ const activityInfo = await assertPendingActivityExistsEventually ( handle , activityId , 5000 ) ;
1499
+ if ( activityInfo . heartbeatDetails ?. payloads ) {
1500
+ for ( const payload of activityInfo . heartbeatDetails ?. payloads || [ ] ) {
1501
+ if ( payload . data && payload . data ?. length > 0 ) {
1502
+ return true ;
1503
+ }
1504
+ }
1505
+ }
1506
+ return false ;
1507
+ }
1508
+
1509
+ const worker = await createWorker ( {
1510
+ activities : {
1511
+ heartbeatCancellationDetailsActivity,
1512
+ heartbeatCancellationDetailsActivity2 : heartbeatCancellationDetailsActivity ,
1513
+ } ,
1514
+ } ) ;
1515
+
1516
+ await worker . runUntil ( async ( ) => {
1517
+ const testActivityId = randomUUID ( ) ;
1518
+ const handle = await startWorkflow ( heartbeatPauseWorkflow , { args : [ testActivityId , false , 2 ] } ) ;
1519
+ const activityInfo = await assertPendingActivityExistsEventually ( handle , testActivityId , 5000 ) ;
1520
+ t . true ( activityInfo . paused === false ) ;
1521
+ await setActivityPauseState ( handle , testActivityId , true ) ;
1522
+ await waitUntil ( async ( ) => {
1523
+ return await checkHeartbeatDetailsExist ( handle , testActivityId ) ;
1524
+ } , 5000 ) ;
1525
+ await setActivityPauseState ( handle , testActivityId , false ) ;
1526
+ const activityInfo2 = await assertPendingActivityExistsEventually ( handle , `${ testActivityId } -2` , 5000 ) ;
1527
+ t . true ( activityInfo2 . paused === false ) ;
1528
+ await setActivityPauseState ( handle , `${ testActivityId } -2` , true ) ;
1529
+ await waitUntil ( async ( ) => {
1530
+ return await checkHeartbeatDetailsExist ( handle , `${ testActivityId } -2` ) ;
1531
+ } , 5000 ) ;
1532
+ await setActivityPauseState ( handle , `${ testActivityId } -2` , false ) ;
1533
+ const result = await handle . result ( ) ;
1534
+ // Undefined values are converted to null by data converter.
1535
+ t . true ( result [ 0 ] === null ) ;
1536
+ t . true ( result [ 1 ] === null ) ;
1537
+ } ) ;
1538
+ } ) ;
0 commit comments