Files
Octop/tests/unit/db/test_usage_window.py
HUANG Cheng cda6e3000f feat(usage): date-range filter and Excel export for token stats (#599)
* feat(usage): date-range filter and Excel export for token stats

Add RangePicker presets/custom ranges, multi-sheet Excel export with
localized headers and charts, and filter coverage for #154.

* fix(usage): export detail sheet in ascending time order

Keep the newest N rows when truncating, then reverse so Excel opens
oldest-first for chronological reading.

* fix(usage): sort export detail ascending in SQL

Order detail rows with a newest-N subquery then ts ASC so Excel opens
oldest-first; restart-sensitive Python reverse was easy to miss.

* fix(usage): keep Excel TOTAL row out of Sort range

Leave a blank spacer above the SUM TOTAL so Excel contiguous Sort
does not move 合计 with the filtered detail rows.
2026-09-08 14:21:00 +08:00

71 lines
2.2 KiB
Python

"""Unit tests for usage window resolution (server timezone day/month)."""
from __future__ import annotations
from datetime import datetime
from zoneinfo import ZoneInfo
import pytest
from octop.infra.db.repos.usage import resolve_usage_window
TZ = "Asia/Shanghai"
def _ts(year: int, month: int, day: int, hour: int = 0, minute: int = 0) -> int:
return int(datetime(year, month, day, hour, minute, tzinfo=ZoneInfo(TZ)).timestamp())
def test_resolve_day_window_shanghai() -> None:
start, end = resolve_usage_window("day:2026-09-07", timezone=TZ)
assert start == _ts(2026, 9, 7)
assert end == _ts(2026, 9, 8)
def test_resolve_month_window_shanghai() -> None:
start, end = resolve_usage_window("month:2026-09", timezone=TZ)
assert start == _ts(2026, 9, 1)
assert end == _ts(2026, 10, 1)
def test_resolve_month_december_rolls_year() -> None:
start, end = resolve_usage_window("month:2025-12", timezone=TZ)
assert start == _ts(2025, 12, 1)
assert end == _ts(2026, 1, 1)
def test_resolve_today_uses_local_midnight() -> None:
# 2026-09-07 15:30 CST
now = _ts(2026, 9, 7, 15, 30)
start, end = resolve_usage_window("today", timezone=TZ, now=now)
assert start == _ts(2026, 9, 7)
assert end == now + 1
def test_resolve_yesterday_local() -> None:
now = _ts(2026, 9, 7, 10, 0)
start, end = resolve_usage_window("yesterday", timezone=TZ, now=now)
assert start == _ts(2026, 9, 6)
assert end == _ts(2026, 9, 7)
def test_resolve_invalid_day_raises() -> None:
with pytest.raises(ValueError, match="invalid usage window"):
resolve_usage_window("day:2026-9-7", timezone=TZ)
def test_resolve_invalid_month_raises() -> None:
with pytest.raises(ValueError, match="invalid usage window"):
resolve_usage_window("month:2026-9", timezone=TZ)
def test_resolve_range_inclusive_days() -> None:
start, end = resolve_usage_window("range:2026-09-01:2026-09-07", timezone=TZ)
assert start == _ts(2026, 9, 1)
assert end == _ts(2026, 9, 8)
def test_resolve_range_inverted_raises() -> None:
with pytest.raises(ValueError, match="invalid usage window"):
resolve_usage_window("range:2026-09-08:2026-09-07", timezone=TZ)