T-HUB/packages/connector-sdk/src/rate-policy.test.ts
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

104 lines
4.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { capacitySnapshot, exceedsDocumentedCap, humanizeMs, resolveRatePolicy } from "./rate-policy.js";
describe("rate policy resolution", () => {
it("falls back to documented defaults when nothing overrides", () => {
const policy = resolveRatePolicy("instagram", { dailyPostCap: 25, requestsPerSecond: 0.5 });
expect(policy.dailyPostCap).toBe(25);
expect(policy.requestsPerSecond).toBe(0.5);
});
it("connection override beats documented (news tier)", () => {
const policy = resolveRatePolicy(
"instagram",
{ dailyPostCap: 25 },
{ dailyPostCap: 100, tierNote: "news-publisher advanced access" },
);
expect(policy.dailyPostCap).toBe(100);
expect(policy.tierNote).toBe("news-publisher advanced access");
});
it("env beats documented, override beats env", () => {
process.env.CONNECTOR_RATE_THREADS_DAILY_POSTS = "500";
process.env.CONNECTOR_RATE_THREADS_RPS = "2";
const fromEnv = resolveRatePolicy("threads", { dailyPostCap: 250, requestsPerSecond: 0.5 });
expect(fromEnv.dailyPostCap).toBe(500);
expect(fromEnv.requestsPerSecond).toBe(2);
const fromOverride = resolveRatePolicy("threads", { dailyPostCap: 250 }, { dailyPostCap: 750 });
expect(fromOverride.dailyPostCap).toBe(750);
delete process.env.CONNECTOR_RATE_THREADS_DAILY_POSTS;
delete process.env.CONNECTOR_RATE_THREADS_RPS;
});
it("sanitize rejects zero/negative/garbage", () => {
const policy = resolveRatePolicy("x", { dailyPostCap: -5, requestsPerSecond: 0 }, { commentsPerHour: 0 });
expect(policy.dailyPostCap).toBe(FALLBACK_FOR("dailyPostCap"));
expect(policy.requestsPerSecond).toBe(FALLBACK_FOR("requestsPerSecond"));
});
it("cap-exceedance warning hook", () => {
expect(exceedsDocumentedCap({ dailyPostCap: 25 }, { dailyPostCap: 100 })).toBe(true);
expect(exceedsDocumentedCap({ dailyPostCap: 25 }, { dailyPostCap: 25 })).toBe(false);
expect(exceedsDocumentedCap({ dailyPostCap: 25 })).toBe(false);
});
});
describe("capacity snapshot (rolling window)", () => {
const HOUR = 3600_000;
const now = Date.now();
const ago = (h: number): string => new Date(now - h * HOUR).toISOString();
it("shows remaining under the cap", () => {
const snap = capacitySnapshot(
{ used: 8, cap: 25, recentPublishes: [ago(1), ago(2), ago(3), ago(4), ago(5), ago(6), ago(7), ago(8)] },
resolveRatePolicy("x", { dailyPostCap: 25 }),
);
expect(snap.remaining).toBe(17);
expect(snap.label).toContain("17 of 25");
});
it("at cap: next slot frees when the oldest publish ages out", () => {
const publishes = [ago(3), ...Array.from({ length: 24 }, () => ago(1))];
const snap = capacitySnapshot(
{ used: 25, cap: 25, recentPublishes: publishes },
resolveRatePolicy("x", { dailyPostCap: 25 }),
);
expect(snap.remaining).toBe(0);
expect(snap.nextSlotInMs).toBeDefined();
if (snap.nextSlotInMs !== undefined) {
expect(snap.nextSlotInMs).toBeGreaterThan(20 * HOUR);
expect(snap.nextSlotInMs).toBeLessThan(21 * HOUR);
}
expect(snap.label).toContain("next slot in");
});
it("uncapped connections say so", () => {
const snap = capacitySnapshot({ used: 40, recentPublishes: [] }, { requestsPerSecond: 1 });
expect(snap.remaining).toBeUndefined();
expect(snap.label).toContain("no cap");
});
it("stale publishes (outside 24h) do not count", () => {
const snap = capacitySnapshot(
{ used: 1, cap: 25, recentPublishes: [ago(30)] },
resolveRatePolicy("x", { dailyPostCap: 25 }),
);
expect(snap.remaining).toBe(25);
});
});
describe("humanize", () => {
it("formats durations", () => {
expect(humanizeMs(0)).toBe("now");
expect(humanizeMs(5 * 60000)).toBe("5m");
expect(humanizeMs(90 * 60000)).toBe("1h 30m");
expect(humanizeMs(2 * HOUR_MS)).toBe("2h");
});
});
const HOUR_MS = 3600_000;
function FALLBACK_FOR(key: "requestsPerSecond" | "dailyPostCap"): number {
const base = resolveRatePolicy("fallback-probe", {});
return key === "requestsPerSecond" ? base.requestsPerSecond : (base.dailyPostCap ?? -1);
}