mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
68 lines
3.2 KiB
TypeScript
68 lines
3.2 KiB
TypeScript
/**
|
|
* Per-plugin daily broker budget (#plugins hardening): a per-kind daily counter
|
|
* with a UTC-midnight rollover, clock injected so it tests without timers.
|
|
*/
|
|
import { DailyBudget, envCap } from '../../../src/nest/plugins/host/daily-budget';
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
const CFG = { aiPerDay: 3, notifyPerDay: 2 };
|
|
const t = (iso: string) => new Date(iso).getTime();
|
|
|
|
describe('DailyBudget', () => {
|
|
it('allows up to the per-kind daily cap, then refuses', () => {
|
|
const b = new DailyBudget(CFG, t('2026-07-10T09:00:00Z'));
|
|
expect(b.take('ai', t('2026-07-10T09:00:00Z'))).toBe(true);
|
|
expect(b.take('ai', t('2026-07-10T10:00:00Z'))).toBe(true);
|
|
expect(b.take('ai', t('2026-07-10T11:00:00Z'))).toBe(true);
|
|
expect(b.take('ai', t('2026-07-10T12:00:00Z'))).toBe(false); // ai cap 3 spent
|
|
// notify is a separate budget, still available
|
|
expect(b.take('notify', t('2026-07-10T12:00:00Z'))).toBe(true);
|
|
expect(b.take('notify', t('2026-07-10T12:00:00Z'))).toBe(true);
|
|
expect(b.take('notify', t('2026-07-10T12:00:00Z'))).toBe(false); // notify cap 2 spent
|
|
});
|
|
|
|
it('resets both counters at UTC midnight', () => {
|
|
const b = new DailyBudget(CFG, t('2026-07-10T23:00:00Z'));
|
|
expect(b.take('ai', t('2026-07-10T23:00:00Z'))).toBe(true);
|
|
expect(b.take('ai', t('2026-07-10T23:30:00Z'))).toBe(true);
|
|
expect(b.take('ai', t('2026-07-10T23:45:00Z'))).toBe(true);
|
|
expect(b.take('ai', t('2026-07-10T23:59:00Z'))).toBe(false); // spent for the 10th
|
|
expect(b.take('ai', t('2026-07-11T00:01:00Z'))).toBe(true); // new UTC day -> reset
|
|
});
|
|
|
|
it("seeds today's already-spent counts so a restart continues the same day", () => {
|
|
const b = new DailyBudget(CFG, t('2026-07-10T12:00:00Z'), { ai: 2, notify: 2 });
|
|
expect(b.take('ai', t('2026-07-10T12:00:00Z'))).toBe(true); // 3rd ai
|
|
expect(b.take('ai', t('2026-07-10T12:00:00Z'))).toBe(false); // cap already reached
|
|
expect(b.take('notify', t('2026-07-10T12:00:00Z'))).toBe(false); // seeded at cap
|
|
});
|
|
|
|
it('reports usage for the admin view', () => {
|
|
const b = new DailyBudget(CFG, t('2026-07-10T12:00:00Z'), { ai: 1 });
|
|
b.take('ai', t('2026-07-10T12:00:00Z'));
|
|
expect(b.used(t('2026-07-10T12:00:00Z'))).toMatchObject({ ai: 2, aiMax: 3, notify: 0, notifyMax: 2 });
|
|
});
|
|
|
|
it('a cap of 0 disables the broker entirely (take always refuses)', () => {
|
|
const b = new DailyBudget({ aiPerDay: 0, notifyPerDay: 5 }, t('2026-07-10T12:00:00Z'));
|
|
expect(b.take('ai', t('2026-07-10T12:00:00Z'))).toBe(false); // 0 means off, not unlimited
|
|
expect(b.take('notify', t('2026-07-10T12:00:00Z'))).toBe(true);
|
|
});
|
|
|
|
it('envCap honours a literal 0 and only falls back on absent/malformed values', () => {
|
|
const KEY = 'TREK_TEST_CAP_XYZ';
|
|
delete process.env[KEY];
|
|
expect(envCap(KEY, 200)).toBe(200); // absent -> default
|
|
process.env[KEY] = '0';
|
|
expect(envCap(KEY, 200)).toBe(0); // literal 0 honoured (would have been 200 with `|| def`)
|
|
process.env[KEY] = '50';
|
|
expect(envCap(KEY, 200)).toBe(50);
|
|
process.env[KEY] = 'nonsense';
|
|
expect(envCap(KEY, 200)).toBe(200); // malformed -> default
|
|
process.env[KEY] = '-5';
|
|
expect(envCap(KEY, 200)).toBe(200); // negative -> default
|
|
delete process.env[KEY];
|
|
});
|
|
});
|