import { test } from "node:test"; import assert from "node:assert/strict"; import { esperaDeToken, registrarPeticion, MIN_INTERVAL_MS, anotarLimites, limitesDe, intervaloDe, } from "./client.ts"; test("el estrangulador cuenta por token, no globalmente", () => { const ahora = 1_000_000; registrarPeticion("token-A", ahora); // El mismo token tiene que esperar… assert.ok(esperaDeToken("token-A", ahora + 10) > 0); // …pero otro token no espera nada: su límite es independiente. assert.equal(esperaDeToken("token-B", ahora + 10), 0); }); test("pasado el intervalo, el mismo token deja de esperar", () => { const ahora = 2_000_000; registrarPeticion("token-C", ahora); assert.equal(esperaDeToken("token-C", ahora + MIN_INTERVAL_MS + 1), 0); }); test("sin cabeceras se usa el intervalo conservador por defecto", () => { assert.equal(intervaloDe("token-sin-datos"), MIN_INTERVAL_MS); }); test("las cabeceras del CRM mandan sobre el valor por defecto", () => { // MEDIDO (hallazgo 37): la cuota real de la subcuenta. anotarLimites( "token-D", new Headers({ "x-ratelimit-max": "100", "x-ratelimit-interval-milliseconds": "10000", "x-ratelimit-remaining": "94", "x-ratelimit-daily-remaining": "199970", }) ); const l = limitesDe("token-D"); assert.equal(l?.max, 100); assert.equal(l?.ventanaMs, 10000); assert.equal(l?.diarioRestante, 199970); // 10000/100 = 100 ms teóricos, con 50 % de margen = 150 assert.equal(intervaloDe("token-D"), 150); }); test("con la ventana casi agotada se espacia más, para no comerse un 429", () => { anotarLimites( "token-E", new Headers({ "x-ratelimit-max": "100", "x-ratelimit-interval-milliseconds": "10000", "x-ratelimit-remaining": "3", }) ); assert.ok(intervaloDe("token-E") > 150); }); test("una respuesta sin cabeceras de límite no borra lo que ya se sabía", () => { anotarLimites( "token-F", new Headers({ "x-ratelimit-max": "100", "x-ratelimit-interval-milliseconds": "10000" }) ); anotarLimites("token-F", new Headers({})); assert.equal(limitesDe("token-F")?.max, 100); });