pithos/test/autoflush.test.js

89 lines
3.1 KiB
JavaScript
Raw Normal View History

// The leftover-flush watcher, driven with a fake clock and fake s3d stats.
import test from 'node:test';
import assert from 'node:assert/strict';
import { createAutoFlush, clampMinutes } from '../core/autoflush.js';
function rig({ enabled = true, minutes = 5 } = {}) {
const r = { t: 0, pending: { pendingObjects: 0, pendingSize: 0 }, flushes: 0, fail: null, prefs: { enabled, minutes }, down: false };
r.af = createAutoFlush({
settings: () => r.prefs,
stats: async () => { if (r.down) throw new Error('refused'); return { ...r.pending }; },
flush: async () => {
if (r.fail) throw new Error(r.fail);
r.flushes++;
r.pending = { pendingObjects: 0, pendingSize: 0 };
},
now: () => r.t,
});
r.at = async (min) => { r.t = min * 60_000; await r.af.tick(); };
return r;
}
test('flushes once the pending set has been unchanged for N minutes', async () => {
const r = rig({ minutes: 5 });
r.pending = { pendingObjects: 3, pendingSize: 1000 };
await r.at(0);
await r.at(4.9);
assert.equal(r.flushes, 0);
assert.equal(r.af.status().dueAt, 5 * 60_000);
await r.at(5);
assert.equal(r.flushes, 1);
assert.equal(r.af.status().lastFlushAt, 5 * 60_000);
await r.at(20);
assert.equal(r.flushes, 1, 'nothing pending, nothing to flush');
});
test('a new upload restarts the quiet period', async () => {
const r = rig({ minutes: 5 });
r.pending = { pendingObjects: 1, pendingSize: 10 };
await r.at(0);
await r.at(4);
r.pending = { pendingObjects: 2, pendingSize: 20 };
await r.at(4.5);
await r.at(9);
assert.equal(r.flushes, 0);
await r.at(9.5);
assert.equal(r.flushes, 1);
});
test('off means never, and s3d being down is not an error', async () => {
const r = rig({ enabled: false });
r.pending = { pendingObjects: 5, pendingSize: 50 };
for (const m of [0, 10, 100]) await r.at(m);
assert.equal(r.flushes, 0);
assert.equal(r.af.status().dueAt, null);
r.prefs.enabled = true;
r.down = true;
for (const m of [101, 200]) await r.at(m);
assert.equal(r.flushes, 0);
});
test('failed flushes back off, doubling up to an hour', async () => {
const r = rig({ minutes: 2 });
r.pending = { pendingObjects: 1, pendingSize: 1 };
r.fail = 'indexer unreachable';
await r.at(0);
await r.at(2); // fails; next try 4 min later
assert.equal(r.af.status().lastError, 'indexer unreachable');
assert.equal(r.af.status().dueAt, 6 * 60_000);
await r.at(6); // fails again; next try 8 min later
assert.equal(r.af.status().dueAt, 14 * 60_000);
for (let i = 0; i < 6; i++) await r.at(r.af.status().dueAt / 60_000);
assert.equal(r.af.status().dueAt - r.t, 60 * 60_000, 'capped at an hour');
r.fail = null;
await r.at(r.af.status().dueAt / 60_000);
assert.equal(r.flushes, 1);
assert.equal(r.af.status().lastError, null);
r.pending = { pendingObjects: 1, pendingSize: 2 };
await r.at(r.t / 60_000);
assert.equal(r.af.status().dueAt - r.t, 2 * 60_000, 'a success resets the backoff');
});
test('minutes are clamped to a sane range', () => {
assert.equal(clampMinutes(0), 1);
assert.equal(clampMinutes(99999), 1440);
assert.equal(clampMinutes('abc'), 10);
assert.equal(clampMinutes('7'), 7);
});