feat: P0/P1/P2全部功能 — 四层泳道/视角切换/KPI看板/预警/差异反打/预算/知识面板/回顾会/情景预测/Excel导入/角色权限
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
"""
|
||||
预警闭环模块测试
|
||||
"""
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import datetime, timedelta
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header, create_test_kpi
|
||||
from app.models import KPIAlert, ActionPlan
|
||||
|
||||
|
||||
def create_test_alert(db: Session, **kwargs) -> KPIAlert:
|
||||
"""创建测试预警"""
|
||||
defaults = {
|
||||
"kpi_id": 1,
|
||||
"alert_level": "yellow",
|
||||
"alert_message": "测试预警消息",
|
||||
"status": "pending",
|
||||
"assignee": None,
|
||||
"created_at": datetime.now(),
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
alert = KPIAlert(**defaults)
|
||||
db.add(alert)
|
||||
db.commit()
|
||||
db.refresh(alert)
|
||||
return alert
|
||||
|
||||
|
||||
class TestAlerts:
|
||||
"""预警闭环测试"""
|
||||
|
||||
def test_list_alerts_empty(self, client: TestClient, db: Session):
|
||||
"""空预警列表"""
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
|
||||
resp = client.get("/api/cma/alerts", headers=auth_header(token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 0
|
||||
assert data["data"] == []
|
||||
|
||||
def test_list_alerts_with_data(self, client: TestClient, db: Session):
|
||||
"""有预警数据时列表正常"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id, alert_message="营收下滑预警")
|
||||
|
||||
resp = client.get("/api/cma/alerts", headers=auth_header(token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 1
|
||||
assert data["data"][0]["alert_message"] == "营收下滑预警"
|
||||
|
||||
def test_list_alerts_filter_by_status(self, client: TestClient, db: Session):
|
||||
"""按状态过滤预警"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
create_test_alert(db, kpi_id=kpi.id, alert_message="黄色预警", status="pending")
|
||||
create_test_alert(db, kpi_id=kpi.id, alert_message="处理中", status="processing")
|
||||
create_test_alert(db, kpi_id=kpi.id, alert_message="已解决", status="resolved")
|
||||
|
||||
resp = client.get("/api/cma/alerts?status=pending", headers=auth_header(token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total"] == 1
|
||||
assert data["data"][0]["status"] == "pending"
|
||||
|
||||
def test_process_alert_success(self, client: TestClient, db: Session):
|
||||
"""标记预警为处理中"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id)
|
||||
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/process",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "张三"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["message"] == "已标记为处理中"
|
||||
assert resp.json()["alert"]["status"] == "processing"
|
||||
assert resp.json()["alert"]["assignee"] == "张三"
|
||||
|
||||
def test_process_alert_missing_assignee(self, client: TestClient, db: Session):
|
||||
"""缺少处理人参数"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id)
|
||||
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/process",
|
||||
headers=auth_header(token),
|
||||
json={},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert "缺少" in resp.json()["detail"]
|
||||
|
||||
def test_process_alert_not_found(self, client: TestClient, db: Session):
|
||||
"""处理不存在的预警"""
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
|
||||
resp = client.post(
|
||||
"/api/cma/alerts/9999/process",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "张三"},
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_process_resolved_alert_fails(self, client: TestClient, db: Session):
|
||||
"""已处理的预警不能重复处理"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id, status="resolved")
|
||||
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/process",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "张三"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert "已处理" in resp.json()["detail"]
|
||||
|
||||
def test_resolve_alert(self, client: TestClient, db: Session):
|
||||
"""解决预警"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id)
|
||||
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/resolve",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "李四", "resolution": "已采取措施改善"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["message"] == "已处理"
|
||||
assert data["alert"]["status"] == "resolved"
|
||||
assert data["suggest_create_action_plan"] is True
|
||||
|
||||
def test_resolve_alert_not_found(self, client: TestClient, db: Session):
|
||||
"""解决不存在的预警"""
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
|
||||
resp = client.post(
|
||||
"/api/cma/alerts/9999/resolve",
|
||||
headers=auth_header(token),
|
||||
json={},
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_escalate_yellow_to_red(self, client: TestClient, db: Session):
|
||||
"""升级黄色预警为红色"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id, alert_level="yellow")
|
||||
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/escalate",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "上级主管"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["message"] == "已升级"
|
||||
assert resp.json()["alert"]["alert_level"] == "red"
|
||||
|
||||
def test_escalate_red_stays_red(self, client: TestClient, db: Session):
|
||||
"""已经是红色的预警保持红色"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id, alert_level="red")
|
||||
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/escalate",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "上级主管"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["alert"]["alert_level"] == "red"
|
||||
|
||||
def test_escalate_missing_assignee(self, client: TestClient, db: Session):
|
||||
"""升级预警缺少处理人"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id)
|
||||
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/escalate",
|
||||
headers=auth_header(token),
|
||||
json={},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
def test_check_timeout(self, client: TestClient, db: Session):
|
||||
"""超时预警检测"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
# 创建超过24小时且仍为pending的预警
|
||||
old_time = datetime.now() - timedelta(hours=48)
|
||||
create_test_alert(db, kpi_id=kpi.id, created_at=old_time, alert_level="yellow")
|
||||
# 创建一个未超时的
|
||||
create_test_alert(db, kpi_id=kpi.id, alert_level="yellow")
|
||||
|
||||
resp = client.get("/api/cma/alerts/check-timeout", headers=auth_header(token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["total_timeout"] == 1
|
||||
assert data["upgraded_count"] == 1
|
||||
|
||||
def test_create_action_plan_from_alert(self, client: TestClient, db: Session):
|
||||
"""从预警创建改善行动计划"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id, alert_level="red", alert_message="营收严重下滑")
|
||||
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/create-action-plan",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "王五", "created_by": "系统"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["message"] == "改善行动计划已创建"
|
||||
assert data["plan_id"] is not None
|
||||
assert data["priority"] == "high" # red → high
|
||||
|
||||
def test_create_action_plan_duplicate(self, client: TestClient, db: Session):
|
||||
"""重复创建行动计划被拒绝"""
|
||||
user = create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
kpi = create_test_kpi(db)
|
||||
alert = create_test_alert(db, kpi_id=kpi.id, alert_message="测试预警")
|
||||
|
||||
# 第一次创建
|
||||
client.post(
|
||||
f"/api/cma/alerts/{alert.id}/create-action-plan",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "王五"},
|
||||
)
|
||||
|
||||
# 重复创建
|
||||
resp = client.post(
|
||||
f"/api/cma/alerts/{alert.id}/create-action-plan",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "王五"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert "已关联" in resp.json()["detail"]
|
||||
|
||||
def test_create_action_plan_alert_not_found(self, client: TestClient, db: Session):
|
||||
"""基于不存在的预警创建行动计划"""
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client)
|
||||
|
||||
resp = client.post(
|
||||
"/api/cma/alerts/9999/create-action-plan",
|
||||
headers=auth_header(token),
|
||||
json={"assignee": "王五"},
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
Reference in New Issue
Block a user