Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
+89
View File
@@ -0,0 +1,89 @@
const { createLimiter, createCircuitBreaker, API_BUDGETS } = require('../../src/utils/rateLimiter');
describe('createLimiter', () => {
test('first N tokens within capacity resolve immediately', async () => {
const limiter = createLimiter({ tokensPerInterval: 3, interval: 60_000 });
const start = Date.now();
expect(await limiter.waitForToken()).toBe(true);
expect(await limiter.waitForToken()).toBe(true);
expect(await limiter.waitForToken()).toBe(true);
expect(Date.now() - start).toBeLessThan(100);
});
test('fourth token in a small bucket waits for refill', async () => {
const limiter = createLimiter({ tokensPerInterval: 2, interval: 200 });
expect(await limiter.waitForToken()).toBe(true);
expect(await limiter.waitForToken()).toBe(true);
const start = Date.now();
expect(await limiter.waitForToken(2_000)).toBe(true);
const elapsed = Date.now() - start;
// Should take roughly interval/tokens = 100ms before refill yields one.
expect(elapsed).toBeGreaterThan(50);
expect(elapsed).toBeLessThan(500);
});
test('timeout returns false (does not throw) so caller can proceed', async () => {
const limiter = createLimiter({ tokensPerInterval: 1, interval: 60_000 });
expect(await limiter.waitForToken()).toBe(true); // consume the one token
const result = await limiter.waitForToken(120); // wait briefly, then bail
expect(result).toBe(false);
});
test('throws on invalid config', () => {
expect(() => createLimiter({ tokensPerInterval: 0, interval: 1000 })).toThrow();
expect(() => createLimiter({ tokensPerInterval: 1, interval: 0 })).toThrow();
});
});
describe('createCircuitBreaker', () => {
test('closed initially; success resets failure count', async () => {
const cb = createCircuitBreaker({ failureThreshold: 2, resetTimeout: 60_000 });
const result = await cb.call(async () => 'ok');
expect(result).toBe('ok');
expect(cb.snapshot().state).toBe('closed');
});
test('opens after threshold failures and rejects further calls', async () => {
const cb = createCircuitBreaker({ failureThreshold: 2, resetTimeout: 60_000 });
await expect(cb.call(async () => { throw new Error('boom'); })).rejects.toThrow('boom');
await expect(cb.call(async () => { throw new Error('boom'); })).rejects.toThrow('boom');
expect(cb.snapshot().state).toBe('open');
await expect(cb.call(async () => 'should never run')).rejects.toMatchObject({ code: 'CIRCUIT_OPEN' });
});
test('transitions to half-open after resetTimeout, closes on success', async () => {
const cb = createCircuitBreaker({ failureThreshold: 1, resetTimeout: 50 });
await expect(cb.call(async () => { throw new Error('x'); })).rejects.toThrow();
expect(cb.snapshot().state).toBe('open');
await new Promise((r) => setTimeout(r, 80));
// Reading the snapshot triggers the transition check.
expect(cb.snapshot().state).toBe('half_open');
const result = await cb.call(async () => 'recovered');
expect(result).toBe('recovered');
expect(cb.snapshot().state).toBe('closed');
});
test('half-open failure re-opens the circuit immediately', async () => {
const cb = createCircuitBreaker({ failureThreshold: 1, resetTimeout: 30 });
await expect(cb.call(async () => { throw new Error('first'); })).rejects.toThrow();
await new Promise((r) => setTimeout(r, 60));
await expect(cb.call(async () => { throw new Error('second'); })).rejects.toThrow('second');
expect(cb.snapshot().state).toBe('open');
});
});
describe('API_BUDGETS', () => {
test('contains the five named upstreams', () => {
expect(API_BUDGETS).toMatchObject({
sharpApi: { tokensPerInterval: 10, interval: 60_000 },
espn: { tokensPerInterval: 2, interval: 60_000 },
mlbStats: { tokensPerInterval: 2, interval: 60_000 },
oddsPapi: { tokensPerInterval: 5, interval: 60_000 },
openRouter: { tokensPerInterval: 15, interval: 60_000 },
});
});
test('is frozen — adapters cannot mutate it accidentally', () => {
expect(Object.isFrozen(API_BUDGETS)).toBe(true);
});
});