generated from nhcarrigan/template
293 lines
9.2 KiB
TypeScript
293 lines
9.2 KiB
TypeScript
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);
|
|
});
|
|
});
|