Files
cma-management/backend/tests/test_cash.py
T
Hermes CI Fix a13a080381 test: CMA自动化测试补覆盖 226→452用例, 覆盖率36%→60%
- 新增8个测试文件(bot_bridge/kpi_causality/cash/predict/reports/tax_compliance/expenses/probe_cost)
- 增强 budget/auth/users + conftest账套模式适配
- 测试驱动修复: bot_bridge导入batch_id→source_batch; cash_forecast extra空dict
- 全量: 451 passed, 1 xfailed; 报告 docs/cma-test-coverage-report.md
2026-08-20 06:57:24 +08:00

221 lines
9.6 KiB
Python

"""现金流模块测试 — 收付款计划 + 资金缺口预测 + 看板
覆盖 cash.py 核心端点:
gap-forecast / balance GET+POST / plans CRUD / plans{id}/complete /
upcoming / dashboard / check-alerts / alerts/status
"""
from datetime import datetime, timedelta
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app.models import CashPlan
from tests.conftest import create_test_user, get_token_for_user, auth_header
BASE = "/api/cma/cash"
def _future_date(days: int = 5) -> str:
return (datetime.now() + timedelta(days=days)).strftime("%Y-%m-%d")
def _past_date(days: int = 5) -> str:
return (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
class TestBalance:
def test_get_balance_default(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.get(f"{BASE}/balance", headers=auth_header(token))
assert resp.status_code == 200
assert resp.json()["entity_id"] == 1
def test_set_balance(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/balance", headers=auth_header(token),
json={"current_cash": 88.5})
assert resp.status_code == 200
assert resp.json()["current_cash"] == 88.5
get_resp = client.get(f"{BASE}/balance", headers=auth_header(token))
assert get_resp.json()["current_cash"] == 88.5
def test_set_balance_negative(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/balance", headers=auth_header(token),
json={"current_cash": -5})
assert resp.status_code == 400
class TestPlans:
def test_create_plan(self, client: TestClient, db: Session):
"""创建收款计划"""
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/plans", headers=auth_header(token), json={
"plan_type": "receive", "amount": 50.0, "plan_date": _future_date(),
"counterparty": "客户A", "owner": "张三",
})
assert resp.status_code == 200
data = resp.json()["data"]
assert data["plan_type"] == "receive"
assert data["status"] == "pending"
def test_create_plan_invalid_type(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "weird", "amount": 10, "plan_date": _future_date()})
assert resp.status_code == 400
def test_create_plan_zero_amount(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "pay", "amount": 0, "plan_date": _future_date()})
assert resp.status_code == 400
def test_create_plan_missing_date(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "pay", "amount": 10})
assert resp.status_code == 400
def test_create_plan_bad_date(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "pay", "amount": 10, "plan_date": "not-a-date"})
assert resp.status_code == 400
def test_list_plans_filters(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "receive", "amount": 10, "plan_date": _future_date()})
client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "pay", "amount": 20, "plan_date": _future_date()})
resp = client.get(f"{BASE}/plans?plan_type=receive", headers=auth_header(token))
assert resp.status_code == 200
assert resp.json()["total"] == 1
assert resp.json()["data"][0]["plan_type"] == "receive"
def test_list_plans_bad_month(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.get(f"{BASE}/plans?month=bad", headers=auth_header(token))
assert resp.status_code == 400
def test_update_plan(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
pid = client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "receive", "amount": 10, "plan_date": _future_date()}
).json()["data"]["id"]
resp = client.put(f"{BASE}/plans/{pid}", headers=auth_header(token),
json={"amount": 30, "counterparty": "客户B"})
assert resp.status_code == 200
assert resp.json()["data"]["amount"] == 30.0
def test_update_plan_not_found(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.put(f"{BASE}/plans/99999", headers=auth_header(token), json={"amount": 10})
assert resp.status_code == 404
def test_delete_plan(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
pid = client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "pay", "amount": 10, "plan_date": _future_date()}
).json()["data"]["id"]
resp = client.delete(f"{BASE}/plans/{pid}", headers=auth_header(token))
assert resp.status_code == 200
assert resp.json()["message"] == "计划已删除"
def test_complete_plan(self, client: TestClient, db: Session):
"""标记收款完成"""
create_test_user(db)
token = get_token_for_user(client)
pid = client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "receive", "amount": 10, "plan_date": _future_date()}
).json()["data"]["id"]
resp = client.post(f"{BASE}/plans/{pid}/complete", headers=auth_header(token))
assert resp.status_code == 200
data = resp.json()["data"]
assert data["status"] == "completed"
assert data["paid_amount"] == 10.0
def test_complete_plan_not_found(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/plans/99999/complete", headers=auth_header(token))
assert resp.status_code == 404
class TestUpcomingDashboard:
def test_upcoming(self, client: TestClient, db: Session):
"""到期提醒 + 逾期"""
create_test_user(db)
token = get_token_for_user(client)
client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "receive", "amount": 10, "plan_date": _future_date(3)})
client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "pay", "amount": 20, "plan_date": _past_date(3)})
resp = client.get(f"{BASE}/upcoming?days=7", headers=auth_header(token))
assert resp.status_code == 200
data = resp.json()
assert len(data["upcoming"]) == 1
assert len(data["overdue"]) == 1
assert data["overdue_receive_amount"] == 0.0
assert data["overdue_pay_amount"] == 20.0
def test_dashboard(self, client: TestClient, db: Session):
"""资金看板"""
create_test_user(db)
token = get_token_for_user(client)
client.post(f"{BASE}/plans", headers=auth_header(token),
json={"plan_type": "receive", "amount": 10, "plan_date": _future_date(3)})
resp = client.get(f"{BASE}/dashboard", headers=auth_header(token))
assert resp.status_code == 200
data = resp.json()
assert "calendar" in data
def test_dashboard_bad_month(self, client: TestClient, db: Session):
create_test_user(db)
token = get_token_for_user(client)
resp = client.get(f"{BASE}/dashboard?month=bad", headers=auth_header(token))
assert resp.status_code == 400
def test_gap_forecast(self, client: TestClient, db: Session):
"""资金缺口预测"""
create_test_user(db)
token = get_token_for_user(client)
resp = client.get(f"{BASE}/gap-forecast?days=10&current_cash=100",
headers=auth_header(token))
assert resp.status_code == 200
class TestAlerts:
def test_check_alerts(self, client: TestClient, db: Session):
"""触发资金预警检查"""
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(f"{BASE}/check-alerts", headers=auth_header(token), json={})
assert resp.status_code == 200
def test_alerts_status(self, client: TestClient, db: Session):
"""预警状态查询"""
create_test_user(db)
token = get_token_for_user(client)
resp = client.get(f"{BASE}/alerts/status", headers=auth_header(token))
assert resp.status_code == 200
data = resp.json()
assert "entity_id" in data and "critical_line" in data