881eea9195
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 36s
CI/CD Pipeline / Frontend Lint (push) Successful in 51s
CI/CD Pipeline / Build Production Runtime Images (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 / Unit Tests (push) Successful in 56s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (push) Failing after 9s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m50s
fix(api+ci): DELETE 204响应体全修复 + isort/black整理 + CI环境兼容 + 测试修复
254 lines
8.3 KiB
Python
254 lines
8.3 KiB
Python
"""
|
||
DELETE /asset-libraries/{library_id} 单元测试
|
||
|
||
覆盖:
|
||
- 正常删除空素材库(204)
|
||
- 删除含素材的库(同时删除库内素材)
|
||
- 素材库不存在(404)
|
||
- 无权限访问(403)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
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 fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from packages.adapters.in_memory.asset_library_repository import InMemoryAssetLibraryRepository
|
||
from packages.adapters.in_memory.asset_repository import InMemoryAssetRepository
|
||
from packages.domain import Asset, AssetLibrary, AssetLibraryKind, AssetStatus
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Stub Project Repository
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class StubProject:
|
||
"""最小化 Project stub,支持 can_access"""
|
||
|
||
def __init__(self, project_id: str, owner_id: str):
|
||
self.id = project_id
|
||
self._owner_id = owner_id
|
||
|
||
def can_access(self, user_id: str) -> bool:
|
||
return user_id == self._owner_id
|
||
|
||
|
||
class StubProjectRepository:
|
||
def __init__(self, projects: dict[str, StubProject] | None = None):
|
||
self._projects = projects or {}
|
||
|
||
def find_by_id(self, project_id: str):
|
||
return self._projects.get(project_id)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Helpers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _make_auth_user(user_id: str = "user-001"):
|
||
from app.auth import AuthenticatedUser
|
||
|
||
from packages.domain.entities import User
|
||
|
||
user = User(id=user_id, email="test@example.com", display_name="测试用户")
|
||
return AuthenticatedUser(user=user)
|
||
|
||
|
||
def _create_test_app(
|
||
library_repo: InMemoryAssetLibraryRepository,
|
||
asset_repo: InMemoryAssetRepository,
|
||
project_repo: StubProjectRepository,
|
||
user_id: str = "user-001",
|
||
):
|
||
from app.api.routes import asset_libraries as module
|
||
from app.api.routes.asset_libraries import router
|
||
|
||
app = FastAPI()
|
||
app.include_router(router, prefix="/api/v1/asset-libraries")
|
||
|
||
app.dependency_overrides[module.get_current_user] = lambda: _make_auth_user(user_id)
|
||
app.dependency_overrides[module.get_asset_library_repository] = lambda: library_repo
|
||
app.dependency_overrides[module.get_asset_repository] = lambda: asset_repo
|
||
app.dependency_overrides[module.get_project_repository] = lambda: project_repo
|
||
|
||
return app
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Tests
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestDeleteAssetLibrary:
|
||
"""DELETE /asset-libraries/{library_id} 测试"""
|
||
|
||
def test_delete_empty_library(self):
|
||
"""删除空素材库 → 204"""
|
||
from datetime import datetime, timezone
|
||
|
||
lib_repo = InMemoryAssetLibraryRepository()
|
||
asset_repo = InMemoryAssetRepository()
|
||
proj_repo = StubProjectRepository({"proj-1": StubProject("proj-1", "user-001")})
|
||
|
||
now = datetime.now(timezone.utc)
|
||
library = AssetLibrary(
|
||
id="lib-1",
|
||
project_id="proj-1",
|
||
name="视频素材库",
|
||
kind=AssetLibraryKind.VIDEO,
|
||
asset_count=0,
|
||
total_size=0,
|
||
created_at=now,
|
||
updated_at=now,
|
||
)
|
||
lib_repo.create(library)
|
||
|
||
app = _create_test_app(lib_repo, asset_repo, proj_repo)
|
||
client = TestClient(app)
|
||
|
||
response = client.delete("/api/v1/asset-libraries/lib-1")
|
||
assert response.status_code == 204
|
||
|
||
# 验证库已删除
|
||
assert lib_repo.find_by_id("lib-1") is None
|
||
|
||
def test_delete_library_with_assets(self):
|
||
"""删除含素材的库 → 库和素材都被删除"""
|
||
from datetime import datetime, timezone
|
||
|
||
lib_repo = InMemoryAssetLibraryRepository()
|
||
asset_repo = InMemoryAssetRepository()
|
||
proj_repo = StubProjectRepository({"proj-1": StubProject("proj-1", "user-001")})
|
||
|
||
now = datetime.now(timezone.utc)
|
||
library = AssetLibrary(
|
||
id="lib-1",
|
||
project_id="proj-1",
|
||
name="视频素材库",
|
||
kind=AssetLibraryKind.VIDEO,
|
||
asset_count=3,
|
||
total_size=1000,
|
||
created_at=now,
|
||
updated_at=now,
|
||
)
|
||
lib_repo.create(library)
|
||
|
||
# 创建 3 个素材
|
||
for i in range(3):
|
||
asset = Asset.create(
|
||
project_id="proj-1",
|
||
library_id="lib-1",
|
||
name=f"video_{i}.mp4",
|
||
storage_key=f"uploads/video_{i}.mp4",
|
||
mime_type="video/mp4",
|
||
status=AssetStatus.READY,
|
||
)
|
||
asset_repo.create(asset)
|
||
|
||
# 创建一个不属于该库的素材(不应被删除)
|
||
other_asset = Asset.create(
|
||
project_id="proj-1",
|
||
library_id="lib-other",
|
||
name="other.mp4",
|
||
storage_key="uploads/other.mp4",
|
||
mime_type="video/mp4",
|
||
status=AssetStatus.READY,
|
||
)
|
||
asset_repo.create(other_asset)
|
||
|
||
app = _create_test_app(lib_repo, asset_repo, proj_repo)
|
||
client = TestClient(app)
|
||
|
||
response = client.delete("/api/v1/asset-libraries/lib-1")
|
||
assert response.status_code == 204
|
||
|
||
# 库已删除
|
||
assert lib_repo.find_by_id("lib-1") is None
|
||
# 库内素材已删除
|
||
assert asset_repo.find_by_library("lib-1") == []
|
||
# 其他素材未受影响
|
||
assert asset_repo.get(other_asset.id) is not None
|
||
|
||
def test_delete_nonexistent_library(self):
|
||
"""删除不存在的素材库 → 404"""
|
||
lib_repo = InMemoryAssetLibraryRepository()
|
||
asset_repo = InMemoryAssetRepository()
|
||
proj_repo = StubProjectRepository({"proj-1": StubProject("proj-1", "user-001")})
|
||
|
||
app = _create_test_app(lib_repo, asset_repo, proj_repo)
|
||
client = TestClient(app)
|
||
|
||
response = client.delete("/api/v1/asset-libraries/nonexistent-id")
|
||
assert response.status_code == 404
|
||
assert "素材库不存在" in response.json()["detail"]
|
||
|
||
def test_delete_library_access_denied(self):
|
||
"""无权限用户删除素材库 → 403"""
|
||
from datetime import datetime, timezone
|
||
|
||
lib_repo = InMemoryAssetLibraryRepository()
|
||
asset_repo = InMemoryAssetRepository()
|
||
# 项目属于 user-002,当前用户是 user-001
|
||
proj_repo = StubProjectRepository({"proj-1": StubProject("proj-1", "user-002")})
|
||
|
||
now = datetime.now(timezone.utc)
|
||
library = AssetLibrary(
|
||
id="lib-1",
|
||
project_id="proj-1",
|
||
name="视频素材库",
|
||
kind=AssetLibraryKind.VIDEO,
|
||
asset_count=0,
|
||
total_size=0,
|
||
created_at=now,
|
||
updated_at=now,
|
||
)
|
||
lib_repo.create(library)
|
||
|
||
app = _create_test_app(lib_repo, asset_repo, proj_repo, user_id="user-001")
|
||
client = TestClient(app)
|
||
|
||
response = client.delete("/api/v1/asset-libraries/lib-1")
|
||
assert response.status_code == 403
|
||
assert "无权访问该项目" in response.json()["detail"]
|
||
|
||
# 库未被删除
|
||
assert lib_repo.find_by_id("lib-1") is not None
|
||
|
||
def test_delete_library_project_not_found(self):
|
||
"""素材库所属项目不存在 → 404"""
|
||
from datetime import datetime, timezone
|
||
|
||
lib_repo = InMemoryAssetLibraryRepository()
|
||
asset_repo = InMemoryAssetRepository()
|
||
# 空的项目仓库,找不到项目
|
||
proj_repo = StubProjectRepository({})
|
||
|
||
now = datetime.now(timezone.utc)
|
||
library = AssetLibrary(
|
||
id="lib-1",
|
||
project_id="proj-missing",
|
||
name="视频素材库",
|
||
kind=AssetLibraryKind.VIDEO,
|
||
asset_count=0,
|
||
total_size=0,
|
||
created_at=now,
|
||
updated_at=now,
|
||
)
|
||
lib_repo.create(library)
|
||
|
||
app = _create_test_app(lib_repo, asset_repo, proj_repo)
|
||
client = TestClient(app)
|
||
|
||
response = client.delete("/api/v1/asset-libraries/lib-1")
|
||
assert response.status_code == 404
|