Files
xiaoxia-saas/tests/unit/test_asset_diagnosis.py
T
CI Bot 1a57878f76
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 1m12s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 2m17s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1m33s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 3m14s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
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
chore(backend): Phase 3 清理 — 未使用依赖删除 + pyflakes 警告清零 + 测试文件冗余清理
1. 未使用依赖清理:
   - 从 requirements-base.txt 移除 cryptography 和 pyOpenSSL

2. pyflakes 警告清零 (apps/ + packages/ + tests/):
   - 移除 17 处未使用的 import (F401)
   - 修复 26 处未使用的局部变量 (F841):
     * 有副作用的赋值转为裸调用
     * 无副作用的赋值直接删除
   - 修复 1 处未使用的异常变量 (F841)
   - 修复 1 处空 except 块

3. 测试文件冗余清理:
   - 删除 tests/integration/test_project_management.py (模块级 skip,测试不存在的模块)
   - 删除 tests/integration/fixtures/duplication_routes_fixed.py (未被引用)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:14:46 +08:00

215 lines
8.4 KiB
Python

import os
import sys
from pathlib import Path
# 设置必要环境变量(必须在导入 app 模块之前)
os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret-key-for-testing")
os.environ.setdefault("DATABASE_URL", "sqlite:///test.db")
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "api"))
from app.api.routes.asset_diagnosis import _build_diagnosis
from fastapi import FastAPI
from fastapi.testclient import TestClient
from packages.domain import (
Asset,
AssetLibrary,
AssetLibraryKind,
AssetStatus,
ClassificationStatus,
Project,
)
def _asset(name: str, mime_type: str, *, status=AssetStatus.READY, duration=None, quality_score=None):
return Asset.create(
project_id="project-1",
library_id="library-1",
name=name,
storage_key=f"uploads/{name}",
mime_type=mime_type,
file_size=1024,
duration=duration,
status=status,
classification_status=ClassificationStatus.COMPLETED,
quality_score=quality_score,
)
def test_asset_diagnosis_reports_missing_video_gap():
diagnosis = _build_diagnosis(
"project-1",
[_asset("voice.mp3", "audio/mpeg"), _asset("image.jpg", "image/jpeg")],
)
assert diagnosis.readiness_score < 80
assert diagnosis.video_assets == 0
assert any(gap.key == "missing_video" and gap.severity == "critical" for gap in diagnosis.gaps)
def test_asset_diagnosis_scores_ready_video_assets():
used_asset = _asset("video-1.mp4", "video/mp4", duration=8)
used_asset.metadata = {"generation_use_count": 1, "review_status": "pending_review"}
diagnosis = _build_diagnosis(
"project-1",
[
used_asset,
_asset("video-2.mp4", "video/mp4", duration=8),
_asset("video-3.mov", "video/quicktime", duration=8),
_asset("voice.mp3", "audio/mpeg"),
],
)
assert diagnosis.readiness_score >= 80
assert diagnosis.video_assets == 3
assert diagnosis.voice_assets == 1
assert diagnosis.estimated_video_count == 3
assert diagnosis.used_assets == 1
assert diagnosis.unused_assets == 3
assert diagnosis.pending_review_assets == 1
smart_view_counts = {item.key: item.count for item in diagnosis.smart_views}
assert smart_view_counts["recommended"] == 3
assert smart_view_counts["used"] == 1
assert smart_view_counts["pending_review"] == 1
def test_asset_diagnosis_flags_unready_and_low_quality_assets():
diagnosis = _build_diagnosis(
"project-1",
[
_asset("video.mp4", "video/mp4", duration=10, quality_score=40),
_asset("pending.mp4", "video/mp4", status=AssetStatus.UPLOADING),
],
)
gap_keys = {gap.key for gap in diagnosis.gaps}
assert "not_ready_assets" in gap_keys
assert "low_quality_assets" in gap_keys
smart_view_counts = {item.key: item.count for item in diagnosis.smart_views}
assert smart_view_counts["needs_attention"] == 2
assert smart_view_counts["high_risk"] == 1
# ---------------------------------------------------------------------------
# 路由层测试 — 验证 find_by_project 调用正确性
# ---------------------------------------------------------------------------
class _StubProjectRepository:
def __init__(self, projects: dict[str, Project] | None = None):
self._projects = projects or {}
def get(self, project_id: str) -> Project | None:
return self._projects.get(project_id)
def find_by_id(self, project_id: str) -> Project | None:
return self._projects.get(project_id)
class _StubAssetLibraryRepository:
def __init__(self, libraries: dict[str, AssetLibrary] | None = None):
self._libraries = libraries or {}
self.find_by_project_called_with: list[str] = []
def find_by_project(self, project_id: str, kind=None) -> list[AssetLibrary]:
self.find_by_project_called_with.append(project_id)
return [lib for lib in self._libraries.values() if lib.project_id == project_id]
def list_by_project(self, project_id: str) -> list[AssetLibrary]:
raise AssertionError("路由不应调用 list_by_project,应调用 find_by_project")
class _StubAssetRepository:
def __init__(self, assets: dict[str, Asset] | None = None):
self._assets = assets or {}
self.list_by_library_called_with: list[str] = []
def list_by_library(self, library_id: str, skip: int = 0, limit: int = 50) -> list[Asset]:
self.list_by_library_called_with.append(library_id)
return [a for a in self._assets.values() if a.library_id == library_id]
def count_by_library(self, library_id: str) -> int:
return len([a for a in self._assets.values() if a.library_id == library_id])
def _dep(name: str):
from app import dependencies
return getattr(dependencies, name)
def _build_route_test_app(project_repo, library_repo, asset_repo):
from unittest.mock import MagicMock
from app.api.routes.asset_diagnosis import router
from app.auth import AuthenticatedUser, get_current_user
app = FastAPI()
app.include_router(router, prefix="/api/v1")
mock_user = MagicMock(spec=AuthenticatedUser)
mock_user.id = "user-1"
mock_user.email = "test@example.com"
mock_user.user.id = "user-1"
app.dependency_overrides[get_current_user] = lambda: mock_user
app.dependency_overrides[_dep("get_project_repository")] = lambda: project_repo
app.dependency_overrides[_dep("get_asset_library_repository")] = lambda: library_repo
app.dependency_overrides[_dep("get_asset_repository")] = lambda: asset_repo
return app
class TestAssetDiagnosisRoute:
"""路由层测试 — 验证 find_by_project 调用正确性。"""
def test_returns_404_when_project_not_found(self):
project_repo = _StubProjectRepository()
library_repo = _StubAssetLibraryRepository()
asset_repo = _StubAssetRepository()
app = _build_route_test_app(project_repo, library_repo, asset_repo)
client = TestClient(app)
resp = client.get("/api/v1/projects/nonexistent/asset-diagnosis")
assert resp.status_code == 404
def test_find_by_project_called_with_correct_project_id(self):
project = Project(id="proj-123", name="Test", owner_user_id="user-1")
library = AssetLibrary(id="lib-1", name="Lib", project_id="proj-123", kind=AssetLibraryKind.VIDEO)
project_repo = _StubProjectRepository({"proj-123": project})
library_repo = _StubAssetLibraryRepository({"lib-1": library})
asset_repo = _StubAssetRepository()
app = _build_route_test_app(project_repo, library_repo, asset_repo)
client = TestClient(app)
resp = client.get("/api/v1/projects/proj-123/asset-diagnosis")
assert resp.status_code == 200
assert library_repo.find_by_project_called_with == ["proj-123"]
def test_list_by_library_called_for_each_library(self):
project = Project(id="proj-1", name="Test", owner_user_id="user-1")
lib1 = AssetLibrary(id="lib-1", name="Lib1", project_id="proj-1", kind=AssetLibraryKind.VIDEO)
lib2 = AssetLibrary(id="lib-2", name="Lib2", project_id="proj-1", kind=AssetLibraryKind.VOICE)
project_repo = _StubProjectRepository({"proj-1": project})
library_repo = _StubAssetLibraryRepository({"lib-1": lib1, "lib-2": lib2})
asset_repo = _StubAssetRepository()
app = _build_route_test_app(project_repo, library_repo, asset_repo)
client = TestClient(app)
resp = client.get("/api/v1/projects/proj-1/asset-diagnosis")
assert resp.status_code == 200
assert set(asset_repo.list_by_library_called_with) == {"lib-1", "lib-2"}
def test_find_by_project_not_list_by_project(self):
"""路由调用 find_by_project 而非 list_by_project(否则会触发 AssertionError)。"""
project = Project(id="proj-1", name="Test", owner_user_id="user-1")
library = AssetLibrary(id="lib-1", name="Lib", project_id="proj-1", kind=AssetLibraryKind.VIDEO)
project_repo = _StubProjectRepository({"proj-1": project})
library_repo = _StubAssetLibraryRepository({"lib-1": library})
asset_repo = _StubAssetRepository()
app = _build_route_test_app(project_repo, library_repo, asset_repo)
client = TestClient(app)
resp = client.get("/api/v1/projects/proj-1/asset-diagnosis")
# 如果调用了 list_by_project,会抛 AssertionError → 500
assert resp.status_code == 200