2026-10-16 07:20:44 +02:00
|
|
|
import test from "node:test";
|
|
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import { Limits } from "../src/Limits.js";
|
|
|
|
|
import { LIMITS, REFUSALS } from "../src/spec.js";
|
|
|
|
|
|
|
|
|
|
const MB = 1024 * 1024;
|
|
|
|
|
|
|
|
|
|
function at(hour, minute = 0) {
|
|
|
|
|
return () => new Date(2026, 0, 15, hour, minute);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test("defaults come from the baked spec", () => {
|
|
|
|
|
const limits = new Limits();
|
|
|
|
|
assert.equal(limits.concurrency, LIMITS.max_concurrent_per_node);
|
|
|
|
|
assert.equal(limits.bandwidthBytes, LIMITS.default_donor_mb_per_day * MB);
|
|
|
|
|
assert.equal(limits.maxBodyBytes, LIMITS.max_body_bytes);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("local settings are clamped", () => {
|
|
|
|
|
const limits = new Limits({ local: { bandwidthMb: 10 ** 9, maxConcurrent: 99 } });
|
|
|
|
|
assert.equal(limits.local.bandwidthMb, LIMITS.max_donor_mb_per_day);
|
|
|
|
|
assert.equal(limits.local.maxConcurrent, LIMITS.max_concurrent_per_node);
|
|
|
|
|
limits.setLocal({ bandwidthMb: -5, maxConcurrent: 0 });
|
|
|
|
|
assert.equal(limits.local.bandwidthMb, LIMITS.default_donor_mb_per_day);
|
|
|
|
|
assert.equal(limits.local.maxConcurrent, LIMITS.max_concurrent_per_node);
|
|
|
|
|
limits.setLocal({ activeFrom: "25:99", activeTo: "10:00" });
|
|
|
|
|
assert.equal(limits.local.activeFrom, "");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("a welcome can lower caps and never raise them", () => {
|
|
|
|
|
const limits = new Limits();
|
|
|
|
|
limits.applyWelcome(
|
|
|
|
|
{
|
|
|
|
|
max_body_bytes: 10 ** 9,
|
|
|
|
|
max_concurrent_per_node: 1,
|
|
|
|
|
job_timeout_seconds: 5,
|
|
|
|
|
ping_seconds: 999,
|
|
|
|
|
reconnect_min_seconds: 2,
|
|
|
|
|
reconnect_max_seconds: 10 ** 6,
|
|
|
|
|
},
|
|
|
|
|
{ bandwidth_mb_day: 5, max_concurrent: 99 },
|
|
|
|
|
);
|
|
|
|
|
assert.equal(limits.maxBodyBytes, LIMITS.max_body_bytes);
|
|
|
|
|
assert.equal(limits.concurrency, 1);
|
|
|
|
|
assert.equal(limits.timeoutSeconds(25), 5);
|
|
|
|
|
assert.equal(limits.pingSeconds, LIMITS.ping_seconds);
|
|
|
|
|
assert.equal(limits.bandwidthBytes, 5 * MB);
|
|
|
|
|
assert.equal(limits.reconnectMin, 2);
|
|
|
|
|
assert.equal(limits.reconnectMax, LIMITS.reconnect_max_seconds);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("concurrency cap admits and releases", () => {
|
|
|
|
|
const limits = new Limits({ local: { maxConcurrent: 1 } });
|
|
|
|
|
assert.equal(limits.admit(0), null);
|
|
|
|
|
assert.equal(limits.admit(0), REFUSALS.CONCURRENCY_EXCEEDED);
|
|
|
|
|
limits.release();
|
|
|
|
|
assert.equal(limits.admit(0), null);
|
|
|
|
|
limits.release();
|
|
|
|
|
limits.release();
|
|
|
|
|
assert.equal(limits.inflight, 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("bandwidth cap refuses when used up", () => {
|
|
|
|
|
const limits = new Limits({ local: { bandwidthMb: 1 } });
|
|
|
|
|
assert.equal(limits.admit(MB - 1), null);
|
|
|
|
|
limits.release();
|
|
|
|
|
assert.equal(limits.admit(MB), REFUSALS.BANDWIDTH_EXHAUSTED);
|
|
|
|
|
assert.equal(limits.remainingBytes(2 * MB), 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("active hours, including a window that wraps midnight", () => {
|
|
|
|
|
const day = new Limits({ clock: at(12), local: { activeFrom: "09:00", activeTo: "17:00" } });
|
|
|
|
|
assert.equal(day.admit(0), null);
|
|
|
|
|
const early = new Limits({ clock: at(8, 59), local: { activeFrom: "09:00", activeTo: "17:00" } });
|
|
|
|
|
assert.equal(early.admit(0), REFUSALS.OUTSIDE_HOURS);
|
|
|
|
|
const end = new Limits({ clock: at(17), local: { activeFrom: "09:00", activeTo: "17:00" } });
|
|
|
|
|
assert.equal(end.admit(0), REFUSALS.OUTSIDE_HOURS);
|
|
|
|
|
const wrapIn = new Limits({ clock: at(23, 30), local: { activeFrom: "22:00", activeTo: "06:00" } });
|
|
|
|
|
assert.equal(wrapIn.inActiveHours(), true);
|
|
|
|
|
const wrapEarly = new Limits({ clock: at(3), local: { activeFrom: "22:00", activeTo: "06:00" } });
|
|
|
|
|
assert.equal(wrapEarly.inActiveHours(), true);
|
|
|
|
|
const wrapOut = new Limits({ clock: at(12), local: { activeFrom: "22:00", activeTo: "06:00" } });
|
|
|
|
|
assert.equal(wrapOut.inActiveHours(), false);
|
|
|
|
|
const always = new Limits({ clock: at(12) });
|
|
|
|
|
assert.equal(always.inActiveHours(), true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("hello limits advertise the effective concurrency", () => {
|
|
|
|
|
const limits = new Limits({ local: { maxConcurrent: 1 } });
|
|
|
|
|
assert.deepEqual(limits.helloLimits(), { max_concurrent: 1 });
|
|
|
|
|
});
|
2026-10-17 02:23:43 +02:00
|
|
|
|
|
|
|
|
function serverLimits(window, clock, local = {}) {
|
|
|
|
|
const limits = new Limits({ clock, local });
|
|
|
|
|
limits.applyServerSettings({ active_from: window.from, active_to: window.to, timezone: window.zone });
|
|
|
|
|
return limits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test("the server window is evaluated in the server timezone", () => {
|
|
|
|
|
const moment = () => new Date(Date.UTC(2026, 0, 15, 12, 0));
|
|
|
|
|
assert.equal(serverLimits({ from: "09:00", to: "17:00", zone: "UTC" }, moment).inActiveHours(), true);
|
|
|
|
|
assert.equal(serverLimits({ from: "09:00", to: "17:00", zone: "Asia/Tokyo" }, moment).inActiveHours(), false);
|
|
|
|
|
assert.equal(serverLimits({ from: "20:00", to: "23:00", zone: "Asia/Tokyo" }, moment).inActiveHours(), true);
|
|
|
|
|
assert.equal(serverLimits({ from: "22:00", to: "06:00", zone: "America/New_York" }, moment).inActiveHours(), false);
|
|
|
|
|
assert.equal(serverLimits({ from: "21:00", to: "06:00", zone: "Asia/Tokyo" }, moment).inActiveHours(), true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("an invalid server timezone falls back to UTC and empty or equal bounds mean always", () => {
|
|
|
|
|
const moment = () => new Date(Date.UTC(2026, 0, 15, 12, 0));
|
|
|
|
|
assert.equal(serverLimits({ from: "09:00", to: "17:00", zone: "Not/AZone" }, moment).inActiveHours(), true);
|
|
|
|
|
assert.equal(serverLimits({ from: "13:00", to: "17:00", zone: "Not/AZone" }, moment).inActiveHours(), false);
|
|
|
|
|
assert.equal(serverLimits({ from: "", to: "", zone: "Asia/Tokyo" }, moment).inActiveHours(), true);
|
|
|
|
|
assert.equal(serverLimits({ from: "10:00", to: "10:00", zone: "Asia/Tokyo" }, moment).inActiveHours(), true);
|
|
|
|
|
const bare = new Limits({ clock: moment });
|
|
|
|
|
bare.applyServerSettings({});
|
|
|
|
|
assert.equal(bare.inActiveHours(), true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("both the donor window and the server window must be open", () => {
|
|
|
|
|
const noon = at(12);
|
|
|
|
|
const open = { from: "00:00", to: "23:59", zone: "UTC" };
|
|
|
|
|
const shut = { from: "00:00", to: "00:01", zone: "UTC" };
|
|
|
|
|
assert.equal(serverLimits(open, noon, { activeFrom: "09:00", activeTo: "17:00" }).admit(0), null);
|
|
|
|
|
assert.equal(serverLimits(shut, noon, { activeFrom: "09:00", activeTo: "17:00" }).admit(0), REFUSALS.OUTSIDE_HOURS);
|
|
|
|
|
assert.equal(serverLimits(open, noon, { activeFrom: "13:00", activeTo: "17:00" }).admit(0), REFUSALS.OUTSIDE_HOURS);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("a settings_update replaces the server window", () => {
|
|
|
|
|
const limits = serverLimits({ from: "13:00", to: "17:00", zone: "UTC" }, () => new Date(Date.UTC(2026, 0, 15, 12, 0)));
|
|
|
|
|
assert.equal(limits.inActiveHours(), false);
|
|
|
|
|
limits.applyServerSettings({ active_from: "", active_to: "", timezone: "UTC" });
|
|
|
|
|
assert.equal(limits.inActiveHours(), true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const BASE = 1767225600;
|
|
|
|
|
const STEP = 40009;
|
|
|
|
|
const SAMPLES = 200;
|
|
|
|
|
const PARITY = [
|
|
|
|
|
{ from: "09:00", to: "17:00", zone: "Europe/Amsterdam", bits: "01010000001010101010000101010101000000101010100000010101010000001010101010000101010101000000101010100000010101010000001010101010000101010101000000101010100000010101010000001010101010000001010101010000" },
|
|
|
|
|
{ from: "22:00", to: "06:00", zone: "America/New_York", bits: "00010101010000001010101000000101010101000010101010100000010101010000001010101000000101010101000010101010100000010101010000001010101000000101010101000000101010101000010101010100000010101010000001010101" },
|
|
|
|
|
{ from: "08:30", to: "12:15", zone: "Asia/Kolkata", bits: "00000001010000000000101000000000010101000000000010100000000001010000000000101000000000000101000000000010100000000001010000000000101000000000000101000000000010100000000001010000000000101010000000000101" },
|
|
|
|
|
{ from: "00:00", to: "12:00", zone: "Pacific/Auckland", bits: "01101010101010110101010101010010101010101001010101010101101010101010110101010101010010101010101001010101010101101010101010110101010101010010101010101011010101010101101010101010100101010101010010101010" },
|
|
|
|
|
{ from: "10:00", to: "10:00", zone: "UTC", bits: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" },
|
|
|
|
|
{ from: "09:00", to: "17:00", zone: "Not/AZone", bits: "01010000101010101000000101010100000010101010000001010101010000001010101000000101010100000010101010100001010101010000001010101000000101010100000010101010100001010101010000001010101000000101010100000010" },
|
|
|
|
|
{ from: "23:00", to: "01:00", zone: "Australia/Lord_Howe", bits: "00000000000010000000000001010000000000001000000000000100000000000010000000000001010000000000001000000000000100000000000010000000000000010000000000001000000000000100000000000010000000000000010000000000" },
|
|
|
|
|
{ from: "", to: "", zone: "Europe/Amsterdam", bits: "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
test("server window parity with the Python scheduler over 200 sampled instants", () => {
|
|
|
|
|
for (const item of PARITY) {
|
|
|
|
|
assert.equal(item.bits.length, SAMPLES);
|
|
|
|
|
for (let index = 0; index < SAMPLES; index += 1) {
|
|
|
|
|
const moment = new Date((BASE + index * STEP) * 1000);
|
|
|
|
|
const limits = serverLimits({ from: item.from, to: item.to, zone: item.zone }, () => moment);
|
|
|
|
|
assert.equal(limits.inActiveHours(), item.bits[index] === "1", `${item.from}-${item.to} ${item.zone} #${index}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|