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>
168 lines
5.5 KiB
Python
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
|