"""测试 create_video_record_and_dedup 传递 user_id 到查重逻辑. 验证 P0 修复:查重范围从项目级扩大到用户级。 dedup_helpers 必须把 user_id 传给 compute_duplicate_rate。 """ from __future__ import annotations import sys from pathlib import Path from unittest.mock import MagicMock, patch import pytest # Mock cv2/numpy before imports sys.modules.setdefault("cv2", MagicMock()) sys.modules.setdefault("numpy", MagicMock()) ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "apps" / "api")) sys.path.insert(0, str(ROOT / "packages")) sys.path.insert(0, str(ROOT / "apps" / "worker")) import os os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret") os.environ.setdefault("DATABASE_URL", "sqlite:///test.db") class TestDedupHelpersUserIdPassthrough: """验证 dedup_helpers 把 user_id 传递给 compute_duplicate_rate.""" def test_user_id_passed_to_compute_duplicate_rate(self): """create_video_record_and_dedup 必须传 user_id 给 compute_duplicate_rate.""" from video_processing.dedup_helpers import create_video_record_and_dedup session = MagicMock() mock_video_repo = MagicMock() mock_fingerprint = MagicMock() mock_fingerprint.to_dict.return_value = {"md5": "test", "keyframe_phashes": ["aa"]} mock_deduplicator = MagicMock() mock_deduplicator.compute_fingerprint.return_value = mock_fingerprint mock_deduplicator.check_duplicate.return_value = None mock_deduplicator.compute_duplicate_rate.return_value = { "duplicate_rate": 42.5, "visual_similarity": 0.7, "match_count": 2, } with ( patch( "packages.adapters.sqlalchemy_impl.generated_video_repository.SQLAlchemyGeneratedVideoRepository", return_value=mock_video_repo, ), patch("video_processing.dedup.VideoDeduplicator", return_value=mock_deduplicator), ): result = create_video_record_and_dedup( generation_task_id="task-001", project_id="proj-001", user_id="user-abc", batch_id="", file_url="https://example.com/video.mp4", file_size=1024, duration=15.0, video_path="/tmp/fake_video.mp4", mode="smart", session=session, ) # 验证 compute_duplicate_rate 被调用且 user_id 正确传递 mock_deduplicator.compute_duplicate_rate.assert_called_once() call_kwargs = mock_deduplicator.compute_duplicate_rate.call_args assert ( call_kwargs.kwargs.get("user_id") == "user-abc" ), f"user_id 应传递给 compute_duplicate_rate,实际: {call_kwargs}" def test_empty_user_id_still_works(self): """user_id 为空时仍然正常执行(回退到 project 级比较).""" from video_processing.dedup_helpers import create_video_record_and_dedup session = MagicMock() mock_video_repo = MagicMock() mock_fingerprint = MagicMock() mock_fingerprint.to_dict.return_value = {"md5": "test"} mock_deduplicator = MagicMock() mock_deduplicator.compute_fingerprint.return_value = mock_fingerprint mock_deduplicator.check_duplicate.return_value = None mock_deduplicator.compute_duplicate_rate.return_value = { "duplicate_rate": 0.0, "visual_similarity": 0.0, "match_count": 0, } with ( patch( "packages.adapters.sqlalchemy_impl.generated_video_repository.SQLAlchemyGeneratedVideoRepository", return_value=mock_video_repo, ), patch("video_processing.dedup.VideoDeduplicator", return_value=mock_deduplicator), ): result = create_video_record_and_dedup( generation_task_id="task-002", project_id="proj-002", user_id="", batch_id="", file_url="https://example.com/video.mp4", file_size=1024, duration=10.0, video_path="/tmp/fake.mp4", mode="smart", session=session, ) mock_deduplicator.compute_duplicate_rate.assert_called_once() call_kwargs = mock_deduplicator.compute_duplicate_rate.call_args assert call_kwargs.kwargs.get("user_id") == "" def test_duplicate_rate_saved_to_video_record(self): """compute_duplicate_rate 的返回值应写入 generated_video.duplicate_rate.""" from video_processing.dedup_helpers import create_video_record_and_dedup session = MagicMock() mock_video_repo = MagicMock() mock_fingerprint = MagicMock() mock_fingerprint.to_dict.return_value = {"md5": "test"} mock_deduplicator = MagicMock() mock_deduplicator.compute_fingerprint.return_value = mock_fingerprint mock_deduplicator.check_duplicate.return_value = None mock_deduplicator.compute_duplicate_rate.return_value = { "duplicate_rate": 78.5, "visual_similarity": 0.85, "match_count": 3, } with ( patch( "packages.adapters.sqlalchemy_impl.generated_video_repository.SQLAlchemyGeneratedVideoRepository", return_value=mock_video_repo, ), patch("video_processing.dedup.VideoDeduplicator", return_value=mock_deduplicator), ): result = create_video_record_and_dedup( generation_task_id="task-003", project_id="proj-003", user_id="user-xyz", batch_id="", file_url="https://example.com/v.mp4", file_size=2048, duration=20.0, video_path="/tmp/fake2.mp4", mode="smart", session=session, ) # 验证 update 被调用(包含 duplicate_rate 的记录) mock_video_repo.create.assert_called_once() updated_video = mock_video_repo.create.call_args[0][0] assert updated_video.duplicate_rate == 78.5