|
1 | 1 | import { afterEach, describe, expect, it, vi } from 'vitest' |
2 | | -import { CopilotBackendError } from '@/lib/mothership/request/go/stream' |
| 2 | +import { |
| 3 | + CopilotBackendError, |
| 4 | + StreamEndedWithoutTerminalError, |
| 5 | +} from '@/lib/mothership/request/go/stream' |
3 | 6 | import { StreamRetryWindow } from '@/lib/mothership/request/lifecycle/stream-retry' |
4 | 7 |
|
5 | 8 | afterEach(() => vi.useRealTimers()) |
6 | 9 |
|
7 | 10 | describe('stream recovery budget', () => { |
8 | | - it('survives multiple unavailable connections without resetting its deadline', () => { |
| 11 | + it.each([ |
| 12 | + new TypeError('fetch failed'), |
| 13 | + new StreamEndedWithoutTerminalError('/api/mothership'), |
| 14 | + new CopilotBackendError('Unavailable', { status: 503 }), |
| 15 | + ])('stops after three retries despite a long task budget: %s', (error) => { |
9 | 16 | vi.useFakeTimers() |
10 | | - const retry = new StreamRetryWindow(120_000) |
11 | | - for (let index = 0; index < 6; index++) { |
12 | | - const delay = retry.nextDelay(new TypeError('fetch failed')) |
| 17 | + const retry = new StreamRetryWindow() |
| 18 | + for (let index = 0; index < 3; index++) { |
| 19 | + const delay = retry.nextDelay(error) |
13 | 20 | expect(delay).not.toBeNull() |
14 | 21 | vi.advanceTimersByTime(delay ?? 0) |
15 | 22 | } |
16 | | - expect(retry.attempt).toBe(6) |
17 | | - expect(retry.remainingMs()).toBeLessThan(120_000) |
| 23 | + expect(retry.nextDelay(error)).toBeNull() |
| 24 | + expect(retry.attempt).toBe(3) |
| 25 | + expect(retry.remainingMs()).toBeGreaterThan(3_500_000) |
| 26 | + }) |
| 27 | + |
| 28 | + it('bounds the recovery period from the first failure without shortening healthy work', () => { |
| 29 | + vi.useFakeTimers() |
| 30 | + const retry = new StreamRetryWindow() |
| 31 | + vi.advanceTimersByTime(600_000) |
| 32 | + expect(retry.nextDelay(new TypeError('fetch failed'))).not.toBeNull() |
| 33 | + vi.advanceTimersByTime(30_000) |
| 34 | + expect(retry.nextDelay(new TypeError('fetch failed'))).toBeNull() |
| 35 | + expect(retry.remainingMs()).toBe(2_970_000) |
| 36 | + }) |
| 37 | + |
| 38 | + it('never extends the original execution deadline', () => { |
| 39 | + vi.useFakeTimers() |
| 40 | + const retry = new StreamRetryWindow(120_000) |
| 41 | + vi.advanceTimersByTime(119_999) |
| 42 | + expect(retry.nextDelay(new TypeError('fetch failed'))).toBeNull() |
18 | 43 | vi.advanceTimersByTime(120_000) |
19 | 44 | expect(retry.nextDelay(new TypeError('fetch failed'))).toBeNull() |
20 | 45 | expect(() => retry.remainingMs()).toThrow('could not be restored') |
|
0 commit comments