"""#1660 成品视频 API 查重字段透传测试。 覆盖两套响应构造路径: - routes/videos.py::_to_video_response -> VideoItemResponse (/videos 列表) - routes/generation_tasks.py::_to_generated_video_response -> GeneratedVideoResponse """ from types import SimpleNamespace from unittest.mock import MagicMock from app.api.routes.generation_tasks import _to_generated_video_response from app.api.routes.videos import _to_video_response from app.schemas.generated_video import GeneratedVideoResponse from app.schemas.video_center import VideoItemResponse def _make_item(**overrides): base = dict( id="v1", project_id="p1", generation_task_id="t1", name="成片", file_url="oss://bucket/v1.mp4", file_size=1024, duration=12.5, thumbnail_url=None, width=1080, height=1920, fps=30.0, status="completed", review_status="pending_review", generation_params={}, generated_at=None, duplicate_rate=None, match_count=None, visual_similarity=None, ) base.update(overrides) return SimpleNamespace(**base) class TestVideoItemResponseDupFields: def test_passes_through_all_three_fields(self): item = _make_item(duplicate_rate=42.5, match_count=7, visual_similarity=0.83) resp = _to_video_response(item, storage=None) assert isinstance(resp, VideoItemResponse) assert resp.duplicate_rate == 42.5 assert resp.match_count == 7 assert resp.visual_similarity == 0.83 def test_legacy_video_without_fields_returns_none(self): """老数据/实体无查重字段时保持 None(前端自动隐藏),不报错。""" item = SimpleNamespace( id="v2", project_id="p1", generation_task_id="t2", name="老视频", file_url="oss://bucket/v2.mp4", file_size=1, duration=1.0, thumbnail_url=None, width=720, height=1280, fps=24.0, status="completed", review_status="pending_review", generation_params={}, ) resp = _to_video_response(item, storage=None) assert resp.duplicate_rate is None assert resp.match_count is None assert resp.visual_similarity is None def test_explicit_none_values_kept(self): item = _make_item() resp = _to_video_response(item, storage=None) assert resp.duplicate_rate is None assert resp.match_count is None assert resp.visual_similarity is None def test_zero_match_count_is_valid_value(self): """计算后确无匹配:match_count=0 / visual_similarity=0.0 是合法值,不能变 None。""" item = _make_item(duplicate_rate=0.0, match_count=0, visual_similarity=0.0) resp = _to_video_response(item, storage=None) assert resp.match_count == 0 assert resp.visual_similarity == 0.0 class TestGeneratedVideoResponseDupFields: def test_passes_through_all_three_fields(self): item = _make_item(duplicate_rate=15.2, match_count=3, visual_similarity=0.61) resp = _to_generated_video_response(item, download_url="https://dl/x") assert isinstance(resp, GeneratedVideoResponse) assert resp.duplicate_rate == 15.2 assert resp.match_count == 3 assert resp.visual_similarity == 0.61 assert resp.download_url == "https://dl/x" def test_missing_fields_default_none(self): item = SimpleNamespace( id="v3", project_id="p1", generation_task_id="t3", name="x", file_url="oss://x", file_size=1, duration=1.0, thumbnail_url=None, width=720, height=1280, fps=24.0, ) resp = _to_generated_video_response(item) assert resp.duplicate_rate is None assert resp.match_count is None assert resp.visual_similarity is None def test_storage_failure_falls_back_to_file_url(self): storage = MagicMock() storage.get_download_url.side_effect = RuntimeError("oss down") item = _make_item() resp = _to_video_response(item, storage=storage) assert resp.download_url == item.file_url