"""P1-D 回归测试: 全局 LLM 并发限制 + provider 字段已删除。 运行: pytest tests/test_p1_d_concurrency.py -v """ import asyncio from unittest.mock import patch import pytest from src.api.routes import predict as predict_mod class TestGlobalLLMConcurrency: """P1-D: 全局 LLM 并发限制(默认 4)。""" def test_semaphore_exists_with_limit(self): """路由模块必须存在 _GLOBAL_LLM_SEMAPHORE 且 value <= 4。""" assert hasattr(predict_mod, "_GLOBAL_LLM_SEMAPHORE") sem = predict_mod._GLOBAL_LLM_SEMAPHORE assert isinstance(sem, asyncio.Semaphore) assert sem._value == 4, f"期望并发限制 4,实际 {sem._value}" def test_predict_with_concurrency_limits_parallel(self): """P1-D: 并发调用 _predict_with_concurrency 不得超过信号量限制。""" max_concurrent = 0 current = 0 lock = asyncio.Lock() async def fake_predict(match_id, **kwargs): nonlocal current, max_concurrent async with lock: current += 1 max_concurrent = max(max_concurrent, current) await asyncio.sleep(0.05) # 模拟 LLM 调用 async with lock: current -= 1 return type("R", (), {"prediction_id": 1, "provider": "p", "model": "m", "prompt_version": "v1", "pred_home_goals": 1.0, "pred_away_goals": 0.0, "pred_1x2": "1", "subjective_confidence": 0.5, "reasoning": "", "status": "success", "context": "", "latency_ms": 0, "raw": {}})() req = type("Req", (), {"match_id": 1, "model": None, "prompt_version": None, "mode": "single"})() async def run(): with patch.object(predict_mod, "predict_match", fake_predict): # 启动 10 个并发请求 tasks = [predict_mod._predict_with_concurrency(req) for _ in range(10)] await asyncio.gather(*tasks) asyncio.run(run()) # 最大并发不得超过信号量限制(4) assert max_concurrent <= 4, f"并发 {max_concurrent} 超过限制 4" class TestProviderFieldRemoved: """P1-D: PredictRequest 的 provider 字段必须已删除(未接线)。""" def test_predict_request_no_provider(self): from src.api.schemas import PredictRequest fields = set(PredictRequest.model_fields.keys()) assert "provider" not in fields, f"PredictRequest 应已删除 provider 字段,现有: {fields}" def test_predict_request_still_has_core_fields(self): from src.api.schemas import PredictRequest fields = set(PredictRequest.model_fields.keys()) for required in ("match_id", "model", "prompt_version", "mode"): assert required in fields, f"缺少核心字段 {required}"