324 lines
13 KiB
TypeScript
324 lines
13 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
getModelRpd,
|
|
getModelRpm,
|
|
incrementRequestCount,
|
|
getDailyRequestCount,
|
|
getMinuteRequestCount,
|
|
isRpdExhausted,
|
|
isRpmExhausted,
|
|
resetCounters,
|
|
} from "../../../open-sse/services/geminiRateLimitTracker.ts";
|
|
|
|
test.beforeEach(() => {
|
|
resetCounters();
|
|
});
|
|
|
|
// ── getModelRpd ──────────────────────────────────────────────────────────────
|
|
|
|
test("getModelRpd returns known RPD for exact model match", () => {
|
|
assert.equal(getModelRpd("gemini-2.5-flash"), 20);
|
|
});
|
|
|
|
test("getModelRpd returns known RPD for model with gemini/ prefix", () => {
|
|
assert.equal(getModelRpd("gemini/gemini-2.5-flash"), 20);
|
|
});
|
|
|
|
test("getModelRpd returns 0 for model with zero RPD (Gemini 2.5 Pro)", () => {
|
|
assert.equal(getModelRpd("gemini-2.5-pro"), 0);
|
|
});
|
|
|
|
test("getModelRpd returns 0 for unknown model", () => {
|
|
assert.equal(getModelRpd("gemini/fake-model-not-in-list"), 0);
|
|
});
|
|
|
|
test("getModelRpd returns 0 for empty string", () => {
|
|
assert.equal(getModelRpd(""), 0);
|
|
});
|
|
|
|
test("getModelRpd matches gemma-4-31b-it via suffix fallback", () => {
|
|
// The JSON has "gemma-4-31b-it", and callers may pass "gemma-4-31b-it"
|
|
assert.equal(getModelRpd("gemma-4-31b-it"), 1500);
|
|
});
|
|
|
|
test("getModelRpd strips gemma- prefix correctly for gemma models", () => {
|
|
// stripModelPrefix("gemini/gemma-4-31b-it") → "gemma-4-31b-it"
|
|
assert.equal(getModelRpd("gemini/gemma-4-31b-it"), 1500);
|
|
});
|
|
|
|
test("getModelRpd handles image-generation models (no RPM value, -1)", () => {
|
|
// RPD is 25 for imagen models; RPM is -1 in the JSON
|
|
assert.equal(getModelRpd("imagen-4-generate"), 25);
|
|
});
|
|
|
|
test("getModelRpd handles models with unlimited RPD (-1)", () => {
|
|
// gemini-3.5-live-translate has rpd: -1
|
|
assert.equal(getModelRpd("gemini-3.5-live-translate"), 0);
|
|
});
|
|
|
|
test("getModelRpd returns 0 for null input", () => {
|
|
assert.equal(getModelRpd(null as unknown as string), 0);
|
|
});
|
|
|
|
test("getModelRpd returns 0 for undefined input", () => {
|
|
assert.equal(getModelRpd(undefined as unknown as string), 0);
|
|
});
|
|
|
|
// ── incrementRequestCount / getDailyRequestCount ─────────────────────────────
|
|
|
|
test("incrementRequestCount starts at 1 for first request", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(getDailyRequestCount("gemini-2.5-flash"), 1);
|
|
});
|
|
|
|
test("incrementRequestCount increments sequentially", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(getDailyRequestCount("gemini-2.5-flash"), 3);
|
|
});
|
|
|
|
test("incrementRequestCount treats gemini/ prefix and bare name as same model", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemini/gemini-2.5-flash");
|
|
assert.equal(getDailyRequestCount("gemini-2.5-flash"), 2);
|
|
assert.equal(getDailyRequestCount("gemini/gemini-2.5-flash"), 2);
|
|
});
|
|
|
|
test("models have independent counters", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemma-4-31b-it");
|
|
assert.equal(getDailyRequestCount("gemini-2.5-flash"), 2);
|
|
assert.equal(getDailyRequestCount("gemma-4-31b-it"), 1);
|
|
});
|
|
|
|
test("getDailyRequestCount returns 0 for model with no requests", () => {
|
|
assert.equal(getDailyRequestCount("gemini-2.5-flash"), 0);
|
|
});
|
|
|
|
test("incrementRequestCount does nothing for empty model ID", () => {
|
|
incrementRequestCount("");
|
|
assert.equal(getDailyRequestCount("gemini-2.5-flash"), 0);
|
|
});
|
|
|
|
test("resetCounters clears all state between tests", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemma-4-31b-it");
|
|
resetCounters();
|
|
assert.equal(getDailyRequestCount("gemini-2.5-flash"), 0);
|
|
assert.equal(getDailyRequestCount("gemma-4-31b-it"), 0);
|
|
});
|
|
|
|
// ── isRpdExhausted ───────────────────────────────────────────────────────────
|
|
|
|
test("isRpdExhausted returns false when count is below RPD limit", () => {
|
|
// gemini-2.5-flash has RPD=20
|
|
for (let i = 0; i < 19; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpdExhausted("gemini-2.5-flash"), false);
|
|
});
|
|
|
|
test("isRpdExhausted returns true when count equals RPD limit", () => {
|
|
// gemini-2.5-flash has RPD=20
|
|
for (let i = 0; i < 20; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpdExhausted("gemini-2.5-flash"), true);
|
|
});
|
|
|
|
test("isRpdExhausted returns true when count exceeds RPD limit", () => {
|
|
for (let i = 0; i < 25; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpdExhausted("gemini-2.5-flash"), true);
|
|
});
|
|
|
|
test("isRpdExhausted returns false for model with RPD=0", () => {
|
|
// gemini-2.5-pro has rpd=0
|
|
incrementRequestCount("gemini-2.5-pro");
|
|
assert.equal(isRpdExhausted("gemini-2.5-pro"), false);
|
|
});
|
|
|
|
test("isRpdExhausted returns false for unknown model", () => {
|
|
incrementRequestCount("gemini/unknown-model");
|
|
assert.equal(isRpdExhausted("gemini/unknown-model"), false);
|
|
});
|
|
|
|
test("isRpdExhausted returns false for model with unlimited RPD (-1, no data)", () => {
|
|
// gemini-3.5-live-translate has rpd: -1
|
|
incrementRequestCount("gemini-3.5-live-translate");
|
|
assert.equal(isRpdExhausted("gemini-3.5-live-translate"), false);
|
|
});
|
|
|
|
test("isRpdExhausted works with gemini/ prefix for the model", () => {
|
|
for (let i = 0; i < 20; i++) incrementRequestCount("gemini/gemini-2.5-flash");
|
|
assert.equal(isRpdExhausted("gemini/gemini-2.5-flash"), true);
|
|
});
|
|
|
|
// ── Integration: Gemma 4 RPM scenario ────────────────────────────────────────
|
|
|
|
test("Gemma 4: 15 RPM hits never trigger quota_exhausted (RPD=1500)", () => {
|
|
// Gemma 4 has RPD=1500, so 15 RPM hits should not trigger RPD exhaustion
|
|
for (let i = 0; i < 15; i++) incrementRequestCount("gemini/gemma-4-31b-it");
|
|
assert.equal(isRpdExhausted("gemini/gemma-4-31b-it"), false);
|
|
assert.equal(getDailyRequestCount("gemini/gemma-4-31b-it"), 15);
|
|
});
|
|
|
|
test("Gemma 4: RPD exhaustion requires 1500 requests", () => {
|
|
for (let i = 0; i < 1499; i++) incrementRequestCount("gemini/gemma-4-31b-it");
|
|
assert.equal(isRpdExhausted("gemini/gemma-4-31b-it"), false);
|
|
|
|
incrementRequestCount("gemini/gemma-4-31b-it");
|
|
assert.equal(isRpdExhausted("gemini/gemma-4-31b-it"), true);
|
|
});
|
|
|
|
// ── Integration: Gemini 2.5 Flash RPD scenario ───────────────────────────────
|
|
|
|
test("Gemini 2.5 Flash: first 19 requests do NOT exhaust RPD, 20th does", () => {
|
|
for (let i = 0; i < 19; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpdExhausted("gemini-2.5-flash"), false, "19 requests < 20 RPD");
|
|
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpdExhausted("gemini-2.5-flash"), true, "20 requests = 20 RPD");
|
|
});
|
|
|
|
test("Gemini 2.5 Flash: excess requests stay exhausted", () => {
|
|
for (let i = 0; i < 22; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpdExhausted("gemini-2.5-flash"), true);
|
|
assert.equal(getDailyRequestCount("gemini-2.5-flash"), 22);
|
|
});
|
|
|
|
// ── getModelRpm ───────────────────────────────────────────────────────────────
|
|
|
|
test("getModelRpm returns known RPM for exact model match", () => {
|
|
assert.equal(getModelRpm("gemini-2.5-flash"), 5);
|
|
});
|
|
|
|
test("getModelRpm returns known RPM for model with gemini/ prefix", () => {
|
|
assert.equal(getModelRpm("gemini/gemini-2.5-flash"), 5);
|
|
});
|
|
|
|
test("getModelRpm returns 0 for model with zero RPM", () => {
|
|
assert.equal(getModelRpm("gemini-2.5-pro"), 0);
|
|
});
|
|
|
|
test("getModelRpm returns 0 for unknown model", () => {
|
|
assert.equal(getModelRpm("gemini/fake-model-not-in-list"), 0);
|
|
});
|
|
|
|
test("getModelRpm returns 0 for empty string", () => {
|
|
assert.equal(getModelRpm(""), 0);
|
|
});
|
|
|
|
test("getModelRpm returns 0 for models with RPM=-1 (imagen)", () => {
|
|
assert.equal(getModelRpm("imagen-4-generate"), 0);
|
|
});
|
|
|
|
test("getModelRpm returns 0 for null input", () => {
|
|
assert.equal(getModelRpm(null as unknown as string), 0);
|
|
});
|
|
|
|
// ── incrementMinuteRequestCount / getMinuteRequestCount ───────────────────────
|
|
|
|
test("getMinuteRequestCount returns 0 before first request", () => {
|
|
assert.equal(getMinuteRequestCount("gemini-2.5-flash"), 0);
|
|
});
|
|
|
|
test("incrementMinuteRequestCount starts at 1", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(getMinuteRequestCount("gemini-2.5-flash"), 1);
|
|
});
|
|
|
|
test("incrementMinuteRequestCount increments with each call", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(getMinuteRequestCount("gemini-2.5-flash"), 3);
|
|
});
|
|
|
|
test("incrementMinuteRequestCount normalizes gemini/ prefix", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemini/gemini-2.5-flash");
|
|
assert.equal(getMinuteRequestCount("gemini-2.5-flash"), 2);
|
|
});
|
|
|
|
test("minute counters are independent per model", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemma-4-31b-it");
|
|
assert.equal(getMinuteRequestCount("gemini-2.5-flash"), 2);
|
|
assert.equal(getMinuteRequestCount("gemma-4-31b-it"), 1);
|
|
});
|
|
|
|
test("incrementRequestCount does nothing for empty model ID", () => {
|
|
incrementRequestCount("");
|
|
assert.equal(getMinuteRequestCount("gemini-2.5-flash"), 0);
|
|
});
|
|
|
|
test("resetCounters clears minute windows", () => {
|
|
incrementRequestCount("gemini-2.5-flash");
|
|
incrementRequestCount("gemma-4-31b-it");
|
|
resetCounters();
|
|
assert.equal(getMinuteRequestCount("gemini-2.5-flash"), 0);
|
|
assert.equal(getMinuteRequestCount("gemma-4-31b-it"), 0);
|
|
});
|
|
|
|
// ── isRpmExhausted ────────────────────────────────────────────────────────────
|
|
|
|
test("isRpmExhausted returns false below RPM limit", () => {
|
|
// gemini-2.5-flash has RPM=5
|
|
for (let i = 0; i < 4; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpmExhausted("gemini-2.5-flash"), false);
|
|
});
|
|
|
|
test("isRpmExhausted returns true at RPM limit", () => {
|
|
for (let i = 0; i < 5; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpmExhausted("gemini-2.5-flash"), true);
|
|
});
|
|
|
|
test("isRpmExhausted returns true above RPM limit", () => {
|
|
for (let i = 0; i < 8; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpmExhausted("gemini-2.5-flash"), true);
|
|
});
|
|
|
|
test("isRpmExhausted returns false for model with RPM=0", () => {
|
|
incrementRequestCount("gemini-2.5-pro");
|
|
assert.equal(isRpmExhausted("gemini-2.5-pro"), false);
|
|
});
|
|
|
|
test("isRpmExhausted returns false for unknown model", () => {
|
|
incrementRequestCount("gemini/unknown-model");
|
|
assert.equal(isRpmExhausted("gemini/unknown-model"), false);
|
|
});
|
|
|
|
test("isRpmExhausted returns false for model with RPM=-1 (imagen)", () => {
|
|
incrementRequestCount("imagen-4-generate");
|
|
assert.equal(isRpmExhausted("imagen-4-generate"), false);
|
|
});
|
|
|
|
test("isRpmExhausted works with gemini/ prefix", () => {
|
|
for (let i = 0; i < 5; i++) incrementRequestCount("gemini/gemini-2.5-flash");
|
|
assert.equal(isRpmExhausted("gemini/gemini-2.5-flash"), true);
|
|
});
|
|
|
|
// ── Integration: RPM + RPD work independently ─────────────────────────────────
|
|
|
|
test("RPM and RPD limits are tracked independently", () => {
|
|
// Gemma 4: RPM=15, RPD=1500
|
|
// After 15 requests, RPM is exhausted but RPD is not
|
|
for (let i = 0; i < 15; i++) incrementRequestCount("gemini/gemma-4-31b-it");
|
|
assert.equal(isRpmExhausted("gemini/gemma-4-31b-it"), true);
|
|
assert.equal(isRpdExhausted("gemini/gemma-4-31b-it"), false);
|
|
});
|
|
|
|
test("Gemini 2.5 Flash: RPM=5 always hits before RPD=20", () => {
|
|
// After 5 requests, RPM is exhausted; after 20, RPD is exhausted
|
|
for (let i = 0; i < 5; i++) incrementRequestCount("gemini-2.5-flash");
|
|
assert.equal(isRpmExhausted("gemini-2.5-flash"), true);
|
|
assert.equal(isRpdExhausted("gemini-2.5-flash"), false);
|
|
|
|
incrementRequestCount("gemini-2.5-flash"); // 6
|
|
incrementRequestCount("gemini-2.5-flash"); // 7
|
|
incrementRequestCount("gemini-2.5-flash"); // 8
|
|
|
|
// Still at RPD=8 which is < 20
|
|
assert.equal(isRpdExhausted("gemini-2.5-flash"), false);
|
|
});
|