Files
boocode/apps/control/src/services/__tests__/fleet-connector.test.ts
indifferentketchup b18de2a331 chore: snapshot working tree - pty_exited notifications + in-flight inference WIP
feat(booterm): structured pty_exited WS notifications. Plan-validated, impl-validated, code-reviewed green (contracts build clean, contracts test 29/29, booterm + web typecheck clean).

wip: in-progress inference/provider refactor (agents.ts, provider.ts, new llama-providers.ts, removed llama-args-validator), plus arena, dispatcher, compaction, schema changes.

openspec: pty-exit-notifications complete; x-agent-flags planned (not yet implemented).
2026-06-14 12:48:47 +00:00

83 lines
3.1 KiB
TypeScript

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<number>();
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' });
});
});