""" 预警闭环模块测试 """ 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, alert_level="red", alert_message="营收严重下滑") 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