Files
xiaoxia-saas/tests/unit/test_asset_diagnosis.py
T
灵应 a57a26cb67
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Staging E2E Tests (push) Failing after 107h15m3s
Deploy / Deploy Staging (push) Failing after 107h16m41s
CI/CD Pipeline / Frontend Lint (push) Failing after 107h17m11s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 107h17m18s
fix(tests): 修复 sys.modules 污染导致 test_generated_video_management 间歇失败
根因:test_subscription_api._install_mocks() 在模块导入时替换了
packages.domain / packages.ports 等命名空间包,导致后续测试文件
无法导入真实的 GeneratedVideo 等实体类。

修复:移除所有 packages.* 命名空间包的 fake module 创建,仅保留
必要的叶子 mock(redis/smtp 适配器、app.config/auth/dependencies)。
让 packages.* 模块从磁盘正常加载。

同时包含:
- P2: asset_diagnosis 单素材诊断支持
- P2: edit_plans/edit_templates 管理员权限校验
- P2: edit_plans project 归属鉴权
- P3: test_error_scenarios 限流计数器重置
- P3: test_asset_diagnosis/test_edit_templates 测试修复
- P3: Alembic 迁移 024_add_user_is_admin
2026-07-05 01:37:06 +08:00

217 lines
8.4 KiB
Python

import os
import sys
from pathlib import Path
from uuid import uuid4
# 设置必要环境变量(必须在导入 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"))
import pytest
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