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.
This commit is contained in:
redshift
2026-07-05 21:27:20 +05:30
parent b2738109e3
commit 1ff3b9a4e1
2 changed files with 39 additions and 19 deletions
+26 -13
View File
@@ -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: <anchor> 10:00Z → local <anchor> 05:00 → local day "<anchor>"
// d2: <anchor+1d> 03:00Z → local <anchor> 22:00 → local day "<anchor>"
// d3: <anchor+1d> 06:00Z → local <anchor+1d> 01:00 → local day "<anchor+1d>"
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 <anchor>
const D2 = _ANCHOR_MS + 1 * 86_400_000 + 3 * 3_600_000; // 03:00Z next day → local day <anchor>
const D3 = _ANCHOR_MS + 1 * 86_400_000 + 6 * 3_600_000; // 06:00Z next day → local day <anchor+1d>
/** 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<UsageTrackingEntry, "id" | "timestamp" | "modelId" | "client" | "totalTokens"> = {
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 <anchor>;
// D3 (next day 06:00Z) buckets to local day <anchor+1d>.
// So: day <anchor> has e1+e2, day <anchor+1d> 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: <anchor+1d> 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);
});
+13 -6
View File
@@ -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<number, { requests: number; satsCost: number }>;
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<number, { requests: number; satsCost: number }> = 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);