diff --git a/tests/utils/exponential-backoff.test.ts b/tests/utils/exponential-backoff.test.ts index 6184a6b..f5c9443 100644 --- a/tests/utils/exponential-backoff.test.ts +++ b/tests/utils/exponential-backoff.test.ts @@ -261,6 +261,46 @@ const testExponentialBackoffRunWithAbortSignal = async (): Promise => { expect(abortAndThrowFn).not.toHaveResolved(); }; +/** + * Tests that aborting while the delay between attempts is active rejects the + * pending wait immediately and prevents the next attempt from starting. + */ +const testExponentialBackoffAbortsDuringDelay = async (): Promise => { + vi.useFakeTimers(); + + try { + let abortRetry: ((reason?: unknown) => void) | undefined; + const rejectedFn = vi.fn(async ({ abort }) => { + abortRetry = abort; + + throw new Error('attempt failed'); + }); + const onError = vi.fn(); + + const result = ExponentialBackoff.run(rejectedFn, onError, { + baseDelay: 1_000, + maxDelay: 1_000, + maxAttempts: 2, + growthRate: 1, + jitter: 0, + }); + + // Run the first attempt, enter the retry delay, then stop halfway through it. + await vi.advanceTimersByTimeAsync(500); + expect(abortRetry).toBeDefined(); + expect(vi.getTimerCount()).toBe(1); + + abortRetry?.(new Error('cancelled during delay')); + + await expect(result).rejects.toThrow('Exponential backoff aborted'); + expect(rejectedFn).toHaveBeenCalledOnce(); + expect(onError).toHaveBeenCalledOnce(); + expect(vi.getTimerCount()).toBe(0); + } finally { + vi.useRealTimers(); + } +}; + /** * Tests that when the abort signal is aborted with a string, an AggregateError is thrown * with the string as the message. @@ -578,6 +618,7 @@ const runTests = async (): Promise => { test('ExponentialBackoff: wraps non-Error throws before calling onError', testExponentialBackoffWrapsNonErrorThrows); test('ExponentialBackoff: succeeds and aborts with abort signal', testExponentialBackoffRunSuccessAndAbortSignal); test('ExponentialBackoff: aborts with abort signal', testExponentialBackoffRunWithAbortSignal); + test('ExponentialBackoff: aborts while waiting between attempts', testExponentialBackoffAbortsDuringDelay); test('ExponentialBackoff: aborts with aborted string creates error', testExponentialBackoffRunAbortedStringCreatesError); test('ExponentialBackoff: works via from and instance run', testExponentialBackoffFromAndInstanceRun); test('ExponentialBackoff: retries indefinitely when maxAttempts is 0', testExponentialBackoffRetriesIndefinitelyWhenMaxAttemptsIsZero);