File size: 4,588 Bytes
f0634fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { SyncDescriptor } from '#/_base/di/descriptors';
import { DisposableStore } from '#/_base/di/lifecycle';
import { TestInstantiationService } from '#/_base/di/test';
import {
ITowerRateLimitService,
type TowerRateLimitSnapshot,
} from '#/features/tower/towerRateLimit';
import {
RATE_LIMIT_CAPACITY_RECOVERY_INTERVAL_MS,
TOWER_MAX_BUDGET,
TOWER_SPAWN_PAUSE_MS,
TowerRateLimitService,
} from '#/features/tower/towerRateLimitService';
describe('TowerRateLimitService', () => {
let disposables: DisposableStore;
let ix: TestInstantiationService;
let now: number;
beforeEach(() => {
now = 1_000_000;
disposables = new DisposableStore();
ix = disposables.add(new TestInstantiationService());
ix.set(ITowerRateLimitService, new SyncDescriptor(TowerRateLimitService, [() => now]));
});
afterEach(() => disposables.dispose());
function snapshot(): TowerRateLimitSnapshot {
return ix.get(ITowerRateLimitService).snapshot();
}
it('starts uncapped at the tower max budget with no pause', () => {
expect(snapshot()).toEqual({ budget: TOWER_MAX_BUDGET, inflight: 0, blockedUntil: null });
});
it('acquire / release track inflight agents', () => {
const limiter = ix.get(ITowerRateLimitService);
expect(limiter.acquire()).toEqual({ ok: true });
expect(limiter.acquire()).toEqual({ ok: true });
expect(snapshot().inflight).toBe(2);
limiter.release();
expect(snapshot().inflight).toBe(1);
limiter.release();
limiter.release();
expect(snapshot().inflight).toBe(0);
});
it('anchors the budget to (inflight - 1) on the first 429 and pauses spawns', () => {
const limiter = ix.get(ITowerRateLimitService);
expect(limiter.acquire().ok).toBe(true);
expect(limiter.acquire().ok).toBe(true);
expect(limiter.acquire().ok).toBe(true);
limiter.reportRateLimited();
expect(snapshot()).toEqual({
budget: 2,
inflight: 3,
blockedUntil: now + TOWER_SPAWN_PAUSE_MS,
});
});
it('refuses new spawns while the post-429 pause is in effect', () => {
const limiter = ix.get(ITowerRateLimitService);
limiter.reportRateLimited();
const gate = limiter.acquire();
expect(gate.ok).toBe(false);
if (gate.ok) throw new Error('expected acquire to be refused');
expect(gate.reason).toContain('new tower spawns paused');
now += TOWER_SPAWN_PAUSE_MS;
expect(limiter.acquire()).toEqual({ ok: true });
});
it('lifts the spawn pause early once a request succeeds', () => {
const limiter = ix.get(ITowerRateLimitService);
limiter.reportRateLimited();
expect(snapshot().blockedUntil).not.toBeNull();
limiter.reportSuccess();
expect(snapshot().blockedUntil).toBeNull();
expect(limiter.acquire()).toEqual({ ok: true });
});
it('refuses spawns when the inflight count reaches the budget', () => {
const limiter = ix.get(ITowerRateLimitService);
expect(limiter.acquire().ok).toBe(true);
limiter.reportRateLimited();
now += TOWER_SPAWN_PAUSE_MS;
const gate = limiter.acquire();
expect(gate.ok).toBe(false);
if (gate.ok) throw new Error('expected acquire to be refused');
expect(gate.reason).toContain('tower concurrency budget exhausted (1/1');
limiter.release();
expect(limiter.acquire()).toEqual({ ok: true });
});
it('recovers capacity by one per quiet window without 429s', () => {
const limiter = ix.get(ITowerRateLimitService);
expect(limiter.acquire().ok).toBe(true);
expect(limiter.acquire().ok).toBe(true);
limiter.reportRateLimited();
expect(snapshot().budget).toBe(1);
now += RATE_LIMIT_CAPACITY_RECOVERY_INTERVAL_MS;
expect(limiter.budget()).toBe(2);
now += RATE_LIMIT_CAPACITY_RECOVERY_INTERVAL_MS;
expect(limiter.budget()).toBe(3);
});
it('never recovers beyond the tower max budget', () => {
const limiter = ix.get(ITowerRateLimitService);
expect(limiter.acquire().ok).toBe(true);
limiter.reportRateLimited();
for (let i = 0; i < TOWER_MAX_BUDGET + 4; i += 1) {
now += RATE_LIMIT_CAPACITY_RECOVERY_INTERVAL_MS;
limiter.budget();
}
expect(snapshot().budget).toBe(TOWER_MAX_BUDGET);
});
it('reset restores the pristine state', () => {
const limiter = ix.get(ITowerRateLimitService);
expect(limiter.acquire().ok).toBe(true);
limiter.reportRateLimited();
limiter.reset();
expect(snapshot()).toEqual({ budget: TOWER_MAX_BUDGET, inflight: 0, blockedUntil: null });
});
});
|