import { describe, it, expect } from 'vitest'; import { addJitter, reconnectDecision, DEFAULT_RECONNECT_POLICY } from '../fleet-connector.js'; describe('addJitter', () => { it('returns a value >= the input delay', () => { const jittered = addJitter(1000); expect(jittered).toBeGreaterThanOrEqual(1000); }); it('returns a value <= 1.5x the input delay', () => { const jittered = addJitter(1000); expect(jittered).toBeLessThanOrEqual(1500); }); it('0ms delay stays 0ms', () => { expect(addJitter(0)).toBe(0); }); it('returns different values on repeated calls (stochastic)', () => { const results = new Set(); for (let i = 0; i < 20; i++) { results.add(addJitter(1000)); } expect(results.size).toBeGreaterThan(1); }); }); describe('reconnectDecision', () => { it('first failure returns baseMs with jitter', () => { const decision = reconnectDecision(1); expect(decision.action).toBe('reconnect'); expect(decision.delayMs).toBeGreaterThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs); expect(decision.delayMs).toBeLessThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 1.5); }); it('exponential growth: failure 2 returns 2x baseMs with jitter', () => { const decision = reconnectDecision(2); expect(decision.action).toBe('reconnect'); expect(decision.delayMs).toBeGreaterThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 2); expect(decision.delayMs).toBeLessThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 3); }); it('exponential growth: failure 3 returns 4x baseMs with jitter', () => { const decision = reconnectDecision(3); expect(decision.action).toBe('reconnect'); expect(decision.delayMs).toBeGreaterThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 4); expect(decision.delayMs).toBeLessThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 6); }); it('capped at maxMs with jitter', () => { const decision = reconnectDecision(6); expect(decision.action).toBe('reconnect'); expect(decision.delayMs).toBeGreaterThanOrEqual(DEFAULT_RECONNECT_POLICY.maxMs); expect(decision.delayMs).toBeLessThanOrEqual(DEFAULT_RECONNECT_POLICY.maxMs * 1.5); }); it('gives up after maxAttempts', () => { const decision = reconnectDecision(DEFAULT_RECONNECT_POLICY.maxAttempts + 1); expect(decision).toEqual({ action: 'give-up' }); }); it('custom policy works with jitter', () => { const policy = { baseMs: 500, maxMs: 5000, maxAttempts: 3 }; const d1 = reconnectDecision(1, policy); expect(d1.action).toBe('reconnect'); expect(d1.delayMs).toBeGreaterThanOrEqual(500); expect(d1.delayMs).toBeLessThanOrEqual(750); const d2 = reconnectDecision(2, policy); expect(d2.action).toBe('reconnect'); expect(d2.delayMs).toBeGreaterThanOrEqual(1000); expect(d2.delayMs).toBeLessThanOrEqual(1500); const d3 = reconnectDecision(3, policy); expect(d3.action).toBe('reconnect'); expect(d3.delayMs).toBeGreaterThanOrEqual(2000); expect(d3.delayMs).toBeLessThanOrEqual(3000); const d4 = reconnectDecision(4, policy); expect(d4).toEqual({ action: 'give-up' }); }); });