@@ -296,6 +296,53 @@ describe("RetryableTask", () => {
296296 expect ( delay ) . toBeDefined ( ) ;
297297 expect ( delay ) . toBeGreaterThan ( 0 ) ;
298298 } ) ;
299+
300+ it ( "should return undefined when last failure is marked non-retriable" , ( ) => {
301+ // Arrange
302+ const retryPolicy = new RetryPolicy ( {
303+ maxNumberOfAttempts : 10 ,
304+ firstRetryIntervalInMilliseconds : 1000 ,
305+ } ) ;
306+ const action = new pb . OrchestratorAction ( ) ;
307+ const startTime = new Date ( ) ;
308+ const task = new RetryableTask < string > ( retryPolicy , action , startTime , "activity" ) ;
309+
310+ // Record a non-retriable failure (e.g., activity not found, version mismatch)
311+ const failureDetails = new pb . TaskFailureDetails ( ) ;
312+ failureDetails . setErrortype ( "ActivityNotFoundError" ) ;
313+ failureDetails . setErrormessage ( "Activity 'DoWork' is not registered" ) ;
314+ failureDetails . setIsnonretriable ( true ) ;
315+ task . recordFailure ( "Activity 'DoWork' is not registered" , failureDetails ) ;
316+
317+ const currentTime = new Date ( startTime . getTime ( ) + 100 ) ;
318+
319+ // Act
320+ const delay = task . computeNextDelayInMilliseconds ( currentTime ) ;
321+
322+ // Assert - non-retriable failures must not be retried regardless of policy
323+ expect ( delay ) . toBeUndefined ( ) ;
324+ } ) ;
325+
326+ it ( "should return undefined when computed delay exceeds remaining timeout" , ( ) => {
327+ // Arrange - timeout is 5000ms, elapsed is 4500ms, so remaining is 500ms.
328+ // With firstRetryInterval of 1000ms, the computed delay (1000) > remaining (500).
329+ const retryPolicy = new RetryPolicy ( {
330+ maxNumberOfAttempts : 10 ,
331+ firstRetryIntervalInMilliseconds : 1000 ,
332+ retryTimeoutInMilliseconds : 5000 ,
333+ } ) ;
334+ const startTime = new Date ( ) ;
335+ const action = new pb . OrchestratorAction ( ) ;
336+ const task = new RetryableTask < string > ( retryPolicy , action , startTime , "activity" ) ;
337+ // 4500ms elapsed — within the 5000ms timeout, but delay (1000ms) exceeds remaining (500ms)
338+ const currentTime = new Date ( startTime . getTime ( ) + 4500 ) ;
339+
340+ // Act
341+ const delay = task . computeNextDelayInMilliseconds ( currentTime ) ;
342+
343+ // Assert - delay would overshoot the timeout, so no more retries
344+ expect ( delay ) . toBeUndefined ( ) ;
345+ } ) ;
299346 } ) ;
300347
301348 describe ( "incrementAttemptCount" , ( ) => {
@@ -361,6 +408,36 @@ describe("RetryableTask", () => {
361408 } ) ;
362409 } ) ;
363410
411+ describe ( "complete" , ( ) => {
412+ it ( "should clear failure state when retry succeeds" , ( ) => {
413+ // Arrange
414+ const retryPolicy = new RetryPolicy ( {
415+ maxNumberOfAttempts : 5 ,
416+ firstRetryIntervalInMilliseconds : 1000 ,
417+ } ) ;
418+ const action = new pb . OrchestratorAction ( ) ;
419+ const task = new RetryableTask < string > ( retryPolicy , action , new Date ( ) , "activity" ) ;
420+
421+ // Simulate a failed attempt followed by a successful retry
422+ const failureDetails = new pb . TaskFailureDetails ( ) ;
423+ failureDetails . setErrortype ( "TransientError" ) ;
424+ failureDetails . setErrormessage ( "Temporary failure" ) ;
425+ task . recordFailure ( "Temporary failure" , failureDetails ) ;
426+
427+ // Verify failure is recorded
428+ expect ( task . lastFailure ) . toBeDefined ( ) ;
429+
430+ // Act - retry succeeds
431+ task . complete ( "success" ) ;
432+
433+ // Assert - failure state is cleared, result is set
434+ expect ( task . lastFailure ) . toBeUndefined ( ) ;
435+ expect ( task . isFailed ) . toBe ( false ) ;
436+ expect ( task . getResult ( ) ) . toBe ( "success" ) ;
437+ expect ( task . isComplete ) . toBe ( true ) ;
438+ } ) ;
439+ } ) ;
440+
364441 describe ( "task types" , ( ) => {
365442 it ( "should support activity task type" , ( ) => {
366443 // Arrange
0 commit comments