Files
xiaoxia-saas/tests/unit/test_duplication_api_enqueue.py
xiaoxia 0542654ca8
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 4s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 6s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
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 / Build Staging API Image (push) Successful in 17s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m6s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 48s
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 / Integration Tests (push) Successful in 2m0s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 21s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m25s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 2m33s
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 / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 19s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 45s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m58s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m9s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 57s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
AI Code Review / AI Code Review (pull_request) Successful in 6m27s
CI/CD Pipeline / Validate - Security (push) Successful in 6m41s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 6m16s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m48s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 5m39s
CI/CD Pipeline / Unit Tests (push) Successful in 11m34s
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: 查重 worker 无限重试 bug + 补 3 个 API/repository 单测 (#1661 follow-up) (#1680)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-04 11:38:11 +08:00

169 lines
5.4 KiB
Python

"""#1661 查重 API enqueue 及仓储 commit 覆盖测试。
覆盖:
- upload 接口在成功后调用 celery_app.send_task
- retry 接口在成功后调用 celery_app.send_task
- duplication_repository.update() 正确调用 session.commit()
"""
from __future__ import annotations
import os
import sys
from unittest.mock import MagicMock, patch
import pytest
os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret-key-for-testing")
os.environ.setdefault("DATABASE_URL", "sqlite:///test.db")
ROOT = os.path.join(os.path.dirname(__file__), "..", "..")
sys.path.insert(0, os.path.join(ROOT, "apps", "api"))
sys.path.insert(0, os.path.join(ROOT, "packages"))
from app.api.routes.duplication import router
from app.auth import AuthenticatedUser, get_current_user
from app.core.storage import get_storage_service
from app.dependencies import get_duplication_repository
from fastapi import FastAPI
from fastapi.testclient import TestClient
from packages.domain.duplication import DuplicationRecord
from packages.domain.entities import User
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_test_user():
return User(id="user-1", username="testuser", email="test@example.com", display_name="Test User")
def _make_auth_user():
return AuthenticatedUser(user=_make_test_user(), session_id="test-session", token_type="bearer")
def _make_record(status="pending"):
record = DuplicationRecord.create(
user_id="user-1",
filename="test.mp4",
file_size=1024,
storage_key="duplication/abc/test.mp4",
)
if status != "pending":
record.status = status
return record
def _build_client(auth_user, repo, storage=None):
"""构建带 dependency_overrides 的 TestClient。"""
app = FastAPI()
app.include_router(router, prefix="/duplication")
app.dependency_overrides[get_current_user] = lambda: auth_user
app.dependency_overrides[get_duplication_repository] = lambda: repo
if storage is not None:
app.dependency_overrides[get_storage_service] = lambda: storage
return TestClient(app)
# ---------------------------------------------------------------------------
# 1. Upload endpoint enqueues celery task
# ---------------------------------------------------------------------------
def test_upload_enqueue_calls_celery_task():
"""POST /duplication/upload 成功创建记录后必须调用 send_task。"""
record = _make_record()
fake_repo = MagicMock()
fake_repo.create.return_value = record
fake_storage = MagicMock()
fake_auth = _make_auth_user()
client = _build_client(fake_auth, fake_repo, fake_storage)
with patch("app.api.routes.duplication.celery_app") as mock_celery:
response = client.post(
"/duplication/upload",
files={"file": ("test.mp4", b"fake-video-content", "video/mp4")},
)
assert response.status_code == 200, response.text
mock_celery.send_task.assert_called_once_with(
"worker.process_duplication_check",
args=[record.id],
)
# ---------------------------------------------------------------------------
# 2. Retry endpoint enqueues celery task
# ---------------------------------------------------------------------------
def test_retry_enqueue_calls_celery_task():
"""POST /duplication/records/{id}/retry 成功后必须调用 send_task。"""
record = _make_record(status="failed")
fake_repo = MagicMock()
fake_repo.get.return_value = record
# RetryDuplicationUseCase.execute 内部调用 repo.get → record.reset_for_retry → repo.update
updated = _make_record()
updated.id = record.id
updated.status = "pending"
fake_repo.update.return_value = updated
fake_auth = _make_auth_user()
client = _build_client(fake_auth, fake_repo)
with patch("app.api.routes.duplication.celery_app") as mock_celery:
response = client.post(f"/duplication/records/{record.id}/retry")
assert response.status_code == 200, response.text
mock_celery.send_task.assert_called_once_with(
"worker.process_duplication_check",
args=[record.id],
)
# ---------------------------------------------------------------------------
# 3. Repository update calls session.commit()
# ---------------------------------------------------------------------------
def test_repository_update_calls_session_commit():
"""duplication_repository 的 update 方法必须调用 session.commit()。"""
from packages.adapters.sqlalchemy_impl.duplication_repository import (
SQLAlchemyDuplicationRecordRepository,
)
from packages.adapters.sqlalchemy_impl.models import DuplicationRecordModel
mock_session = MagicMock()
mock_model = MagicMock(spec=DuplicationRecordModel)
mock_model.id = "rec-1"
mock_session.query.return_value.filter.return_value.first.return_value = mock_model
repo = SQLAlchemyDuplicationRecordRepository(mock_session)
record = DuplicationRecord.create(
user_id="user-1",
filename="test.mp4",
file_size=1024,
storage_key="duplication/abc/test.mp4",
)
record.status = "completed"
record.duplicate_rate = 42.0
record.duplicate_count = 1
record.visual_similarity = 0.85
record.match_count = 2
result = repo.update(record)
mock_session.commit.assert_called()
assert result.visual_similarity == 0.85
assert result.match_count == 2