From 1a1a663b6a09f7dab9b5decaf6e9eb9120cca778 Mon Sep 17 00:00:00 2001 From: SmartUp Developer Date: Fri, 3 Jul 2026 15:59:47 +0800 Subject: [PATCH] fix: use user log stat endpoint for finance upstream costs --- backend/app/services/finance_service.py | 18 +++++++++++++-- backend/test_finance_daily_summary.py | 29 ++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/backend/app/services/finance_service.py b/backend/app/services/finance_service.py index 14eac7f..9e42633 100644 --- a/backend/app/services/finance_service.py +++ b/backend/app/services/finance_service.py @@ -166,8 +166,19 @@ def fetch_upstream_cost_sub2api(upstream: Upstream, target_date: date) -> tuple[ # Upstream cost — New-API / Nox-API # ───────────────────────────────────────────── +def _new_api_failure_message(data: Any) -> str | None: + if not isinstance(data, dict): + return None + message = data.get("message") or data.get("detail") or data.get("error") + if data.get("success") is False: + return str(message or "上游返回 success=false") + if message and data.get("error"): + return str(message) + return None + + def fetch_upstream_cost_new_api(upstream: Upstream, target_date: date) -> tuple[float, str | None]: - """Fetch cost for New-API/Nox-API upstream via /api/log/stat. + """Fetch cost for New-API/Nox-API upstream via /api/log/self/stat. Keeps UpstreamClient open for the full request so that: - login_password token refresh is available if the stat call gets 401 @@ -193,9 +204,12 @@ def fetch_upstream_cost_new_api(upstream: Upstream, target_date: date) -> tuple[ client.ensure_authenticated() quota_per_unit = client._new_api_quota_per_unit() params = {"type": 2, "start_timestamp": start_ts, "end_timestamp": end_ts} - resp = client._send_request("GET", client._url("/api/log/stat"), params=params) + resp = client._send_request("GET", client._url("/api/log/self/stat"), params=params) resp.raise_for_status() data = resp.json() + failure_message = _new_api_failure_message(data) + if failure_message: + return 0.0, failure_message payload = data.get("data", data) if isinstance(data, dict) else {} if isinstance(payload, dict): quota = payload.get("quota") diff --git a/backend/test_finance_daily_summary.py b/backend/test_finance_daily_summary.py index 8687a6a..3857d31 100644 --- a/backend/test_finance_daily_summary.py +++ b/backend/test_finance_daily_summary.py @@ -282,7 +282,7 @@ def test_upstream_cost_new_api_success(monkeypatch): assert err is None assert abs(amount - 10.0) < 1e-6 # 5_000_000 / 500_000 = 10.0 assert mock_client_instance.calls[0]["method"] == "GET" - assert mock_client_instance.calls[0]["url"] == "http://up.test/api/log/stat" + assert mock_client_instance.calls[0]["url"] == "http://up.test/api/log/self/stat" assert mock_client_instance.calls[0]["params"] == { "type": 2, "start_timestamp": 1782921600, @@ -290,6 +290,33 @@ def test_upstream_cost_new_api_success(monkeypatch): } +def test_upstream_cost_new_api_zero_quota_success(monkeypatch): + u = FakeUpstream(api_prefix="", auth_type="new_api_token") + monkeypatch.setattr( + "app.services.finance_service.UpstreamClient", + lambda **kw: FakeUpstreamClientCtx(response={"data": {"quota": 0}}), + ) + + amount, err = fetch_upstream_cost_new_api(u, date(2026, 7, 2)) + + assert err is None + assert amount == 0.0 + + +def test_upstream_cost_new_api_success_false_uses_remote_message(monkeypatch): + u = FakeUpstream(api_prefix="", auth_type="new_api_token") + monkeypatch.setattr( + "app.services.finance_service.UpstreamClient", + lambda **kw: FakeUpstreamClientCtx(response={"success": False, "message": "无权访问日志统计"}), + ) + + amount, err = fetch_upstream_cost_new_api(u, date(2026, 7, 2)) + + assert amount == 0.0 + assert err == "无权访问日志统计" + assert "quota" not in err + + def test_upstream_cost_new_api_missing_quota(monkeypatch): u = FakeUpstream(api_prefix="", auth_type="new_api_token") monkeypatch.setattr(