import { describe, expect, it, vi } from 'vitest' import { createBackoff, reconnectLoop, BACKOFF_CAP_MS } from '../src/transport/backoff.js' import type { Tunnel } from '../src/transport/tunnel.js' describe('createBackoff (T10)', () => { it('follows 1s,2s,4s…cap 30s', () => { const b = createBackoff() const seq = [b.nextDelayMs(), b.nextDelayMs(), b.nextDelayMs(), b.nextDelayMs(), b.nextDelayMs(), b.nextDelayMs(), b.nextDelayMs()] expect(seq).toEqual([1000, 2000, 4000, 8000, 16000, 30000, 30000]) expect(BACKOFF_CAP_MS).toBe(30_000) }) it('reset() returns to 1s', () => { const b = createBackoff() b.nextDelayMs() b.nextDelayMs() b.reset() expect(b.nextDelayMs()).toBe(1000) }) it('jitter stays within [0.5×, 1×]', () => { const b = createBackoff({ jitter: true, rng: () => 0 }) expect(b.nextDelayMs()).toBe(500) // 1000 * 0.5 const b2 = createBackoff({ jitter: true, rng: () => 1 }) expect(b2.nextDelayMs()).toBe(1000) // 1000 * 1.0 }) }) describe('reconnectLoop (T10, INV12)', () => { const fakeTunnel = {} as Tunnel it('resolves on the first successful dial and resets backoff', async () => { const dial = vi.fn().mockRejectedValueOnce(new Error('down')).mockResolvedValueOnce(fakeTunnel) const backoff = createBackoff() const reset = vi.spyOn(backoff, 'reset') const sleeps: number[] = [] const result = await reconnectLoop(dial, backoff, () => false, async (ms) => { sleeps.push(ms) }) expect(result).toBe(fakeTunnel) expect(sleeps).toEqual([1000]) expect(reset).toHaveBeenCalled() }) it('a revoked host does not reconnect (INV12)', async () => { const dial = vi.fn().mockResolvedValue(fakeTunnel) const result = await reconnectLoop(dial, createBackoff(), () => true, async () => {}) expect(dial).not.toHaveBeenCalled() expect(result).toBeNull() }) it('stops retrying once revoked mid-loop', async () => { let revoked = false const dial = vi.fn(async () => { revoked = true throw new Error('down') }) const result = await reconnectLoop(dial, createBackoff(), () => revoked, async () => {}) expect(result).toBeNull() expect(dial).toHaveBeenCalledTimes(1) }) })