Files
AgentCompany/tests/unit/budget-alert.test.ts
T
2026-04-05 20:14:48 -04:00

114 lines
3.6 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
import { checkBudgetThreshold } from "@main/automation/automation-run-completion";
/**
* Tests for budget alert notification logic in automation-run-completion.ts.
*
* Budget alerts fire at two thresholds:
* 1. >= 80% utilization -> warning notification with agent name and percentage
* 2. >= 100% utilization -> exhaustion notification (both fire)
* 3. budgetMonthlyUsd <= 0 (unlimited) -> no notification
*/
/** Identity localizer: returns the English string, as in the production default path. */
function en(enStr: string, _zh: string): string {
return enStr;
}
describe("Budget Alert Notifications", () => {
it("fires warning notification at 80% budget utilization", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Alice", budgetMonthlyUsd: 100, spentMonthlyUsd: 85 },
notify,
en,
);
expect(notify).toHaveBeenCalledTimes(1);
const call = notify.mock.calls[0]![0] as { title: string; body: string };
expect(call.title).toContain("Budget");
expect(call.body).toContain("Alice");
expect(call.body).toContain("85%");
});
it("fires both notifications at >= 100% utilization", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Bob", budgetMonthlyUsd: 50, spentMonthlyUsd: 55 },
notify,
en,
);
expect(notify).toHaveBeenCalledTimes(2);
const bodies = notify.mock.calls.map((c) => (c[0] as { body: string }).body);
// Second call is the exhaustion notification
expect(bodies[1]).toContain("100%");
});
it("correctly rounds percentage as Math.round(utilization * 100)", () => {
const notify = vi.fn();
// 83.333...% should round to 83%
checkBudgetThreshold(
{ name: "Charlie", budgetMonthlyUsd: 60, spentMonthlyUsd: 50 },
notify,
en,
);
const body = (notify.mock.calls[0]![0] as { body: string }).body;
expect(body).toContain("83%");
});
it("does not fire notification when budgetMonthlyUsd is 0 (unlimited)", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Diana", budgetMonthlyUsd: 0, spentMonthlyUsd: 500 },
notify,
en,
);
expect(notify).not.toHaveBeenCalled();
});
it("does not fire notification when utilization is below 80%", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Eve", budgetMonthlyUsd: 100, spentMonthlyUsd: 50 },
notify,
en,
);
expect(notify).not.toHaveBeenCalled();
});
it("fires both notifications at exactly 100%", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Frank", budgetMonthlyUsd: 100, spentMonthlyUsd: 100 },
notify,
en,
);
expect(notify).toHaveBeenCalledTimes(2);
const first = notify.mock.calls[0]![0] as { title: string; body: string };
const second = notify.mock.calls[1]![0] as { title: string; body: string };
expect(first.body).toContain("100%");
expect(second.title).toContain("Exhausted");
});
it("does not fire notification when budgetMonthlyUsd is negative", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Grace", budgetMonthlyUsd: -10, spentMonthlyUsd: 500 },
notify,
en,
);
expect(notify).not.toHaveBeenCalled();
});
it("fires only the warning (not exhaustion) at exactly 80%", () => {
const notify = vi.fn();
checkBudgetThreshold(
{ name: "Hank", budgetMonthlyUsd: 100, spentMonthlyUsd: 80 },
notify,
en,
);
expect(notify).toHaveBeenCalledTimes(1);
const body = (notify.mock.calls[0]![0] as { body: string }).body;
expect(body).toContain("80%");
});
});