452a484c5b
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 6s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 18s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 16s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 33s
CI/CD Pipeline / Build Staging API Image (push) Successful in 26s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 40s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 42s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m54s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m7s
AI Code Review / AI Code Review (pull_request) Failing after 2m25s
CI/CD Pipeline / Integration Tests (push) Successful in 2m28s
CI/CD Pipeline / Validate - Style (push) Successful in 2m50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m1s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m1s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m34s
CI/CD Pipeline / Validate - Security (push) Successful in 5m2s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 5m26s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m8s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 3m56s
CI/CD Pipeline / Unit Tests (push) Successful in 8m40s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
165 lines
6.1 KiB
Python
165 lines
6.1 KiB
Python
"""测试 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
|