"""回归测试: 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.slices.h2h 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.slices.h2h 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.slices.h2h 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