Files

121 lines
5.0 KiB
JavaScript

// Tests unitaires de backend/src/utils/Limiter.js (node limiter.test.js)
const assert = require("assert");
const path = require("path").resolve(__dirname, "../../src/utils/Limiter.js");
const { createLimiter, mapLimit, createInflight, createCache } = require(path);
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const tests = [];
const test = (name, fn) => tests.push({ name, fn });
test("limiter: concurrence max respectée et résultats corrects", async () => {
const lim = createLimiter(2);
let active = 0, peak = 0;
const res = await Promise.all([1, 2, 3, 4, 5, 6].map(i => lim.run(async () => {
active++; peak = Math.max(peak, active);
await sleep(20);
active--;
return i * 10;
})));
assert.deepStrictEqual(res, [10, 20, 30, 40, 50, 60]);
assert.strictEqual(peak, 2);
assert.strictEqual(lim.active, 0);
assert.strictEqual(lim.pending, 0);
});
test("limiter: erreurs (async et sync) propagées sans bloquer la file", async () => {
const lim = createLimiter(1);
await assert.rejects(lim.run(async () => { throw new Error("a"); }), /a/);
await assert.rejects(lim.run(() => { throw new Error("b"); }), /b/);
assert.strictEqual(await lim.run(() => 42), 42);
});
test("limiter: minIntervalMs espace les démarrages", async () => {
const lim = createLimiter(5, { minIntervalMs: 30 });
const starts = [];
await Promise.all([0, 1, 2, 3].map(() => lim.run(async () => { starts.push(Date.now()); })));
for (let i = 1; i < starts.length; i++) assert.ok(starts[i] - starts[i - 1] >= 25, "écart " + (starts[i] - starts[i - 1]));
});
test("mapLimit: ordre conservé, concurrence bornée, erreurs ignorées", async () => {
let active = 0, peak = 0;
const out = await mapLimit([30, 5, 20, 1, 10], 3, async (ms, i) => {
active++; peak = Math.max(peak, active);
await sleep(ms);
active--;
if (i === 2) throw new Error("x");
return ms;
}, () => null);
assert.deepStrictEqual(out, [30, 5, null, 1, 10]);
assert.strictEqual(peak, 3);
assert.deepStrictEqual(await mapLimit(null, 2, async x => x), []);
});
test("inflight: appels simultanés dédupliqués, libérés ensuite", async () => {
const inf = createInflight();
let calls = 0;
const fn = async () => { calls++; await sleep(10); return "v"; };
const [a, b] = await Promise.all([inf.run("k", fn), inf.run("k", fn)]);
assert.strictEqual(a, "v"); assert.strictEqual(b, "v"); assert.strictEqual(calls, 1);
assert.strictEqual(inf.size, 0);
await inf.run("k", fn);
assert.strictEqual(calls, 2);
await assert.rejects(inf.run("e", async () => { throw new Error("boom"); }));
assert.strictEqual(inf.size, 0);
});
test("cache: TTL, null explicite, expiration", async () => {
const c = createCache({ ttlMs: 40, max: 10 });
c.set("a", 1);
c.set("n", null);
assert.strictEqual(c.get("a"), 1);
assert.strictEqual(c.get("n"), null);
assert.strictEqual(c.get("absent"), undefined);
c.set("z", 1, 0); // TTL 0 → pas mis en cache
assert.strictEqual(c.get("z"), undefined);
await sleep(60);
assert.strictEqual(c.get("a"), undefined);
assert.strictEqual(c.size, 1); // "n" expiré mais pas encore lu
});
test("cache: taille max et éviction LRU", () => {
const c = createCache({ ttlMs: 10000, max: 3 });
c.set("a", 1); c.set("b", 2); c.set("c", 3);
c.get("a"); // a devient le plus récent
c.set("d", 4);
assert.strictEqual(c.size, 3);
assert.strictEqual(c.get("b"), undefined);
assert.strictEqual(c.get("a"), 1);
assert.strictEqual(c.get("d"), 4);
});
test("cache.wrap: hit, dédup en cours, null non mis en cache, ttlFor", async () => {
const c = createCache({ ttlMs: 10000, max: 10 });
let calls = 0;
const fn = async () => { calls++; await sleep(10); return { x: 1 }; };
const [a, b] = await Promise.all([c.wrap("k", fn), c.wrap("k", fn)]);
assert.strictEqual(a, b); assert.strictEqual(calls, 1);
await c.wrap("k", fn);
assert.strictEqual(calls, 1);
let nulls = 0;
await c.wrap("n", async () => { nulls++; return null; });
await c.wrap("n", async () => { nulls++; return null; });
assert.strictEqual(nulls, 2);
await c.wrap("m", async () => { nulls++; return null; }, { ttlFor: v => v === null ? 5000 : 10000 });
await c.wrap("m", async () => { nulls++; return null; }, { ttlFor: v => v === null ? 5000 : 10000 });
assert.strictEqual(nulls, 3);
await assert.rejects(c.wrap("e", async () => { throw new Error("err"); }));
assert.strictEqual(c.get("e"), undefined);
const s = c.stats();
assert.ok(s.hits >= 2 && s.misses >= 4, JSON.stringify(s));
});
(async () => {
let failed = 0;
for (const t of tests) {
try { await t.fn(); console.log("OK " + t.name); }
catch (e) { failed++; console.log("FAIL " + t.name + "\n " + (e && e.stack || e)); }
}
console.log(`\n${tests.length - failed}/${tests.length} réussis`);
process.exit(failed ? 1 : 0);
})();