Files
xiaoxia-saas/tests/unit/test_recompute_dedup_api.py
T
xiaoxia 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
fix: 查重流程全面核实修复 — 两阶段持久化 + 重新计算查重API (#1664) (#1684)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-04 13:00:11 +08:00

168 lines
5.5 KiB
Python

"""Tests for POST /videos/recompute-dedup endpoint (#1664 follow-up)."""
from unittest.mock import MagicMock, patch
import pytest
from fastapi.testclient import TestClient
@pytest.fixture
def mock_video():
"""Mock video with missing dedup data."""
v = MagicMock()
v.id = "video-001"
v.user_id = "user-abc"
v.duplicate_rate = None
v.video_fingerprint = None
v.project_id = "proj-001"
v.generation_task_id = "task-001"
v.name = "test.mp4"
v.file_url = "https://example.com/test.mp4"
v.file_size = 1024
v.duration = 10.0
v.width = 1920
v.height = 1080
v.fps = 25.0
v.status = "completed"
v.review_status = "pending_review"
v.generation_params = {}
v.thumbnail_url = None
v.is_duplicate = False
v.duplicate_of = None
v.match_count = None
v.visual_similarity = None
v.generated_at = "2026-09-04T00:00:00"
return v
@pytest.fixture
def mock_video_with_dedup(mock_video):
"""Mock video that already has dedup data."""
mock_video.duplicate_rate = 15.5
mock_video.video_fingerprint = {"md5": "abc123"}
return mock_video
class TestRecomputeDedupEndpoint:
"""POST /videos/recompute-dedup"""
def test_enqueue_videos_without_dedup(self, mock_video):
"""Videos missing duplicate_rate should be enqueued."""
from app.api.routes.videos import RecomputeDedupRequest
mock_repo = MagicMock()
mock_repo.list_by_user.return_value = [mock_video]
with (patch("app.api.routes.videos.celery_app") as mock_celery,):
mock_celery.send_task.return_value = MagicMock(id="task-xyz")
from app.api.routes.videos import recompute_dedup
auth_user = MagicMock()
auth_user.user.id = "user-abc"
result = recompute_dedup(
request=RecomputeDedupRequest(),
repo=mock_repo,
current_user=auth_user,
)
assert result.enqueued == 1
assert result.total_scanned == 1
assert result.skipped == 0
mock_celery.send_task.assert_called_once_with("worker.check_duplicate", args=["video-001"])
def test_skip_videos_with_complete_dedup(self, mock_video_with_dedup):
"""Videos with both duplicate_rate and video_fingerprint should be skipped."""
from app.api.routes.videos import RecomputeDedupRequest, recompute_dedup
mock_repo = MagicMock()
mock_repo.list_by_user.return_value = [mock_video_with_dedup]
with patch("app.api.routes.videos.celery_app") as mock_celery:
auth_user = MagicMock()
auth_user.user.id = "user-abc"
result = recompute_dedup(
request=RecomputeDedupRequest(),
repo=mock_repo,
current_user=auth_user,
)
assert result.enqueued == 0
assert result.total_scanned == 1
assert result.skipped == 1
mock_celery.send_task.assert_not_called()
def test_specific_video_ids(self, mock_video):
"""When video_ids are provided, only those videos are processed."""
from app.api.routes.videos import RecomputeDedupRequest, recompute_dedup
mock_repo = MagicMock()
mock_repo.get_by_ids.return_value = [mock_video]
with patch("app.api.routes.videos.celery_app") as mock_celery:
mock_celery.send_task.return_value = MagicMock(id="task-xyz")
auth_user = MagicMock()
auth_user.user.id = "user-abc"
result = recompute_dedup(
request=RecomputeDedupRequest(video_ids=["video-001"]),
repo=mock_repo,
current_user=auth_user,
)
assert result.enqueued == 1
mock_repo.get_by_ids.assert_called_once_with(["video-001"])
def test_security_only_own_videos(self, mock_video):
"""Videos belonging to other users should be filtered out."""
from app.api.routes.videos import RecomputeDedupRequest, recompute_dedup
mock_video.user_id = "user-OTHER"
mock_repo = MagicMock()
mock_repo.get_by_ids.return_value = [mock_video]
with patch("app.api.routes.videos.celery_app") as mock_celery:
auth_user = MagicMock()
auth_user.user.id = "user-abc"
result = recompute_dedup(
request=RecomputeDedupRequest(video_ids=["video-001"]),
repo=mock_repo,
current_user=auth_user,
)
assert result.enqueued == 0
mock_celery.send_task.assert_not_called()
def test_mixed_complete_and_incomplete(self, mock_video, mock_video_with_dedup):
"""Mix of videos with and without dedup data."""
import copy
from app.api.routes.videos import RecomputeDedupRequest, recompute_dedup
# Create a second video object
v2 = MagicMock()
v2.id = "video-002"
v2.user_id = "user-abc"
v2.duplicate_rate = None
v2.video_fingerprint = None
mock_repo = MagicMock()
mock_repo.list_by_user.return_value = [mock_video_with_dedup, v2]
with patch("app.api.routes.videos.celery_app") as mock_celery:
mock_celery.send_task.return_value = MagicMock(id="task-xyz")
auth_user = MagicMock()
auth_user.user.id = "user-abc"
result = recompute_dedup(
request=RecomputeDedupRequest(),
repo=mock_repo,
current_user=auth_user,
)
assert result.enqueued == 1
assert result.total_scanned == 2
assert result.skipped == 1