fix(FN-1014): add fallback reset time for Claude session window when API omits resets_at
- When the Claude usage API omits resets_at for the 5-hour session window, fall back to using the full window duration as resetMs - This enables pace calculation and reset text display even without an explicit reset timestamp - Update existing test to verify fallback behavior (resetMs = 5h, resetText = 'resets in 5h') - Add new test for pace calculation with fallback reset time
This commit is contained in:
@@ -769,7 +769,7 @@ describe("usage", () => {
|
||||
expect(weeklyWindow.resetAt).toBeDefined();
|
||||
});
|
||||
|
||||
it("omits resetAt when API response has no resets_at field", async () => {
|
||||
it("uses fallback reset time for session window when API omits resets_at", async () => {
|
||||
setupClaudeMocks({
|
||||
credFileContent: {
|
||||
accessToken: "test-token",
|
||||
@@ -781,7 +781,7 @@ describe("usage", () => {
|
||||
setupClaudeApiResponse({
|
||||
five_hour: {
|
||||
utilization: 30.0,
|
||||
// no resets_at field
|
||||
// no resets_at field — triggers fallback
|
||||
},
|
||||
});
|
||||
|
||||
@@ -791,7 +791,40 @@ describe("usage", () => {
|
||||
expect(claude.status).toBe("ok");
|
||||
const sessionWindow = claude.windows.find((w) => w.label.includes("Session"))!;
|
||||
expect(sessionWindow.resetAt).toBeUndefined();
|
||||
expect(sessionWindow.resetText).toBeNull();
|
||||
// Fallback: when resets_at is missing, session window gets resetMs = 5h
|
||||
expect(sessionWindow.resetMs).toBe(5 * 60 * 60 * 1000);
|
||||
expect(sessionWindow.resetText).toBe("resets in 5h");
|
||||
});
|
||||
|
||||
it("calculates pace for session window with fallback reset time", async () => {
|
||||
setupClaudeMocks({
|
||||
credFileContent: {
|
||||
accessToken: "test-token",
|
||||
scopes: ["user:profile"],
|
||||
subscriptionType: "pro",
|
||||
},
|
||||
});
|
||||
|
||||
setupClaudeApiResponse({
|
||||
five_hour: {
|
||||
utilization: 50.0,
|
||||
// no resets_at — fallback uses full 5h duration
|
||||
},
|
||||
});
|
||||
|
||||
const providers = await fetchAllProviderUsage();
|
||||
const claude = providers.find((p) => p.name === "Claude")!;
|
||||
|
||||
expect(claude.status).toBe("ok");
|
||||
const sessionWindow = claude.windows.find((w) => w.label.includes("Session"))!;
|
||||
expect(sessionWindow.percentUsed).toBe(50);
|
||||
// With fallback resetMs = 5h and percentUsed = 50%, the window duration
|
||||
// and reset time are both set, enabling pace indicator on the frontend.
|
||||
expect(sessionWindow.windowDurationMs).toBe(5 * 60 * 60 * 1000);
|
||||
expect(sessionWindow.resetMs).toBe(5 * 60 * 60 * 1000);
|
||||
expect(sessionWindow.resetText).toBe("resets in 5h");
|
||||
// resetAt stays undefined because the API didn't provide it
|
||||
expect(sessionWindow.resetAt).toBeUndefined();
|
||||
});
|
||||
|
||||
it("handles empty JSON object from API gracefully", async () => {
|
||||
|
||||
@@ -842,6 +842,13 @@ async function fetchClaudeUsage(): Promise<ProviderUsage> {
|
||||
const msLeft = new Date(resetAtValue).getTime() - Date.now();
|
||||
resetMs = msLeft > 0 ? msLeft : 0;
|
||||
resetText = msLeft > 0 ? `resets in ${formatDuration(msLeft)}` : "resetting now";
|
||||
} else if (windowDurationMs === FIVE_HOURS_MS) {
|
||||
// Fallback for session window: when the API doesn't provide reset time,
|
||||
// use the full window duration as a best-effort estimate. This enables
|
||||
// pace calculation and reset text for the session (5h) window even when
|
||||
// the API omits resets_at / reset_at / resetAt fields.
|
||||
resetMs = windowDurationMs;
|
||||
resetText = "resets in 5h";
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user