- Add notify() calls at 80% budget threshold warning and 100% exhaustion auto-pause - Compute percentage as Math.round(utilization * 100) for accurate display - Add scheduled social post timer re-registration on app startup - Add unit tests for budget threshold detection and notification logic
148 lines
4.8 KiB
TypeScript
148 lines
4.8 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
|
|
/**
|
|
* Tests for budget alert notification logic.
|
|
*
|
|
* Budget alerts fire at two thresholds:
|
|
* 1. 80% utilization -> warning notification with agent name and percentage
|
|
* 2. 100% utilization -> paused notification indicating budget exhaustion
|
|
* 3. No notification when budgetMonthlyUsd is 0 (unlimited budget)
|
|
* 4. Percentage is calculated as Math.round((spentMonthlyUsd / budgetMonthlyUsd) * 100)
|
|
*/
|
|
|
|
/**
|
|
* Extract and test the budget notification logic that lives in automation-service.ts
|
|
* handleRunFinished -> budget threshold section.
|
|
*
|
|
* The actual code pattern in automation-service.ts:
|
|
* if (agent.budgetMonthlyUsd > 0) {
|
|
* const utilization = agent.spentMonthlyUsd / agent.budgetMonthlyUsd;
|
|
* if (utilization >= 0.8) {
|
|
* // 80% threshold warning
|
|
* const pct = Math.round(utilization * 100);
|
|
* notify("Budget Alert", `${agent.name} is at ${pct}% of monthly budget ...`);
|
|
* if (utilization >= 1.0) {
|
|
* // 100% exhaustion notification
|
|
* notify("Budget Alert", `${agent.name} paused -- monthly budget exhausted ...`);
|
|
* }
|
|
* }
|
|
* }
|
|
*/
|
|
function simulateBudgetNotification(
|
|
notify: (title: string, body: string) => void,
|
|
agent: { name: string; budgetMonthlyUsd: number; spentMonthlyUsd: number },
|
|
) {
|
|
if (agent.budgetMonthlyUsd > 0) {
|
|
const utilization = agent.spentMonthlyUsd / agent.budgetMonthlyUsd;
|
|
if (utilization >= 0.8) {
|
|
const pct = Math.round(utilization * 100);
|
|
notify(
|
|
"Budget Alert",
|
|
`${agent.name} is at ${pct}% of monthly budget ($${agent.spentMonthlyUsd.toFixed(2)} / $${agent.budgetMonthlyUsd.toFixed(2)})`,
|
|
);
|
|
if (utilization >= 1.0) {
|
|
notify(
|
|
"Budget Alert",
|
|
`${agent.name} paused -- monthly budget exhausted ($${agent.spentMonthlyUsd.toFixed(2)} / $${agent.budgetMonthlyUsd.toFixed(2)})`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
describe("Budget Alert Notifications", () => {
|
|
it("fires warning notification at 80% budget utilization", () => {
|
|
const notify = vi.fn();
|
|
simulateBudgetNotification(notify, {
|
|
name: "Alice",
|
|
budgetMonthlyUsd: 100,
|
|
spentMonthlyUsd: 85,
|
|
});
|
|
expect(notify).toHaveBeenCalledTimes(1);
|
|
expect(notify).toHaveBeenCalledWith(
|
|
"Budget Alert",
|
|
expect.stringContaining("Alice"),
|
|
);
|
|
expect(notify).toHaveBeenCalledWith(
|
|
"Budget Alert",
|
|
expect.stringContaining("85%"),
|
|
);
|
|
expect(notify).toHaveBeenCalledWith(
|
|
"Budget Alert",
|
|
expect.stringContaining("% of monthly budget"),
|
|
);
|
|
});
|
|
|
|
it("fires paused notification at 100% budget utilization", () => {
|
|
const notify = vi.fn();
|
|
simulateBudgetNotification(notify, {
|
|
name: "Bob",
|
|
budgetMonthlyUsd: 50,
|
|
spentMonthlyUsd: 55,
|
|
});
|
|
// Both the 80% and 100% notifications should fire
|
|
expect(notify).toHaveBeenCalledTimes(2);
|
|
// Second call should be the exhaustion notification
|
|
expect(notify).toHaveBeenCalledWith(
|
|
"Budget Alert",
|
|
expect.stringContaining("paused"),
|
|
);
|
|
expect(notify).toHaveBeenCalledWith(
|
|
"Budget Alert",
|
|
expect.stringContaining("budget exhausted"),
|
|
);
|
|
});
|
|
|
|
it("correctly calculates percentage as Math.round(utilization * 100)", () => {
|
|
const notify = vi.fn();
|
|
// 83.333...% should round to 83%
|
|
simulateBudgetNotification(notify, {
|
|
name: "Charlie",
|
|
budgetMonthlyUsd: 60,
|
|
spentMonthlyUsd: 50,
|
|
});
|
|
expect(notify).toHaveBeenCalledWith(
|
|
"Budget Alert",
|
|
expect.stringContaining("83%"),
|
|
);
|
|
});
|
|
|
|
it("does not fire notification when budgetMonthlyUsd is 0 (unlimited)", () => {
|
|
const notify = vi.fn();
|
|
simulateBudgetNotification(notify, {
|
|
name: "Diana",
|
|
budgetMonthlyUsd: 0,
|
|
spentMonthlyUsd: 500,
|
|
});
|
|
expect(notify).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not fire notification when utilization is below 80%", () => {
|
|
const notify = vi.fn();
|
|
simulateBudgetNotification(notify, {
|
|
name: "Eve",
|
|
budgetMonthlyUsd: 100,
|
|
spentMonthlyUsd: 50,
|
|
});
|
|
expect(notify).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("fires both notifications at exactly 100%", () => {
|
|
const notify = vi.fn();
|
|
simulateBudgetNotification(notify, {
|
|
name: "Frank",
|
|
budgetMonthlyUsd: 100,
|
|
spentMonthlyUsd: 100,
|
|
});
|
|
expect(notify).toHaveBeenCalledTimes(2);
|
|
// First call: 80% threshold warning
|
|
expect(notify.mock.calls[0]![0]).toBe("Budget Alert");
|
|
expect(notify.mock.calls[0]![1]).toContain("100%");
|
|
expect(notify.mock.calls[0]![1]).toContain("% of monthly budget");
|
|
// Second call: exhaustion notification
|
|
expect(notify.mock.calls[1]![0]).toBe("Budget Alert");
|
|
expect(notify.mock.calls[1]![1]).toContain("paused");
|
|
expect(notify.mock.calls[1]![1]).toContain("budget exhausted");
|
|
});
|
|
});
|