import { describe, it, expect, vi, beforeEach } from "vitest"; import { get } from "svelte/store"; import { invoke } from "@tauri-apps/api/core"; import { setMockInvokeResult } from "../../../vitest.setup"; import { formatCost, formatAlertType, getAlertMessage, costTrackingStore, formattedCosts, type AlertType, type CostAlert, } from "./costTracking"; vi.mock("$lib/notifications/notificationManager", () => ({ notificationManager: { notifyCostAlert: vi.fn(), }, })); describe("formatCost", () => { it("formats amounts below $0.01 to 4 decimal places", () => { expect(formatCost(0)).toBe("$0.0000"); expect(formatCost(0.001)).toBe("$0.0010"); expect(formatCost(0.0099)).toBe("$0.0099"); }); it("formats amounts between $0.01 and $1 to 3 decimal places", () => { expect(formatCost(0.01)).toBe("$0.010"); expect(formatCost(0.123)).toBe("$0.123"); expect(formatCost(0.999)).toBe("$0.999"); }); it("formats amounts $1 and above to 2 decimal places", () => { expect(formatCost(1)).toBe("$1.00"); expect(formatCost(1.5)).toBe("$1.50"); expect(formatCost(100.99)).toBe("$100.99"); }); }); describe("formatAlertType", () => { it("formats Daily as Today", () => { expect(formatAlertType("Daily")).toBe("Today"); }); it("formats Weekly as This Week", () => { expect(formatAlertType("Weekly")).toBe("This Week"); }); it("formats Monthly as This Month", () => { expect(formatAlertType("Monthly")).toBe("This Month"); }); it("handles all AlertType values", () => { const types: AlertType[] = ["Daily", "Weekly", "Monthly"]; const results = types.map(formatAlertType); expect(results).toEqual(["Today", "This Week", "This Month"]); }); }); describe("getAlertMessage", () => { it("generates a message for a Daily alert", () => { const alert: CostAlert = { alert_type: "Daily", threshold: 1.0, current_cost: 1.5, }; const message = getAlertMessage(alert); expect(message).toContain("Today"); expect(message).toContain("$1.50"); expect(message).toContain("$1.00"); }); it("generates a message for a Weekly alert", () => { const alert: CostAlert = { alert_type: "Weekly", threshold: 5.0, current_cost: 6.0, }; const message = getAlertMessage(alert); expect(message).toContain("This Week"); expect(message).toContain("$6.00"); expect(message).toContain("$5.00"); }); it("generates a message for a Monthly alert", () => { const alert: CostAlert = { alert_type: "Monthly", threshold: 20.0, current_cost: 25.0, }; const message = getAlertMessage(alert); expect(message).toContain("This Month"); expect(message).toContain("$25.00"); expect(message).toContain("$20.00"); }); it("includes threshold and current cost in the message", () => { const alert: CostAlert = { alert_type: "Daily", threshold: 0.005, current_cost: 0.007, }; const message = getAlertMessage(alert); expect(message).toContain("$0.0070"); expect(message).toContain("$0.0050"); }); }); describe("costTrackingStore", () => { beforeEach(() => { vi.clearAllMocks(); costTrackingStore.reset(); }); describe("refresh", () => { it("loads cost data from the backend", async () => { setMockInvokeResult("get_today_cost", 1.5); setMockInvokeResult("get_week_cost", 5.25); setMockInvokeResult("get_month_cost", 20.0); setMockInvokeResult("get_cost_alerts", []); await costTrackingStore.refresh(); const state = get(costTrackingStore); expect(state.todayCost).toBe(1.5); expect(state.weekCost).toBe(5.25); expect(state.monthCost).toBe(20.0); expect(state.isLoading).toBe(false); }); it("triggers notifications for any returned alerts", async () => { const { notificationManager } = await import("$lib/notifications/notificationManager"); const alert: CostAlert = { alert_type: "Daily", threshold: 1.0, current_cost: 1.5 }; setMockInvokeResult("get_today_cost", 1.5); setMockInvokeResult("get_week_cost", 0); setMockInvokeResult("get_month_cost", 0); setMockInvokeResult("get_cost_alerts", [alert]); await costTrackingStore.refresh(); expect(notificationManager.notifyCostAlert).toHaveBeenCalledWith( expect.stringContaining("Today") ); }); it("returns alerts from refresh", async () => { const alert: CostAlert = { alert_type: "Weekly", threshold: 5.0, current_cost: 6.0 }; setMockInvokeResult("get_today_cost", 0); setMockInvokeResult("get_week_cost", 6.0); setMockInvokeResult("get_month_cost", 0); setMockInvokeResult("get_cost_alerts", [alert]); const result = await costTrackingStore.refresh(); expect(result).toEqual([alert]); }); it("handles refresh errors gracefully and returns empty array", async () => { const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("get_today_cost", new Error("Backend error")); const result = await costTrackingStore.refresh(); expect(result).toEqual([]); expect(errorSpy).toHaveBeenCalledWith("Failed to refresh cost tracking:", expect.any(Error)); errorSpy.mockRestore(); }); }); describe("getSummary", () => { it("fetches and stores a cost summary", async () => { const mockSummary = { period_days: 7, total_input_tokens: 1000, total_output_tokens: 500, total_cost: 0.05, total_messages: 20, total_sessions: 3, average_daily_cost: 0.007, daily_breakdown: [], }; setMockInvokeResult("get_cost_summary", mockSummary); const result = await costTrackingStore.getSummary(7); expect(result).toEqual(mockSummary); expect(invoke).toHaveBeenCalledWith("get_cost_summary", { days: 7 }); }); it("returns null and logs error on failure", async () => { const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("get_cost_summary", new Error("Summary unavailable")); const result = await costTrackingStore.getSummary(30); expect(result).toBeNull(); expect(errorSpy).toHaveBeenCalledWith("Failed to get cost summary:", expect.any(Error)); errorSpy.mockRestore(); }); }); describe("setAlertThresholds", () => { it("persists new thresholds to the backend", async () => { const thresholds = { daily: 2.0, weekly: 10.0, monthly: 40.0 }; await costTrackingStore.setAlertThresholds(thresholds); expect(invoke).toHaveBeenCalledWith("set_cost_alert_thresholds", { daily: 2.0, weekly: 10.0, monthly: 40.0, }); }); it("logs an error when the backend call fails", async () => { const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("set_cost_alert_thresholds", new Error("Failed")); await costTrackingStore.setAlertThresholds({ daily: 1.0, weekly: null, monthly: null }); expect(errorSpy).toHaveBeenCalledWith("Failed to set alert thresholds:", expect.any(Error)); errorSpy.mockRestore(); }); }); describe("exportCsv", () => { it("returns the CSV string from the backend", async () => { setMockInvokeResult("export_cost_csv", "date,cost\n2026-01-01,1.50"); const result = await costTrackingStore.exportCsv(7); expect(result).toBe("date,cost\n2026-01-01,1.50"); expect(invoke).toHaveBeenCalledWith("export_cost_csv", { days: 7 }); }); it("returns null and logs error on failure", async () => { const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); setMockInvokeResult("export_cost_csv", new Error("Export failed")); const result = await costTrackingStore.exportCsv(30); expect(result).toBeNull(); expect(errorSpy).toHaveBeenCalledWith("Failed to export CSV:", expect.any(Error)); errorSpy.mockRestore(); }); }); describe("reset", () => { it("resets costs back to zero", async () => { setMockInvokeResult("get_today_cost", 5.0); setMockInvokeResult("get_week_cost", 15.0); setMockInvokeResult("get_month_cost", 50.0); setMockInvokeResult("get_cost_alerts", []); await costTrackingStore.refresh(); costTrackingStore.reset(); const state = get(costTrackingStore); expect(state.todayCost).toBe(0); expect(state.weekCost).toBe(0); expect(state.monthCost).toBe(0); }); }); }); describe("formattedCosts", () => { beforeEach(() => { costTrackingStore.reset(); }); it("formats the initial zero costs correctly", () => { const costs = get(formattedCosts); expect(costs.today).toBe("$0.0000"); expect(costs.week).toBe("$0.0000"); expect(costs.month).toBe("$0.0000"); }); it("reflects updated costs after a refresh", async () => { setMockInvokeResult("get_today_cost", 1.5); setMockInvokeResult("get_week_cost", 5.0); setMockInvokeResult("get_month_cost", 20.0); setMockInvokeResult("get_cost_alerts", []); await costTrackingStore.refresh(); const costs = get(formattedCosts); expect(costs.today).toBe("$1.50"); expect(costs.week).toBe("$5.00"); expect(costs.month).toBe("$20.00"); expect(costs.todayRaw).toBe(1.5); }); });