From 3d472aa21e442de52c648d1d952c3d084a5a63c1 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 24 Mar 2026 05:28:17 -0700 Subject: [PATCH] address greptile review feedback (greploop iteration 1) - Fix P1: strip existing time component from expiration_date before appending end-of-day time to prevent NaN when backend returns full ISO timestamps - Fix P2: append Z suffix for explicit UTC parsing - Fix P2: restore global.Date in afterEach to prevent mock leaking - Add test for full ISO timestamp input edge case Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/LicenseExpiryBanner.test.tsx | 17 +++++++++++++++-- .../src/components/LicenseExpiryBanner.tsx | 5 ++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/components/LicenseExpiryBanner.test.tsx b/ui/litellm-dashboard/src/components/LicenseExpiryBanner.test.tsx index 4dc2ffed49c..c22cffa4e17 100644 --- a/ui/litellm-dashboard/src/components/LicenseExpiryBanner.test.tsx +++ b/ui/litellm-dashboard/src/components/LicenseExpiryBanner.test.tsx @@ -19,6 +19,7 @@ const mockGetLicenseInfo = vi.mocked(getLicenseInfo); const FIXED_NOW = new Date("2026-03-15T12:00:00Z").getTime(); let realDateNow: () => number; +let OrigDate: typeof Date; function makeLicense(expirationDate: string) { return { @@ -34,9 +35,8 @@ describe("LicenseExpiryBanner", () => { beforeEach(() => { vi.clearAllMocks(); realDateNow = Date.now; + OrigDate = Date; // capture before patching so we can restore in afterEach Date.now = () => FIXED_NOW; - // Also override new Date() with no args - const OrigDate = Date; const MockDate = class extends OrigDate { constructor(...args: any[]) { if (args.length === 0) { @@ -70,6 +70,7 @@ describe("LicenseExpiryBanner", () => { }); afterEach(() => { + global.Date = OrigDate; Date.now = realDateNow; }); @@ -145,6 +146,18 @@ describe("LicenseExpiryBanner", () => { expect(container.innerHTML).toBe(""); }); + it("should handle expiration_date with a full ISO timestamp (not just date-only)", async () => { + // Backend might return "2026-03-22T15:00:00Z" instead of "2026-03-22" + // The component should strip the time part and still render correctly. + mockGetLicenseInfo.mockResolvedValue(makeLicense("2026-03-22T15:00:00Z")); + + render(); + + expect( + await screen.findByText("Enterprise License Expiring in 8 days") + ).toBeInTheDocument(); + }); + it("should not render when has_license is false", async () => { mockGetLicenseInfo.mockResolvedValue({ ...makeLicense("2026-03-20"), diff --git a/ui/litellm-dashboard/src/components/LicenseExpiryBanner.tsx b/ui/litellm-dashboard/src/components/LicenseExpiryBanner.tsx index 58a7bff32a6..a265a8018d3 100644 --- a/ui/litellm-dashboard/src/components/LicenseExpiryBanner.tsx +++ b/ui/litellm-dashboard/src/components/LicenseExpiryBanner.tsx @@ -23,7 +23,10 @@ export const LicenseExpiryBanner: React.FC = () => { return null; } - const expDate = new Date(licenseInfo.expiration_date + "T23:59:59"); + // Strip any existing time component so we always get a valid date-only base, + // then append end-of-day in UTC to avoid timezone-dependent parsing. + const dateOnly = licenseInfo.expiration_date.split("T")[0]; + const expDate = new Date(dateOnly + "T23:59:59Z"); const now = new Date(); const diffMs = expDate.getTime() - now.getTime(); const daysRemaining = Math.ceil(diffMs / (1000 * 60 * 60 * 24));