Files
Aether/tests/services/test_usage_state_machine.py
fawney19 04ab4bd9f2 feat: 强化用量计费状态机,新增钱包每日消费汇总分类账
- 将 usage.billing_status 默认值从 settled 改为 pending,完善
  pending -> settled/void 的状态转换逻辑,确保终态不可逆
- 新增 WalletDailyUsageLedger 模型和聚合服务,按账单日汇总
  每个钱包的消费金额、请求数和 token 用量
- 前端钱包中心页面集成每日消费流水展示,支持与充值记录混合
  排序和分页
- 新增两个数据库迁移:修复历史数据状态一致性、创建每日汇总表
- 补充计费状态机单元测试

Closes #218

Co-authored-by: LewisPen <LewisPen@nyadoo.com>
2026-03-11 15:11:33 +08:00

86 lines
2.1 KiB
Python

from types import SimpleNamespace
from typing import Any
from unittest.mock import MagicMock
import pytest
from fastapi import HTTPException
from src.services.task.video.cancel import VideoTaskCancelService
from src.services.usage.service import UsageService
class _DummyQuery:
def __init__(self, obj: Any) -> None:
self._obj = obj
def filter(self, *args: Any, **kwargs: Any) -> "_DummyQuery":
return self
def with_for_update(self) -> "_DummyQuery":
return self
def first(self) -> Any:
return self._obj
def test_update_settled_billing_rejects_terminal_usage() -> None:
usage = SimpleNamespace(
request_id="req-1",
billing_status="settled",
finalized_at="2026-03-06T00:00:00+00:00",
total_cost_usd=1.23,
)
db = MagicMock()
db.query.return_value = _DummyQuery(usage)
updated = UsageService.update_settled_billing(
db,
request_id="req-1",
total_cost_usd=2.34,
status="completed",
)
assert updated is False
assert usage.total_cost_usd == 1.23
def test_update_usage_status_skips_terminal_usage() -> None:
usage = SimpleNamespace(
request_id="req-1",
billing_status="settled",
finalized_at="2026-03-06T00:00:00+00:00",
status="completed",
error_message=None,
provider_name="demo",
)
db = MagicMock()
db.query.return_value = _DummyQuery(usage)
result = UsageService.update_usage_status(
db=db,
request_id="req-1",
status="failed",
error_message="boom",
)
assert result is usage
assert usage.status == "completed"
assert usage.error_message is None
db.commit.assert_not_called()
@pytest.mark.asyncio
async def test_task_cancel_rejects_terminal_task() -> None:
task = SimpleNamespace(
id="t1",
user_id="u1",
status="completed",
request_id="req-1",
)
svc = VideoTaskCancelService(MagicMock())
with pytest.raises(HTTPException) as exc:
await svc.cancel_task(task=task, task_id="t1")
assert exc.value.status_code == 409