/** * @file Tests for the alert rule form's spend rules: a budget rule is built * from period / limit / thresholds / AI tool (plus the browser's timezone * offset), Preview shows what it would have fired without saving it, and * Create saves the same config. * @author Nguyễn Ngọc Trí Vĩ */ import { describe, it, expect, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import userEvent from "@testing-library/user-event"; vi.mock("../../lib/api", () => ({ api: { alerts: { list: vi.fn().mockResolvedValue({ alerts: [], total: 0, unacked: 0, limit: 50, offset: 0 }), rules: { list: vi.fn().mockResolvedValue({ rules: [] }), create: vi.fn().mockResolvedValue({ rule: {} }), preview: vi.fn().mockResolvedValue({ supported: true, window_days: 7, fires: 1, sessions: 0, samples: [], periods: [ { period_start: "2026-09-01", period_end: "2026-09-30", spend: 120, pct: 120, crossed: [80, 100], }, { period_start: "2026-10-01", period_end: "2026-10-31", spend: 10, pct: 10, crossed: [], }, ], }), }, }, aiTools: { list: vi.fn().mockResolvedValue({ tools: [{ id: "codex", label: "Codex" }], default: "claude", }), }, }, })); import { api } from "../../lib/api"; import { AlertsNotifications } from "../AlertsNotifications"; describe("AlertsNotifications — spend rules", () => { it("previews and creates a budget rule", async () => { const user = userEvent.setup(); render( ); await user.click(await screen.findByText("Add rule")); await user.type(screen.getByPlaceholderText("e.g. Error burst"), "Monthly"); const selects = screen.getAllByRole("combobox"); await user.selectOptions(selects[0]!, "budget"); await user.selectOptions(screen.getByDisplayValue("month"), "week"); const limit = screen.getByDisplayValue("100"); await user.clear(limit); await user.type(limit, "50"); await screen.findByRole("option", { name: "Codex" }); await user.selectOptions(screen.getByDisplayValue("All AI tools"), "codex"); await user.click(screen.getByRole("button", { name: /Preview/ })); const config = { period: "week", limit_usd: 50, thresholds: [80, 100], tz_offset: new Date().getTimezoneOffset(), tool: "codex", }; expect(api.alerts.rules.preview).toHaveBeenCalledWith({ rule_type: "budget", config, cooldown_seconds: 300, }); expect(await screen.findByText("2026-09-01")).toBeInTheDocument(); expect(screen.getByText("80% 100%")).toBeInTheDocument(); expect(api.alerts.rules.create).not.toHaveBeenCalled(); await user.click(screen.getByRole("button", { name: /Create rule/ })); expect(api.alerts.rules.create).toHaveBeenCalledWith({ name: "Monthly", rule_type: "budget", config, cooldown_seconds: 300, }); }); });