/** * total-usage-semantics tests * * Verify that total usage reads in display paths use ALL_TIME_MAX_AGE_DAYS (Infinity) * to skip the date filter entirely, querying all-time data. * * Key insight: The usage/quota aggregation functions default maxAgeDays to 365. * For display purposes (showing "total" usage), we want all-time semantics, which * means passing Infinity to skip the date filter. * * IMPORTANT: This test only covers DISPLAY paths. Enforcement paths (RateLimitService) * are intentionally NOT modified. */ import { beforeEach, describe, expect, it, vi } from "vitest"; // All-time max age constant - Infinity means no date filter const ALL_TIME_MAX_AGE_DAYS = Infinity; // Mock functions const getSessionMock = vi.fn(); const sumUserTotalCostMock = vi.fn(); const sumKeyQuotaCostsByIdMock = vi.fn(); const sumUserQuotaCostsMock = vi.fn(); const sumUserCostInTimeRangeMock = vi.fn(); const getTimeRangeForPeriodMock = vi.fn(); const getTimeRangeForPeriodWithModeMock = vi.fn(); const getKeySessionCountMock = vi.fn(); const getUserSessionCountMock = vi.fn(); const findUserByIdMock = vi.fn(); // Mock modules vi.mock("@/lib/auth", () => ({ getSession: () => getSessionMock(), })); vi.mock("@/repository/statistics", () => ({ sumUserCostInTimeRange: (...args: unknown[]) => sumUserCostInTimeRangeMock(...args), sumUserTotalCost: (...args: unknown[]) => sumUserTotalCostMock(...args), sumKeyQuotaCostsById: (...args: unknown[]) => sumKeyQuotaCostsByIdMock(...args), sumUserQuotaCosts: (...args: unknown[]) => sumUserQuotaCostsMock(...args), })); vi.mock("@/lib/rate-limit/time-utils", () => ({ getTimeRangeForPeriod: (...args: unknown[]) => getTimeRangeForPeriodMock(...args), getTimeRangeForPeriodWithMode: (...args: unknown[]) => getTimeRangeForPeriodWithModeMock(...args), })); vi.mock("@/lib/session-tracker", () => ({ SessionTracker: { getKeySessionCount: (...args: unknown[]) => getKeySessionCountMock(...args), getUserSessionCount: (...args: unknown[]) => getUserSessionCountMock(...args), }, })); vi.mock("@/repository/user", () => ({ findUserById: (...args: unknown[]) => findUserByIdMock(...args), })); vi.mock("@/lib/logger", () => ({ logger: { trace: vi.fn(), debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }, })); vi.mock("next-intl/server", () => ({ getTranslations: vi.fn(() => (key: string) => key), })); describe("total-usage-semantics", () => { beforeEach(() => { vi.clearAllMocks(); // Default time range mocks const now = new Date(); const defaultRange = { startTime: now, endTime: now }; getTimeRangeForPeriodMock.mockResolvedValue(defaultRange); getTimeRangeForPeriodWithModeMock.mockResolvedValue(defaultRange); // Default cost mocks sumUserTotalCostMock.mockResolvedValue(0); sumUserCostInTimeRangeMock.mockResolvedValue(0); getKeySessionCountMock.mockResolvedValue(0); getUserSessionCountMock.mockResolvedValue(0); const emptyCosts = { cost5h: 0, costDaily: 0, costWeekly: 0, costMonthly: 0, costTotal: 0, }; sumKeyQuotaCostsByIdMock.mockResolvedValue(emptyCosts); sumUserQuotaCostsMock.mockResolvedValue(emptyCosts); }); describe("getMyQuota in my-usage.ts", () => { it("should call sumKeyQuotaCostsById with ALL_TIME_MAX_AGE_DAYS for key total cost", async () => { // Setup session mock getSessionMock.mockResolvedValue({ key: { id: 1, key: "test-key-hash", name: "Test Key", dailyResetTime: "00:00", dailyResetMode: "fixed", limit5hUsd: null, limitDailyUsd: null, limitWeeklyUsd: null, limitMonthlyUsd: null, limitTotalUsd: null, limitConcurrentSessions: null, providerGroup: null, isEnabled: true, expiresAt: null, }, user: { id: 1, name: "Test User", dailyResetTime: "00:00", dailyResetMode: "fixed", limit5hUsd: null, dailyQuota: null, limitWeeklyUsd: null, limitMonthlyUsd: null, limitTotalUsd: null, limitConcurrentSessions: null, rpm: null, providerGroup: null, isEnabled: true, expiresAt: null, allowedModels: [], allowedClients: [], }, }); // Import and call the function const { getMyQuota } = await import("@/actions/my-usage"); await getMyQuota(); expect(sumKeyQuotaCostsByIdMock).toHaveBeenCalledWith( 1, expect.any(Object), ALL_TIME_MAX_AGE_DAYS, null ); }); it("should call sumUserQuotaCosts with ALL_TIME_MAX_AGE_DAYS for user total cost", async () => { // Setup session mock getSessionMock.mockResolvedValue({ key: { id: 1, key: "test-key-hash", name: "Test Key", dailyResetTime: "00:00", dailyResetMode: "fixed", limit5hUsd: null, limitDailyUsd: null, limitWeeklyUsd: null, limitMonthlyUsd: null, limitTotalUsd: null, limitConcurrentSessions: null, providerGroup: null, isEnabled: true, expiresAt: null, }, user: { id: 1, name: "Test User", dailyResetTime: "00:00", dailyResetMode: "fixed", limit5hUsd: null, dailyQuota: null, limitWeeklyUsd: null, limitMonthlyUsd: null, limitTotalUsd: null, limitConcurrentSessions: null, rpm: null, providerGroup: null, isEnabled: true, expiresAt: null, allowedModels: [], allowedClients: [], }, }); // Import and call the function const { getMyQuota } = await import("@/actions/my-usage"); await getMyQuota(); expect(sumUserQuotaCostsMock).toHaveBeenCalledWith( 1, expect.any(Object), ALL_TIME_MAX_AGE_DAYS, null ); }); }); describe("getUserAllLimitUsage in users.ts", () => { it("should call sumUserTotalCost with ALL_TIME_MAX_AGE_DAYS", async () => { // Setup session mock getSessionMock.mockResolvedValue({ user: { id: 1, role: "admin", }, }); // Setup user mock findUserByIdMock.mockResolvedValue({ id: 1, name: "Test User", dailyResetTime: "00:00", dailyResetMode: "fixed", limit5hUsd: null, dailyQuota: null, limitWeeklyUsd: null, limitMonthlyUsd: null, limitTotalUsd: null, }); // Import and call the function const { getUserAllLimitUsage } = await import("@/actions/users"); await getUserAllLimitUsage(1); // Verify sumUserTotalCost was called with Infinity (all-time) // 3rd arg is user.costResetAt (undefined when not set on mock user) const calls = sumUserTotalCostMock.mock.calls; expect(calls.length).toBe(1); expect(calls[0][0]).toBe(1); expect(calls[0][1]).toBe(Infinity); }); }); describe("ALL_TIME_MAX_AGE_DAYS constant value", () => { it("should be Infinity for all-time semantics", () => { expect(ALL_TIME_MAX_AGE_DAYS).toBe(Infinity); }); }); });