Release Multi-Hoster Uploader 3.3.108
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { makeThrottleTimer } = require('../lib/throttle-timer');
|
||||
|
||||
function fakeClock() {
|
||||
let t = 0;
|
||||
let timers = [];
|
||||
return {
|
||||
now: () => t,
|
||||
schedule: (cb, ms) => {
|
||||
const h = { at: t + ms, cb, dead: false };
|
||||
timers.push(h);
|
||||
return h;
|
||||
},
|
||||
clear: (h) => { if (h) h.dead = true; },
|
||||
advance: (ms) => {
|
||||
const target = t + ms;
|
||||
for (;;) {
|
||||
let next = null;
|
||||
for (const h of timers) {
|
||||
if (!h.dead && h.at <= target && (next === null || h.at < next.at)) next = h;
|
||||
}
|
||||
if (!next) break;
|
||||
t = next.at;
|
||||
next.dead = true;
|
||||
next.cb();
|
||||
}
|
||||
t = target;
|
||||
timers = timers.filter(h => !h.dead);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('idle debounce: fires once after the delay window', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
let fired = 0;
|
||||
tt.request(() => fired++, 500);
|
||||
c.advance(499);
|
||||
assert.equal(fired, 0);
|
||||
c.advance(1);
|
||||
assert.equal(fired, 1);
|
||||
});
|
||||
|
||||
test('idle debounce: rapid requests reset the timer (last wins)', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
let fired = 0;
|
||||
tt.request(() => fired++, 500);
|
||||
c.advance(200);
|
||||
tt.request(() => fired++, 500);
|
||||
c.advance(300);
|
||||
assert.equal(fired, 0, 'should not fire at original 500 — was reset');
|
||||
c.advance(200);
|
||||
assert.equal(fired, 1, 'fires 500ms after the second request');
|
||||
});
|
||||
|
||||
test('STARVATION repro: continuous requests with no maxWait NEVER fire', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
let fired = 0;
|
||||
for (let i = 0; i < 30; i++) {
|
||||
tt.request(() => fired++, 500);
|
||||
c.advance(100);
|
||||
}
|
||||
assert.equal(fired, 0, 'this is exactly the bug the maxWait fix addresses');
|
||||
});
|
||||
|
||||
test('maxWait: continuous requests still force a fire within the window', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
let fired = 0;
|
||||
const fireAtTimes = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
tt.request(() => { fired++; fireAtTimes.push(c.now()); }, 500, 2000);
|
||||
c.advance(100);
|
||||
}
|
||||
assert.ok(fired >= 1, 'maxWait guarantees at least one fire under continuous load');
|
||||
assert.ok(fireAtTimes.every(t => t > 0), 'fires happened, not starved');
|
||||
assert.ok(fireAtTimes.some(t => t <= 2000), 'first fire no later than maxWait');
|
||||
});
|
||||
|
||||
test('maxWait: after a forced fire a fresh burst starts (periodic fires)', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
let fired = 0;
|
||||
for (let i = 0; i < 60; i++) {
|
||||
tt.request(() => fired++, 500, 2000);
|
||||
c.advance(100);
|
||||
}
|
||||
assert.ok(fired >= 2, `~6000ms of continuous load with 2000ms maxWait should fire multiple times, got ${fired}`);
|
||||
});
|
||||
|
||||
test('flushSync fires the pending fn immediately and clears it', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
let fired = 0;
|
||||
tt.request(() => fired++, 5000);
|
||||
assert.ok(tt.isPending());
|
||||
tt.flushSync();
|
||||
assert.equal(fired, 1);
|
||||
assert.ok(!tt.isPending());
|
||||
c.advance(10000);
|
||||
assert.equal(fired, 1, 'no double fire after flushSync');
|
||||
});
|
||||
|
||||
test('cancel drops the pending fn — no fire', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
let fired = 0;
|
||||
tt.request(() => fired++, 500);
|
||||
tt.cancel();
|
||||
assert.ok(!tt.isPending());
|
||||
c.advance(10000);
|
||||
assert.equal(fired, 0);
|
||||
});
|
||||
|
||||
test('last-write-wins: a later request with a DIFFERENT fn replaces the earlier one', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
const fired = [];
|
||||
tt.request(() => fired.push('persist'), 500, 20000);
|
||||
c.advance(100);
|
||||
tt.request(() => fired.push('clear'), 0);
|
||||
c.advance(100);
|
||||
assert.deepEqual(fired, ['clear'], 'only the latest fn fires; the persist was dropped');
|
||||
});
|
||||
|
||||
test('flushSync with nothing pending is a no-op', () => {
|
||||
const c = fakeClock();
|
||||
const tt = makeThrottleTimer(c);
|
||||
assert.doesNotThrow(() => tt.flushSync());
|
||||
});
|
||||
Reference in New Issue
Block a user