fix(proxy): anchor the tool spend clamp floor to UTC midnight

With end_date omitted the floor was end-anchored to now including its
time-of-day, so an explicit start_date exactly 30 days back parsed as
midnight, compared below the floor, and was invisibly clamped to a
mid-day instant: up to a day of spend disappeared while the response
start_date still printed the full calendar date. Anchoring the floor to
today's UTC midnight makes every comparison in the window derivation
date-pure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Tin Chi Lo 2026-07-24 18:00:16 -07:00
parent 5fc4a058c1
commit 3ca9d551dc
2 changed files with 31 additions and 2 deletions

View file

@ -232,7 +232,11 @@ async def get_tool_spend(
now = datetime.now(timezone.utc)
end_day = _parse_day_start(end_date)
window_floor = (end_day or now) - timedelta(days=TOOL_SPEND_MAX_WINDOW_DAYS)
# Anchor the floor to a midnight so the clamp compares dates with dates:
# parsed start_dates are midnight-aligned, and a floor carrying now's
# time-of-day would invisibly truncate an explicit start_date to mid-day.
today = now.replace(hour=0, minute=0, second=0, microsecond=0)
window_floor = (end_day or today) - timedelta(days=TOOL_SPEND_MAX_WINDOW_DAYS)
start_dt = _parse_day_start(start_date) or window_floor
if start_dt < window_floor:
start_dt = window_floor

View file

@ -9,7 +9,7 @@ imports these inside function bodies to avoid circular imports.
import os
import sys
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from typing import Optional
from unittest.mock import AsyncMock, MagicMock, patch
@ -232,6 +232,31 @@ class TestToolManagementEndpoints:
assert call.args[1] == datetime(2026, 6, 25, tzinfo=timezone.utc).isoformat()
assert resp.json()["start_date"] == "2026-06-25"
def test_tool_spend_start_honored_when_end_date_omitted(self):
# Regression: with end_date omitted the floor anchors to today's UTC
# midnight, not now's time-of-day, so an explicit start_date exactly 30
# days back is served from midnight rather than truncated to mid-day.
prisma = MagicMock()
prisma.db.query_raw = AsyncMock(return_value=[])
floor_day = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=30)
with patch("litellm.proxy.proxy_server.prisma_client", prisma):
resp = self.client.get(f"/v1/tool/spend?start_date={floor_day.strftime('%Y-%m-%d')}")
assert resp.status_code == 200
for call in prisma.db.query_raw.await_args_list:
assert call.args[1] == floor_day.isoformat()
assert resp.json()["start_date"] == floor_day.strftime("%Y-%m-%d")
def test_tool_spend_clamp_without_end_date_lands_on_midnight(self):
prisma = MagicMock()
prisma.db.query_raw = AsyncMock(return_value=[])
floor_day = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=30)
with patch("litellm.proxy.proxy_server.prisma_client", prisma):
resp = self.client.get("/v1/tool/spend?start_date=2020-01-01")
assert resp.status_code == 200
for call in prisma.db.query_raw.await_args_list:
assert call.args[1] == floor_day.isoformat()
assert resp.json()["start_date"] == floor_day.strftime("%Y-%m-%d")
def test_tool_spend_total_query_bounds_outer_spendlogs_scan(self):
prisma = MagicMock()
prisma.db.query_raw = AsyncMock(return_value=[])