fix: auto-verify验证引擎4项缺陷修复 verify-fix (OKR幂等防重累加/entity_id多租户回填/period期间过滤/status done->completed)

This commit is contained in:
Hermes CI Fix
2026-08-30 19:16:19 +08:00
parent 2da4cfc42d
commit 6910d90288
4 changed files with 340 additions and 12 deletions
+196
View File
@@ -0,0 +1,196 @@
"""auto-verify 验证引擎修复测试 (2026-08-30 P1)
覆盖 verify.py 4 项缺陷修复:
1. OKR progress 防重复累加(同一 plan 重复 verify 不再 +15%
2. KPIValue 回填 entity_id 多租户隔离
3. 取 KPI 最新值按 period <= 当前月 过滤(跨月验证不取未来/历史期间)
4. status "done""completed"(枚举外值修正)
"""
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app.models import ActionPlan, KPIValue, Objective
from tests.conftest import create_test_user, get_token_for_user, auth_header, create_test_kpi
BASE = "/api/cma/verify"
def _seed_objective(db: Session, progress: int = 10) -> Objective:
obj = Objective(
entity_id=1,
title="测试目标",
quarter="2026Q3",
owner="任富海",
progress=progress,
)
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def _seed_plan(db: Session, kpi_id: int, objective_id: int, rule: dict, status: str = "pending") -> ActionPlan:
plan = ActionPlan(
kpi_id=kpi_id,
objective_id=objective_id,
title="测试行动计划",
status=status,
priority="high",
auto_verify_rule=rule,
)
db.add(plan)
db.commit()
db.refresh(plan)
return plan
def _make_rule(kpi_code: str) -> dict:
return {
"kpi_code": kpi_code,
"condition": "LESS_THAN", # actual < target → passed
"target_value": 80,
"notify": False, # 测试不发企微
}
class TestOkrIdempotent:
def test_repeat_verify_does_not_accumulate(self, client: TestClient, db: Session):
"""缺陷1修复:同一 plan 重复 verifyOKR progress 只累加一次"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="IDEM_001", kpi_name="幂等KPI")
obj = _seed_objective(db, progress=10)
plan = _seed_plan(db, kpi.id, obj.id, _make_rule("IDEM_001"))
# 第一次验证通过 → +15%
r1 = client.post(f"{BASE}/{plan.id}", headers=auth_header(token), json={"actual_value": 50})
assert r1.status_code == 200
assert r1.json()["passed"] is True
assert r1.json()["okr_progress"]["updated"] is True
assert r1.json()["okr_progress"]["after"] == 25 # 10 + 15
# 第二次验证通过 → 不再累加(保持 25)
r2 = client.post(f"{BASE}/{plan.id}", headers=auth_header(token), json={"actual_value": 40})
assert r2.status_code == 200
assert r2.json()["okr_progress"]["updated"] is False
assert r2.json()["okr_progress"]["reason"] == "already_verified"
assert r2.json()["okr_progress"]["after"] == 25 # 保持原值
db.expire_all()
assert db.query(Objective).filter(Objective.id == obj.id).first().progress == 25
def test_force_recalc_accumulates(self, client: TestClient, db: Session):
"""缺陷1修复:force_recalc=True 保留强制重新累加入口"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="IDEM_002", kpi_name="幂等KPI2")
obj = _seed_objective(db, progress=10)
plan = _seed_plan(db, kpi.id, obj.id, _make_rule("IDEM_002"))
client.post(f"{BASE}/{plan.id}", headers=auth_header(token), json={"actual_value": 50})
r2 = client.post(f"{BASE}/{plan.id}", headers=auth_header(token),
json={"actual_value": 40, "force_recalc": True})
assert r2.json()["okr_progress"]["updated"] is True
assert r2.json()["okr_progress"]["after"] == 40 # 25 + 15
class TestEntityBackfill:
def test_kpi_value_gets_entity_id(self, client: TestClient, db: Session):
"""缺陷2修复:回填的 KPIValue 带 entity_id(与 KPI 定义一致,非默认1)"""
create_test_user(db)
token = get_token_for_user(client)
# entity_id=2 的 KPI(模拟第二个账套)
kpi = create_test_kpi(db, kpi_code="ENT_001", kpi_name="多租户KPI", entity_id=2)
obj = _seed_objective(db)
plan = _seed_plan(db, kpi.id, obj.id, _make_rule("ENT_001"))
r = client.post(f"{BASE}/{plan.id}", headers=auth_header(token), json={"actual_value": 60})
assert r.status_code == 200
val = db.query(KPIValue).filter(KPIValue.source_batch == f"verify-plan-{plan.id}").first()
assert val is not None
assert val.entity_id == 2 # 从 KPI 定义继承,而非默认 1
assert val.kpi_id == kpi.id
class TestPeriodFilter:
def test_latest_value_respects_period_limit(self, client: TestClient, db: Session):
"""缺陷3修复:缺省 actual 时只取 period <= 当前月的值,不取未来期间"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="PER_001", kpi_name="期间KPI")
# 未来月(2026-09)有值 200 → GREATER_THAN 100 会通过;但当前月前(2026-07)值为 50 → 应取到 50
from datetime import datetime
db.add_all([
KPIValue(kpi_id=kpi.id, entity_id=1, period="2026-07", actual_value=50,
source_type="manual", data_status="verified"),
KPIValue(kpi_id=kpi.id, entity_id=1, period="2099-12", actual_value=200,
source_type="manual", data_status="verified"),
])
db.commit()
obj = _seed_objective(db)
plan = _seed_plan(db, kpi.id, obj.id, {
"kpi_code": "PER_001",
"condition": "GREATER_THAN", # actual > target
"target_value": 100,
"notify": False,
})
# 缺省 actual → 应取 2026-07 的 50 → 不通过(若错误取到 2099-12 的 200 则会通过)
r = client.post(f"{BASE}/{plan.id}", headers=auth_header(token), json={})
assert r.status_code == 200
assert r.json()["passed"] is False
assert r.json()["kpi_current_after"] == 50
def test_explicit_period_override(self, client: TestClient, db: Session):
"""缺陷3修复:调用方显式传 period 覆盖默认当前月"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="PER_002", kpi_name="期间KPI2")
db.add_all([
KPIValue(kpi_id=kpi.id, entity_id=1, period="2026-06", actual_value=30,
source_type="manual", data_status="verified"),
KPIValue(kpi_id=kpi.id, entity_id=1, period="2026-07", actual_value=90,
source_type="manual", data_status="verified"),
])
db.commit()
obj = _seed_objective(db)
plan = _seed_plan(db, kpi.id, obj.id, {
"kpi_code": "PER_002",
"condition": "GREATER_THAN",
"target_value": 50,
"notify": False,
})
# 显式 period=2026-06 → 取 30 → 不通过
r1 = client.post(f"{BASE}/{plan.id}", headers=auth_header(token), json={"period": "2026-06"})
assert r1.json()["kpi_current_after"] == 30
assert r1.json()["passed"] is False
# 显式 period=2026-07 → 取 90 → 通过
r2 = client.post(f"{BASE}/{plan.id}", headers=auth_header(token), json={"period": "2026-07"})
assert r2.json()["kpi_current_after"] == 90
assert r2.json()["passed"] is True
class TestStatusEnum:
def test_passed_plan_status_is_completed(self, client: TestClient, db: Session):
"""缺陷4修复:验证通过后 plan.status 写入枚举内值 completed,而非 done"""
create_test_user(db)
token = get_token_for_user(client)
kpi = create_test_kpi(db, kpi_code="ST_001", kpi_name="状态KPI")
obj = _seed_objective(db)
plan = _seed_plan(db, kpi.id, obj.id, _make_rule("ST_001"))
r = client.post(f"{BASE}/{plan.id}", headers=auth_header(token), json={"actual_value": 50})
assert r.status_code == 200
assert r.json()["passed"] is True
db.expire_all()
refreshed = db.query(ActionPlan).filter(ActionPlan.id == plan.id).first()
assert refreshed.status == "completed"
assert refreshed.status != "done"
assert refreshed.progress == 100