api_test.py 9.29 KB
"""
api_test.py - 对 api.py 中 FootballReplayMatch.replay_match_event 的单元测试

使用伪数据覆盖主要分支:
1. 时间阈值内直接匹配成功
2. 时间匹配失败 + LLM 判定不是进球
3. 时间匹配失败 + LLM 判定是进球 + LLM 匹配成功
4. 时间匹配失败 + LLM 判定是进球 + LLM 匹配失败
5. 无进球事件 / events 为空
6. 边界:缺少 replay 键时抛出异常
"""
import time
import unittest
from types import SimpleNamespace
from unittest.mock import MagicMock, patch

try:
    from api import FootballReplayMatch
except ImportError:
    from .api import FootballReplayMatch


def _fake_settings(match_by_time_threshold: int = 30):
    """构造一个 settings 伪对象,兼容 api.py 中的 value_or_default 读取逻辑。"""
    return SimpleNamespace(
        match_by_time_threshold=match_by_time_threshold,
        llm=SimpleNamespace(base_url="http://192.168.1.59:11434/v1", model_name="Qwen3.6-35B-A3B-UD-Q8_K_XL.gguf", temperature=0.7),
        common=SimpleNamespace(cache_dir="/root/lzw/tmp_0526_replay_cache0526/videos_cache", task_log_dir="/root/lzw/tmp_0526_replay_cache0526/tasks_log"),
        save_frames_enable=True,
    )


def _base_data():
    """返回一份基础伪数据,replay.start_utc = 1000(毫秒)。"""
    
    # url = "http://video.mam.miguvideo.com/mnt6/fastclip3/wsc/2026/04/29/df760b8d15394cbc9ed0d0a4a9c4b786_1080PS/29133605/vodtmp/3b3e99cf4ca84c3782503d8817242de2.m3u8"
    return {
        "id": "task_001",
        "match_id": "match_001",
        "replay": {
            "id": "replay_001",
            "url": "/root/lzw/finished/69dd5845dd0412067b8d5587-auto-1776074760653/replays/videos/00_14_32_080-00_15_10_560.mp4",
            "start_utc": 5_000,
            "end_utc": 21_000
        },
        "events": [
            {"id": "event_001", 
             "type": "1", 
             "url": "/root/lzw/finished/69dd5845dd0412067b8d5587-auto-1776074760653/live/videos/00-14-09-052.mp4", 
             "event_utc": None
             }, 
            {"id": "event_002", 
             "type": "1", 
             "url": "/root/lzw/finished/69dd5845dd0412067b8d5587-auto-1776074760653/live/videos/00-38-40-052.mp4", 
             "event_utc": None
             },  
            {"id": "event_003", 
             "type": "1", 
             "url": "/root/lzw/finished/69dd5845dd0412067b8d5587-auto-1776074760653/live/videos/01-36-04-972.mp4", 
             "event_utc": None
             }
        ],
    }


class TestReplayMatchEvent(unittest.TestCase):

    @patch("utils.football_replay_match_live.FootballReplayMatchLive")
    @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")
    def test_match_by_time_success(self, mock_video_cls, mock_live_cls):
        """场景1:时间差在阈值内,直接通过 match_by_time 命中。"""
        settings = _fake_settings(match_by_time_threshold=10)  # threshold = 30 * 1000 = 30000 ms
        frm = FootballReplayMatch(settings)

        data = _base_data()
        
        start_time = time.time()
        result = frm.replay_match_event(data)
        end_time = time.time()
        print(f"cost time:{end_time - start_time}")
        
        print(result)
        # exit()

    #     self.assertEqual(result["id"], "task_001")
    #     self.assertEqual(result["match_id"], "match_001")
    #     self.assertEqual(result["replay_id"], "replay_001")
    #     # event_001 与 replay.start_utc 差值 20 ms < 30000 ms,应命中 event_001 的 id
    #     self.assertEqual(result["event_id"], "event_001")
    #     # 未调用 LLM 相关方法
    #     mock_video_cls.return_value.video_event.assert_not_called()
    #     mock_live_cls.return_value.match_batch.assert_not_called()

    # @patch("utils.football_replay_match_live.FootballReplayMatchLive")
    # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")
    # def test_match_by_time_fail_then_not_goal(self, mock_video_cls, mock_live_cls):
    #     """场景2:时间匹配失败,det_goal_replay 判定不是进球。"""
    #     mock_video_instance = MagicMock()
    #     mock_video_instance.video_event.return_value = {"event_name": "无进球", "description": "宣传片"}
    #     mock_video_cls.return_value = mock_video_instance

    #     settings = _fake_settings(match_by_time_threshold=30)
    #     frm = FootballReplayMatch(settings)

    #     data = _base_data()
    #     result = frm.replay_match_event(data)

    #     self.assertEqual(result["event_id"], None)
    #     mock_video_instance.video_event.assert_called_once()
    #     mock_live_cls.return_value.match_batch.assert_not_called()

    # @patch("utils.football_replay_match_live.FootballReplayMatchLive")
    # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")
    # def test_match_by_time_fail_then_goal_then_llm_match(self, mock_video_cls, mock_live_cls):
    #     """场景3:时间匹配失败 -> 判定是进球 -> LLM 匹配成功。"""
    #     mock_video_instance = MagicMock()
    #     mock_video_instance.video_event.return_value = {"event_name": "进球", "description": "世界波"}
    #     mock_video_cls.return_value = mock_video_instance

    #     mock_live_instance = MagicMock()
    #     mock_live_instance.match_batch.return_value = {"video_id": "event_003", "video_path": "http://example.com/event3.mp4", "asr_text": ""}
    #     mock_live_cls.return_value = mock_live_instance

    #     settings = _fake_settings(match_by_time_threshold=30)
    #     frm = FootballReplayMatch(settings)

    #     data = _base_data()
    #     result = frm.replay_match_event(data)

    #     self.assertEqual(result["event_id"], "event_003")
    #     mock_video_instance.video_event.assert_called_once()
    #     mock_live_instance.match_batch.assert_called_once()

    # @patch("utils.football_replay_match_live.FootballReplayMatchLive")
    # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")
    # def test_match_by_time_fail_then_goal_then_llm_no_match(self, mock_video_cls, mock_live_cls):
    #     """场景4:时间匹配失败 -> 判定是进球 -> LLM 未匹配到任何事件。"""
    #     mock_video_instance = MagicMock()
    #     mock_video_instance.video_event.return_value = {"event_name": "进球", "description": "头球破门"}
    #     mock_video_cls.return_value = mock_video_instance

    #     mock_live_instance = MagicMock()
    #     mock_live_instance.match_batch.return_value = None
    #     mock_live_cls.return_value = mock_live_instance

    #     settings = _fake_settings(match_by_time_threshold=30)
    #     frm = FootballReplayMatch(settings)

    #     data = _base_data()
    #     result = frm.replay_match_event(data)

    #     self.assertEqual(result["event_id"], None)
    #     mock_video_instance.video_event.assert_called_once()
    #     mock_live_instance.match_batch.assert_called_once()

    # @patch("utils.football_replay_match_live.FootballReplayMatchLive")
    # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")
    # def test_no_goal_events(self, mock_video_cls, mock_live_cls):
    #     """场景5:events 中没有任何 type='1' 的进球事件。"""
    #     mock_video_instance = MagicMock()
    #     mock_video_instance.video_event.return_value = {"event_name": "无进球", "description": "无候选事件"}
    #     mock_video_cls.return_value = mock_video_instance

    #     settings = _fake_settings(match_by_time_threshold=30)
    #     frm = FootballReplayMatch(settings)

    #     data = _base_data()
    #     data["events"] = [
    #         {"id": "event_005", "type": "2", "url": "http://example.com/event5.mp4", "event_utc": 1020},
    #     ]
    #     result = frm.replay_match_event(data)

    #     self.assertEqual(result["event_id"], None)
    #     mock_video_instance.video_event.assert_called_once()
    #     mock_live_cls.return_value.match_batch.assert_not_called()

    # @patch("utils.football_replay_match_live.FootballReplayMatchLive")
    # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")
    # def test_empty_events(self, mock_video_cls, mock_live_cls):
    #     """场景6:events 为空列表。"""
    #     mock_video_instance = MagicMock()
    #     mock_video_instance.video_event.return_value = {"event_name": "无进球", "description": "空列表"}
    #     mock_video_cls.return_value = mock_video_instance

    #     settings = _fake_settings(match_by_time_threshold=30)
    #     frm = FootballReplayMatch(settings)

    #     data = _base_data()
    #     data["events"] = []
    #     result = frm.replay_match_event(data)

    #     self.assertEqual(result["event_id"], None)
    #     mock_video_instance.video_event.assert_called_once()
    #     mock_live_cls.return_value.match_batch.assert_not_called()

    # @patch("utils.football_replay_match_live.FootballReplayMatchLive")
    # @patch("utils.football_replay_video_event_by_llm.FootballReplayVideoEvent")
    # def test_missing_replay_key(self, mock_video_cls, mock_live_cls):
    #     """场景7:data 缺少 replay 键,应抛出 KeyError。"""
    #     settings = _fake_settings(match_by_time_threshold=30)
    #     frm = FootballReplayMatch(settings)

    #     data = _base_data()
    #     del data["replay"]

    #     with self.assertRaises(KeyError):
    #         frm.replay_match_event(data)


if __name__ == "__main__":
    unittest.main()