Files
Profeto/tests/test_h2h_perspective.py
shangfangjian 951faf31df test: 修复 13 个腐化用例,套件恢复全绿且顺序无关
按「测试腐化(a)/ 源码缺陷(b)/ 测试污染(c)」逐项定性,仅改 tests/:

- 外机绝对路径(4): 迁移文件路径改为相对仓库根解析(沿用
  test_regressions._read 约定),断言内容保持不变。
- cutoff 用例(4+1): mock 未生效的真因是 _agent_provider 被换成同步
  lambda,await 抛 TypeError 被生产代码吞掉后 IndexError;改为 async
  mock 并按 run_specialists/load_match_header/_agent_provider 的真实契约
  打补丁。degraded 用例再加 _upsert_prediction 顶层 kwargs(model)采集。
- 陈旧断言(3): agent 键按现契约断言中文映射;h2h mock 改 async;
  Match.stats 按设计为 lazy="select",从 MATCH_RELATIONS 移出并单独
  固化该设计决定。
- P0-3 守卫(1): seg 越界扫到下游 stats 管线导致误报,改为按缩进收口;
  合法形状含经 raw 派生变量中转的写法,并补元测试确保守卫仍能抓到回归。
- 交叉污染(6): test_multi_agent_cutoff 用 patch.object 精确还原,消除
  裸赋值泄漏的同步 mock;现已验证顺序无关。
2026-09-21 17:31:36 +08:00

137 lines
5.5 KiB
Python

"""回归测试: H2H 切片「主队 n 胜」统计视角修复。
验证: 历史交锋汇总必须从「当前主队」视角统计胜/平/负,
而非按「场地主队」统计。
"""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
from src.llm.context_builder import MatchHeader, h2h_slice
def _make_team(tid: int, name: str) -> MagicMock:
t = MagicMock()
t.id = tid
t.name = name
return t
def _make_h2h_match(mid, home_id, away_id, home_goals, away_goals, home_name="H", away_name="A"):
m = MagicMock()
m.id = mid
m.home_team_id = home_id
m.away_team_id = away_id
m.home_goals = home_goals
m.away_goals = away_goals
m.match_date = None
m.home_team = _make_team(home_id, home_name)
m.away_team = _make_team(away_id, away_name)
return m
def _make_header(home_id=1, away_id=2, home_name="利物浦", away_name="阿森纳"):
return MatchHeader(
match_id=999,
home_name=home_name,
away_name=away_name,
league_name="英超",
season="2025-2026",
match_date="2026-01-15 20:00 UTC",
match_dt=None,
stage=None,
home_team_id=home_id,
away_team_id=away_id,
league_id=1,
)
class TestH2HCurrentHomePerspective:
"""H2H 汇总统计必须从当前主队视角出发。"""
@pytest.mark.asyncio
async def test_swapped_home_away_perspective(self):
"""
场景: 当前比赛利物浦(home_id=1) vs 阿森纳(away_id=2)。
历史交锋两场:
1. 利物浦主场 2-0 阿森纳 (home_id=1, away_id=2)
2. 阿森纳主场 3-1 利物浦 (home_id=2, away_id=1)
从利物浦视角: 1胜(2-0) 1负(1-3)。
原bug: 按场地主队统计 → "主队 1胜 0平 1负"(第二场场地主队是阿森纳,赢了),
导致「利物浦横扫」的假象。
"""
header = _make_header(home_id=1, away_id=2, home_name="利物浦", away_name="阿森纳")
matches = [
_make_h2h_match(100, home_id=1, away_id=2, home_goals=2, away_goals=0,
home_name="利物浦", away_name="阿森纳"),
_make_h2h_match(101, home_id=2, away_id=1, home_goals=3, away_goals=1,
home_name="阿森纳", away_name="利物浦"),
]
import src.llm.context_builder as cb
orig = cb._get_h2h
async def mock_get_h2h(db, home_id, away_id, before, *, limit):
return matches
cb._get_h2h = mock_get_h2h
try:
result = await h2h_slice(header, limit=8, before=None)
text = str(result)
print(text)
# 从利物浦视角: 1胜 0平 1负
assert "1胜 0平 1负" in text, f"期望「1胜 0平 1负」,实际:\n{text}"
assert "利物浦" in text, f"应标明当前主队视角:\n{text}"
# 原bug输出: "主队 1胜 0平 1负"(模糊的「主队」,实际是场地主队)
# 修复后: "从当前主队 利物浦 视角: 1胜 0平 1负"
assert "从当前主队" in text, f"应标明「从当前主队」视角:\n{text}"
finally:
cb._get_h2h = orig
@pytest.mark.asyncio
async def test_all_home_wins_from_current_perspective(self):
"""
当前主队所有交锋都是主场且全胜 → 全部计为当前主队胜。
"""
header = _make_header(home_id=1, away_id=2, home_name="曼城", away_name="诺维奇")
matches = [
_make_h2h_match(200, home_id=1, away_id=2, home_goals=3, away_goals=0,
home_name="曼城", away_name="诺维奇"),
_make_h2h_match(201, home_id=1, away_id=2, home_goals=2, away_goals=1,
home_name="曼城", away_name="诺维奇"),
]
import src.llm.context_builder as cb
async def mock_get_h2h(db, h, a, before, **kw):
# 真实契约是 async(见 context_builder.py 的 `h2h = await _get_h2h(...)`),
# 同步 lambda 会抛 TypeError: object list can't be used in 'await' expression。
return matches
with patch.object(cb, "_get_h2h", mock_get_h2h):
result = await h2h_slice(header, limit=8, before=None)
text = str(result)
assert "2胜 0平 0负" in text, f"期望「2胜 0平 0负」,实际:\n{text}"
@pytest.mark.asyncio
async def test_draw_counted_correctly(self):
"""场景: 两场交锋一胜一平,验证平局也被正确计数。"""
header = _make_header(home_id=1, away_id=2, home_name="切尔西", away_name="热刺")
matches = [
_make_h2h_match(300, home_id=1, away_id=2, home_goals=1, away_goals=1,
home_name="切尔西", away_name="热刺"), # 平局
_make_h2h_match(301, home_id=2, away_id=1, home_goals=0, away_goals=2,
home_name="热刺", away_name="切尔西"), # 切尔西客场 2-0 赢
]
import src.llm.context_builder as cb
orig = cb._get_h2h
async def mock_get_h2h(db, h, a, before, *, limit):
return matches
cb._get_h2h = mock_get_h2h
try:
result = await h2h_slice(header, limit=8, before=None)
text = str(result)
# 切尔西视角: 1胜(客场2-0) 1平(主场1-1) 0负
assert "1胜 1平 0负" in text, f"期望「1胜 1平 0负」,实际:\n{text}"
finally:
cb._get_h2h = orig