From 1ff3b9a4e1f0f1694bc0ed31fe342aecfd10d284 Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Sun, 5 Jul 2026 21:27:20 +0530 Subject: [PATCH] Fix renderToday definite-assignment and usage-summary stale fixtures renderToday declared todayStats/recentDays/hourlyMap with let but only assigned inside if (stats.summary); TypeScript flagged them as used before assignment (TS2454). Initialize with sensible defaults so the function degrades gracefully when summary is missing. The usage-summary tz-bucketing test used hardcoded May 2026 timestamps with a comment dated 2026-06-02. Those entries aged out of getUsageSummary's 30-day rolling window, so days came back empty. Recompute timestamps relative to now and assert on dynamically-derived local-day date strings. --- src/daemon/http/usage-summary.test.ts | 39 ++++++++++++++++++--------- src/tui/usage/render.ts | 19 ++++++++----- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/daemon/http/usage-summary.test.ts b/src/daemon/http/usage-summary.test.ts index 59f97df..f97a201 100644 --- a/src/daemon/http/usage-summary.test.ts +++ b/src/daemon/http/usage-summary.test.ts @@ -6,15 +6,27 @@ import { getUsageSummary, __resetUsageSummaryCacheForTest } from "./usage-summar // ─── Test fixtures ──────────────────────────────────────────────────────────── // -// All timestamps fall within 30 days of 2026-06-02 (today as of this writing). +// All timestamps are computed relative to "now" so the fixtures never age +// out of getUsageSummary's 30-day rolling window. We anchor to a recent UTC +// midnight ~10 days ago and keep the same hour-of-day structure the +// tz-bucketing assertions depend on. +// // tz = 300 (UTC-5 / EST). After shifting by -300min, UTC dates become: -// d1: 2026-05-20T10:00Z → shifted 2026-05-20T05:00Z → local day "2026-05-20", hour 05 -// d2: 2026-05-21T03:00Z → shifted 2026-05-20T22:00Z → local day "2026-05-20", hour 22 -// d3: 2026-05-21T06:00Z → shifted 2026-05-21T01:00Z → local day "2026-05-21", hour 01 +// d1: 10:00Z → local 05:00 → local day "" +// d2: 03:00Z → local 22:00 → local day "" +// d3: 06:00Z → local 01:00 → local day "" -const D1 = new Date("2026-05-20T10:00:00Z").getTime(); // local 2026-05-20, hour 05 -const D2 = new Date("2026-05-21T03:00:00Z").getTime(); // local 2026-05-20, hour 22 -const D3 = new Date("2026-05-21T06:00:00Z").getTime(); // local 2026-05-21, hour 01 +const _BASE_UTC_MIDNIGHT = Math.floor(Date.now() / 86_400_000) * 86_400_000; +const _ANCHOR_MS = _BASE_UTC_MIDNIGHT - 10 * 86_400_000; // 10 days ago UTC +const D1 = _ANCHOR_MS + 10 * 3_600_000; // 10:00Z → local day +const D2 = _ANCHOR_MS + 1 * 86_400_000 + 3 * 3_600_000; // 03:00Z next day → local day +const D3 = _ANCHOR_MS + 1 * 86_400_000 + 6 * 3_600_000; // 06:00Z next day → local day + +/** Compute the local-day date string the same way the SDK's day grouping does. */ +function localDayDateStr(ts: number, tzOffsetMinutes: number): string { + const d = new Date(ts - tzOffsetMinutes * 60_000); + return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}`; +} const BASE_ENTRY: Omit = { baseUrl: "https://api.openai.com/", @@ -152,23 +164,24 @@ describe("getUsageSummary", () => { const summary = await getUsageSummary(driver, CLIENTS, TZ); const { days } = summary; - // D1 (10:00Z) → local 2026-05-20, D2 (03:00Z) → local 2026-05-20, D3 (06:00Z) → local 2026-05-21 - // So: day 2026-05-20 has e1+e2, day 2026-05-21 has e3+e4 + // D1 (10:00Z) and D2 (next day 03:00Z) both bucket to local day ; + // D3 (next day 06:00Z) buckets to local day . + // So: day has e1+e2, day has e3+e4 expect(days.length).toBeGreaterThanOrEqual(2); - // Most-recent-first: 2026-05-21 first - expect(days[0]!.date).toBe("2026-05-21"); + // Most-recent-first: first + expect(days[0]!.date).toBe(localDayDateStr(D3, TZ)); expect(days[0]!.requests).toBe(2); // e3, e4 expect(days[0]!.satsCost).toBe(8 + 50); - expect(days[1]!.date).toBe("2026-05-20"); + expect(days[1]!.date).toBe(localDayDateStr(D1, TZ)); expect(days[1]!.requests).toBe(2); // e1, e2 expect(days[1]!.satsCost).toBe(10 + 20); }); it("returns empty hoursToday (test entries are not today)", async () => { const summary = await getUsageSummary(driver, CLIENTS, TZ); - // Test entries are in May 2026, not today (June 2026) + // Test entries are ~10 days ago, not today expect(summary.hoursToday).toHaveLength(0); }); diff --git a/src/tui/usage/render.ts b/src/tui/usage/render.ts index 2fe7b41..727598e 100644 --- a/src/tui/usage/render.ts +++ b/src/tui/usage/render.ts @@ -278,18 +278,25 @@ export function renderToday(stats: UsageStats, width: number): string { const _now = new Date(); const todayDateStr = `${_now.getFullYear()}-${String(_now.getMonth() + 1).padStart(2, "0")}-${String(_now.getDate()).padStart(2, "0")}`; - let todayStats: { date: string; requests: number; satsCost: number; promptTokens: number; completionTokens: number; totalTokens: number }; - let recentDays: Array<{ date: string; requests: number; satsCost: number; totalTokens: number; promptTokens: number; completionTokens: number }>; - let hourlyMap: Map; + let todayStats: { date: string; requests: number; satsCost: number; promptTokens: number; completionTokens: number; totalTokens: number } = { + date: todayDateStr, + requests: 0, + satsCost: 0, + promptTokens: 0, + completionTokens: 0, + totalTokens: 0, + }; + let recentDays: Array<{ date: string; requests: number; satsCost: number; totalTokens: number; promptTokens: number; completionTokens: number }> = []; + let hourlyMap: Map = new Map(); if (stats.summary) { // Use server-aggregated data const { days, hoursToday } = stats.summary; // days[0] is most-recent-first; check if it's today const todayDayStat = days[0]?.date === todayDateStr ? days[0] : undefined; - todayStats = todayDayStat - ? { date: todayDayStat.date, requests: todayDayStat.requests, satsCost: todayDayStat.satsCost, promptTokens: todayDayStat.promptTokens, completionTokens: todayDayStat.completionTokens, totalTokens: todayDayStat.totalTokens } - : { date: todayDateStr, requests: 0, satsCost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 }; + if (todayDayStat) { + todayStats = { date: todayDayStat.date, requests: todayDayStat.requests, satsCost: todayDayStat.satsCost, promptTokens: todayDayStat.promptTokens, completionTokens: todayDayStat.completionTokens, totalTokens: todayDayStat.totalTokens }; + } // Exclude today by date (it has its own box) rather than by position — // days[0] is only today when there was activity today. recentDays = days.filter((d) => d.date !== todayDateStr).slice(0, 6);