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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
169 lines
5.4 KiB
Python
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
|