Files
2026-07-13 13:39:12 +08:00

114 lines
4.0 KiB
TypeScript

/**
* tests/unit/quota-burn-rate.test.ts
*
* Coverage for src/lib/quota/burnRate.ts:
* - Empty history returns zeros
* - Linear-rate sequence approximates correctly
* - timeToExhaustionMs computed when remaining provided
* - Zero-rate (no consumption) → null exhaustion
*/
import test from "node:test";
import assert from "node:assert/strict";
const { computeBurnRate } = await import("../../src/lib/quota/burnRate.ts");
// ---------------------------------------------------------------------------
// Edge cases
// ---------------------------------------------------------------------------
test("computeBurnRate: empty history → zeros", () => {
const result = computeBurnRate([]);
assert.equal(result.tokensPerSecond, 0);
assert.equal(result.timeToExhaustionMs, null);
});
test("computeBurnRate: single sample → zeros", () => {
const result = computeBurnRate([{ ts: 1000, consumed: 100 }]);
assert.equal(result.tokensPerSecond, 0);
assert.equal(result.timeToExhaustionMs, null);
});
// ---------------------------------------------------------------------------
// Linear consumption rate
// ---------------------------------------------------------------------------
test("computeBurnRate: constant 10 t/s over 5 samples → tokensPerSecond ≈ 10", () => {
// Each sample adds 10 tokens per second over 1 second intervals
const base = Date.now();
const history = [
{ ts: base, consumed: 0 },
{ ts: base + 1000, consumed: 10 },
{ ts: base + 2000, consumed: 20 },
{ ts: base + 3000, consumed: 30 },
{ ts: base + 4000, consumed: 40 },
];
const result = computeBurnRate(history);
// EMA converges but with alpha=0.3 over 4 deltas (all 10 t/s), the result
// should be very close to 10.
assert.ok(result.tokensPerSecond > 9, `Expected rate > 9, got ${result.tokensPerSecond}`);
assert.ok(result.tokensPerSecond < 11, `Expected rate < 11, got ${result.tokensPerSecond}`);
});
test("computeBurnRate: remaining=100, rate=10 → timeToExhaustionMs ≈ 10000", () => {
const base = Date.now();
const history = [
{ ts: base, consumed: 0 },
{ ts: base + 1000, consumed: 10 },
{ ts: base + 2000, consumed: 20 },
{ ts: base + 3000, consumed: 30 },
{ ts: base + 4000, consumed: 40 },
];
const result = computeBurnRate(history, 100);
assert.notEqual(result.timeToExhaustionMs, null);
// Should be close to 10000ms (10s), allow ±10% tolerance
assert.ok(
result.timeToExhaustionMs! > 9000,
`Expected >9000ms, got ${result.timeToExhaustionMs}`
);
assert.ok(
result.timeToExhaustionMs! < 11000,
`Expected <11000ms, got ${result.timeToExhaustionMs}`
);
});
// ---------------------------------------------------------------------------
// Zero rate
// ---------------------------------------------------------------------------
test("computeBurnRate: no consumption → tokensPerSecond=0, timeToExhaustionMs=null", () => {
const base = Date.now();
const history = [
{ ts: base, consumed: 100 },
{ ts: base + 1000, consumed: 100 }, // no change
{ ts: base + 2000, consumed: 100 },
];
const result = computeBurnRate(history, 500);
assert.equal(result.tokensPerSecond, 0);
assert.equal(result.timeToExhaustionMs, null);
});
test("computeBurnRate: no remaining provided → timeToExhaustionMs=null even with non-zero rate", () => {
const base = Date.now();
const history = [
{ ts: base, consumed: 0 },
{ ts: base + 1000, consumed: 10 },
];
const result = computeBurnRate(history);
// Rate should be positive but no remaining given
assert.ok(result.tokensPerSecond > 0);
assert.equal(result.timeToExhaustionMs, null);
});
test("computeBurnRate: duplicate timestamps are skipped gracefully", () => {
const base = Date.now();
const history = [
{ ts: base, consumed: 0 },
{ ts: base, consumed: 10 }, // same ts — should be skipped
{ ts: base + 1000, consumed: 20 },
];
// Should not throw and should compute valid rate for the one valid delta
const result = computeBurnRate(history);
assert.ok(result.tokensPerSecond >= 0);
});