CMVR-AI-ANALYSIS/tests/test_video.py

138 lines
3.8 KiB
Python
Raw Normal View History

2026-08-13 16:56:32 +08:00
import asyncio
import httpx
from app import video
class _FakeResponse:
def __init__(self, body):
self.body = body
def raise_for_status(self):
return None
def json(self):
return self.body
class _FakeClient:
responses = []
payloads = []
def __init__(self, **_):
pass
async def __aenter__(self):
return self
async def __aexit__(self, *_):
return None
async def post(self, _, json):
self.payloads.append(json)
return _FakeResponse(self.responses.pop(0))
def test_vision_request_retries_empty_content(monkeypatch):
_FakeClient.responses = [
{
"done_reason": "length",
"eval_count": 1024,
"message": {"content": "", "thinking": "reasoning"},
},
{
"done_reason": "stop",
"eval_count": 20,
"message": {"content": '{"passed": true}', "thinking": ""},
},
]
_FakeClient.payloads = []
monkeypatch.setattr(httpx, "AsyncClient", _FakeClient)
payload = {
"think": False,
"messages": [{"role": "user", "content": "Analyze"}],
"options": {"num_predict": 1024},
}
content, metadata = asyncio.run(video._request_vision_model(payload))
assert content == '{"passed": true}'
assert metadata["doneReason"] == "stop"
assert len(_FakeClient.payloads) == 2
assert _FakeClient.payloads[0]["think"] is False
assert _FakeClient.payloads[1]["options"]["num_predict"] == 1536
def test_fast_result_quality_controls_accurate_fallback():
complete = {
"passed": True,
"conclusion": "通过",
"summary": "状态正常",
"events": [],
"warnings": [],
}
assert video._fallback_reason(complete, True) is None
assert video._fallback_reason({**complete, "passed": None}, True) == "FAST_RESULT_INSUFFICIENT"
assert video._fallback_reason({**complete, "passed": None}, False) is None
assert video._fallback_reason({"summary": "缺少字段"}, True).startswith(
"FAST_RESULT_MISSING_FIELDS:"
)
def test_auto_mode_falls_back_to_accurate_result(monkeypatch, tmp_path):
frame_dir = tmp_path / "frames"
frame_dir.mkdir()
frame = frame_dir / "frame-001.jpg"
frame.write_bytes(b"frame")
calls = []
monkeypatch.setattr(
video,
"_extract_frames",
lambda *_: ([frame], 2.0, frame_dir),
)
async def fake_analyze(_, mode, *args):
calls.append(mode)
if mode == "FAST":
return (
{
"passed": None,
"conclusion": "证据不足",
"summary": "快速分析无法判断",
"events": [],
"warnings": [],
},
{"mode": "FAST", "model": "fast", "sampledFrameCount": 1, "maximumWidth": 896},
)
return (
{
"passed": True,
"conclusion": "通过",
"summary": "精确分析确认通过",
"events": [],
"warnings": [],
},
{"mode": "ACCURATE", "model": "accurate", "sampledFrameCount": 1, "maximumWidth": 1280},
)
monkeypatch.setattr(video, "_analyze_frames", fake_analyze)
profile_dir = tmp_path / "profile"
profile_dir.mkdir()
(profile_dir / "prompt.txt").write_text("Analyze", encoding="utf-8")
profile = video.Profile(
"test.video.v1",
"VIDEO_ANALYSIS",
profile_dir,
{"fallbackWhenPassedUnknown": True},
)
result, _, metadata = asyncio.run(video.analyze_video(profile, tmp_path / "video.mp4"))
assert calls == ["FAST", "ACCURATE"]
assert result["passed"] is True
assert metadata["fallback"] is True
assert metadata["fallbackReason"] == "FAST_RESULT_INSUFFICIENT"