"""测试 bzzoiro 事件规范化:基于 fixtures/bzzoiro_event.json 的真实字段映射。 运行(需先 pip-sync requirements-dev.txt): pytest tests/test_bzzoiro_normalize.py -v 若真实 bzzoiro 字段名与样例不同,断言会失败 —— 这正是本测试的目的: 锁定 normalize_bzzoiro 所依赖的字段名,避免上游静默变更导致数据丢失。 """ from __future__ import annotations import json from pathlib import Path import pytest from src.data.config import LEAGUE_NAMES from src.data.normalize import normalize_bzzoiro FIXTURE_DIR = Path(__file__).resolve().parent / "fixtures" def load_event() -> dict: with open(FIXTURE_DIR / "bzzoiro_event.json", encoding="utf-8") as f: return json.load(f) class TestBzzoiroNormalize: """验证 normalize_bzzoiro 对 fixture 样例的解析结果。""" def test_basic_fields(self): """基础字段:日期、状态、对阵、进球。""" m = normalize_bzzoiro(load_event(), "E0") assert m is not None assert m.home_team == "Home United FC" assert m.away_team == "Away City FC" assert m.match_status == "finished" assert m.home_goals == 2 assert m.away_goals == 1 assert m.home_ht_goals == 1 assert m.away_ht_goals == 0 def test_shots_mapping(self): """射门数映射到 home_shots / away_shots。""" m = normalize_bzzoiro(load_event(), "E0") assert m.home_shots == 14 assert m.away_shots == 8 def test_shots_on_target_mapping(self): """射正数映射到 home_shots_on_target / away_shots_on_target。""" m = normalize_bzzoiro(load_event(), "E0") assert m.home_shots_on_target == 5 assert m.away_shots_on_target == 3 def test_corners_mapping(self): """角球映射。""" m = normalize_bzzoiro(load_event(), "E0") assert m.home_corners == 6 assert m.away_corners == 4 def test_possession_mapping(self): """控球率:API 提供 home 值。""" m = normalize_bzzoiro(load_event(), "E0") assert m.home_possession == 58.5 def test_xg_mapping(self): """xG 映射到 home_xg / away_xg。""" m = normalize_bzzoiro(load_event(), "E0") assert m.home_xg == 1.85 assert m.away_xg == 0.92 def test_cards_mapping(self): """黄牌、红牌映射。""" m = normalize_bzzoiro(load_event(), "E0") assert m.home_yellow_cards == 2 assert m.away_yellow_cards == 3 assert m.home_red_cards == 0 assert m.away_red_cards == 0 def test_fallback_aliases(self): """回退别名:shots_home → home_shots, xg_home → home_xg。""" raw = { "event_date": "2026-09-12T19:00:00+00:00", "status": "finished", "home_team": "FC Alpha", "away_team": "FC Beta", "home_goals": 1, "away_goals": 1, "shots_home": 10, "shots_away": 5, "sot_home": 4, "sot_away": 2, "corners_home": 3, "corners_away": 2, "possession": 55.0, "xg_home": 1.2, "xg_away": 0.8, "yellow_cards_home": 1, "yellow_cards_away": 2, "red_cards_home": 0, "red_cards_away": 1, } m = normalize_bzzoiro(raw, "SP1") assert m is not None assert m.home_shots == 10 assert m.away_shots == 5 assert m.home_shots_on_target == 4 assert m.away_shots_on_target == 2 assert m.home_corners == 3 assert m.away_corners == 2 assert m.home_possession == 55.0 assert m.home_xg == 1.2 assert m.away_xg == 0.8 assert m.home_yellow_cards == 1 assert m.away_yellow_cards == 2 assert m.home_red_cards == 0 assert m.away_red_cards == 1 def test_missing_stats_still_normalizes(self): """缺少统计字段时仍应解析基础数据,统计字段为 None(不伪造)。""" raw = { "event_date": "2026-09-12T19:00:00+00:00", "status": "finished", "home_team": "FC One", "away_team": "FC Two", "home_goals": 3, "away_goals": 0, } m = normalize_bzzoiro(raw, "D1") assert m is not None assert m.home_goals == 3 assert m.away_goals == 0 # 无数据字段保持 None,不伪造 assert m.home_shots is None assert m.home_xg is None assert m.home_possession is None def test_unknown_status_drops(self): """未知状态 → 丢弃(None)。""" raw = { "event_date": "2026-09-12T19:00:00+00:00", "status": "weird_status", "home_team": "A", "away_team": "B", } assert normalize_bzzoiro(raw, "E0") is None def test_invalid_date_drops(self): """无效日期 → 丢弃(None)。""" raw = { "event_date": "not-a-date", "status": "finished", "home_team": "A", "away_team": "B", "home_goals": 1, "away_goals": 0, } assert normalize_bzzoiro(raw, "E0") is None def test_same_team_drops(self): """主客队同名(规范化后) → 丢弃(None)。""" raw = { "event_date": "2026-09-12T19:00:00+00:00", "status": "finished", "home_team": "Same FC", "away_team": "Same FC", "home_goals": 1, "away_goals": 0, } assert normalize_bzzoiro(raw, "E0") is None # ── 真实响应校验(占位,待替换后取消 skip) ───────────────────────── @pytest.mark.skip(reason="待提供真实 bzzoiro event 响应后替换 fixture 并取消 skip") def test_real_response_matches_fixture_structure(): """真实响应应能被 fixture 结构覆盖(字段名一致)。""" # 真实响应粘贴于此,验证 normalize_bzzoiro 解析成功 real_response = {} if not real_response: pytest.skip("未提供真实响应") m = normalize_bzzoiro(real_response, "E0") assert m is not None