Files
cma-management/backend/tests/test_budget.py
T

195 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""预算管理模块测试 — 预算计划CRUD + 自动分解 + 版本"""
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from tests.conftest import create_test_user, get_token_for_user, auth_header, create_test_kpi
class TestBudgetPlans:
"""预算计划CRUD测试"""
BASE = "/api/cma/budget"
def test_list_plans_empty(self, client: TestClient, db: Session):
"""空列表"""
create_test_user(db)
token = get_token_for_user(client)
resp = client.get(f"{self.BASE}/plans", headers=auth_header(token))
assert resp.status_code == 200
assert resp.json()["data"] == []
def test_create_plan(self, client: TestClient, db: Session):
"""创建预算计划"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="BUDGET_KPI_01")
resp = client.post(
f"{self.BASE}/plans",
headers=auth_header(token),
json={"kpi_id": kpi.id, "period": "2026-06", "budget_value": 50000.0},
)
assert resp.status_code == 200
assert resp.json()["message"] == "预算已创建"
assert resp.json()["id"] > 0
def test_create_plan_duplicate_upsert(self, client: TestClient, db: Session):
"""重复创建同一KPI+期间 → 更新而非新增"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="BUDGET_KPI_02")
# 创建
client.post(
f"{self.BASE}/plans",
headers=auth_header(token),
json={"kpi_id": kpi.id, "period": "2026-06", "budget_value": 30000.0},
)
# 再次创建(更新)
resp = client.post(
f"{self.BASE}/plans",
headers=auth_header(token),
json={"kpi_id": kpi.id, "period": "2026-06", "budget_value": 35000.0},
)
assert resp.status_code == 200
assert resp.json()["message"] == "预算已更新"
def test_create_plan_missing_fields(self, client: TestClient, db: Session):
"""缺少必要参数被拒绝"""
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(
f"{self.BASE}/plans",
headers=auth_header(token),
json={"kpi_id": 1}, # 缺少 period 和 budget_value
)
assert resp.status_code == 400
def test_update_plan(self, client: TestClient, db: Session):
"""更新预算计划"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="BUDGET_KPI_03")
create_resp = client.post(
f"{self.BASE}/plans",
headers=auth_header(token),
json={"kpi_id": kpi.id, "period": "2026-06", "budget_value": 100.0},
)
plan_id = create_resp.json()["id"]
resp = client.put(
f"{self.BASE}/plans/{plan_id}",
headers=auth_header(token),
json={"budget_value": 200.0, "remark": "已更新"},
)
assert resp.status_code == 200
def test_delete_plan(self, client: TestClient, db: Session):
"""删除预算计划"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="BUDGET_KPI_04")
create_resp = client.post(
f"{self.BASE}/plans",
headers=auth_header(token),
json={"kpi_id": kpi.id, "period": "2026-06", "budget_value": 100.0},
)
plan_id = create_resp.json()["id"]
resp = client.delete(f"{self.BASE}/plans/{plan_id}", headers=auth_header(token))
assert resp.status_code == 200
assert resp.json()["message"] == "预算已删除"
# 验证已删除
get_resp = client.get(f"{self.BASE}/plans", headers=auth_header(token))
ids = [p["id"] for p in get_resp.json()["data"]]
assert plan_id not in ids
class TestBudgetAutoDecompose:
"""预算自动分解测试"""
BASE = "/api/cma/budget"
def test_auto_decompose_single_kpi(self, client: TestClient, db: Session):
"""单KPI自动分解年度预算为月度(均分)"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="BUDGET_DECOMPOSE")
resp = client.post(
f"{self.BASE}/auto-decompose",
headers=auth_header(token),
json={"kpi_id": kpi.id, "annual_budget": 120000, "year": 2026, "method": "equal"},
)
assert resp.status_code == 200
data = resp.json()
assert "已分解" in data["message"]
assert len(data["monthly_budgets"]) == 12
# 年度预算12000012个月均分,每月10000
assert data["monthly_budgets"][0]["value"] == 10000.0
def test_auto_decompose_missing(self, client: TestClient, db: Session):
"""没有年度预算数据时尝试分解 → 400"""
create_test_user(db)
token = get_token_for_user(client)
create_test_kpi(db, kpi_code="BUDGET_NO_DATA")
resp = client.post(
f"{self.BASE}/auto-decompose",
headers=auth_header(token),
json={"year": 2026, "method": "equal"},
)
assert resp.status_code == 400
class TestBudgetVersions:
"""预算版本管理测试"""
BASE = "/api/cma/budget"
def test_create_and_submit_version(self, client: TestClient, db: Session):
"""创建预算后查询版本并提交(端点已移除,预期404)"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="BUDGET_VER_KPI")
# 创建一条预算
client.post(
f"{self.BASE}/plans",
headers=auth_header(token),
json={"kpi_id": kpi.id, "period": "2026-06", "budget_value": 10000.0},
)
# 版本端点已移除
ver_resp = client.get(f"{self.BASE}/versions", headers=auth_header(token))
assert ver_resp.status_code == 404, "versions端点已移除"
def test_approve_version(self, client: TestClient, db: Session):
"""审批通过版本(端点已移除,预期404"""
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(
f"{self.BASE}/versions/approve",
headers=auth_header(token),
json={"version": "v1.0", "action": "approved"},
)
assert resp.status_code == 404, "versions/approve端点已移除"
def test_reject_version(self, client: TestClient, db: Session):
"""驳回版本(端点已移除,预期404"""
create_test_user(db)
token = get_token_for_user(client)
resp = client.post(
f"{self.BASE}/versions/approve",
headers=auth_header(token),
json={"version": "v2.0", "action": "rejected"},
)
assert resp.status_code == 404, "versions/approve端点已移除"