Files
xiaoxia-saas/tests/unit/test_generation_presigned_url.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

186 lines
5.9 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
P0-2 修复测试:generation_tasks results 端点返回 OSS 预签名 URL
验证 list_generation_results 端点:
- 对每个生成视频的 file_url 调用 storage_service.get_download_url()
- 返回的 download_url 是预签名临时 URL24h 有效期)
- 与 generated_videos.py 中的模式一致
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
from unittest.mock import MagicMock
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.generation_tasks import router
from app.auth import AuthenticatedUser, get_current_user
from app.core.storage import OSSStorageService, get_storage_service
from app.dependencies import (
get_generated_video_repository,
get_generation_task_repository,
get_project_repository,
)
from fastapi import FastAPI
from fastapi.testclient import TestClient
from packages.domain import (
GeneratedVideo,
GenerationTask,
GenerationTaskStatus,
Project,
User,
)
# ---------------------------------------------------------------------------
# Stub repositories
# ---------------------------------------------------------------------------
class StubProjectRepository:
def __init__(self, projects=None):
self._projects = projects or {}
def find_by_id(self, project_id):
return self._projects.get(project_id)
class StubGenerationTaskRepository:
def __init__(self, tasks=None):
self._tasks = tasks or {}
def get(self, task_id):
return self._tasks.get(task_id)
def count_pending_by_user(self, user_id):
return 0
def count_pending_total(self):
return 0
class StubGeneratedVideoRepository:
def __init__(self, videos=None):
self._videos = videos or {}
def list_by_generation_task(self, task_id):
return [v for v in self._videos.values() if v.generation_task_id == task_id]
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
def _make_app(task, videos, storage_mock):
app = FastAPI()
app.include_router(router, prefix="/api/v1/generation")
user = User(id="user-1", email="test@test.com", display_name="Test", username="testuser")
auth = AuthenticatedUser(user=user, token_type="access")
project = Project(id="project-1", name="Test", description="", owner_user_id="user-1")
app.dependency_overrides[get_current_user] = lambda: auth
app.dependency_overrides[get_project_repository] = lambda: StubProjectRepository({"project-1": project})
app.dependency_overrides[get_generation_task_repository] = lambda: StubGenerationTaskRepository({"task-1": task})
app.dependency_overrides[get_generated_video_repository] = lambda: StubGeneratedVideoRepository(videos)
app.dependency_overrides[get_storage_service] = lambda: storage_mock
return app
def test_results_endpoint_generates_presigned_urls():
"""验证 list_generation_results 为每个视频生成预签名 download_url"""
task = GenerationTask(
id="task-1",
project_id="project-1",
asset_library_id="lib-1",
strategy_id="s1",
status=GenerationTaskStatus.COMPLETED,
progress=100,
result_count=2,
created_by_user_id="user-1",
)
videos = {
"v1": GeneratedVideo.create(
project_id="project-1",
generation_task_id="task-1",
name="video1.mp4",
file_url="https://bucket.oss-cn-hangzhou.aliyuncs.com/generated/v1.mp4",
file_size=1024,
duration=5.0,
width=1280,
height=720,
fps=25.0,
),
"v2": GeneratedVideo.create(
project_id="project-1",
generation_task_id="task-1",
name="video2.mp4",
file_url="https://bucket.oss-cn-hangzhou.aliyuncs.com/generated/v2.mp4",
file_size=2048,
duration=10.0,
width=1920,
height=1080,
fps=30.0,
),
}
storage_mock = MagicMock(spec=OSSStorageService)
storage_mock.get_download_url.side_effect = (
lambda url, expires_seconds=3600: f"{url}?signature=presigned&expires={expires_seconds}"
)
app = _make_app(task, videos, storage_mock)
client = TestClient(app)
response = client.get("/api/v1/generation/tasks/task-1/results")
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 2
# Verify presigned URLs were generated with 24h expiry
assert storage_mock.get_download_url.call_count == 2
for call in storage_mock.get_download_url.call_args_list:
assert call.kwargs["expires_seconds"] == 86400
# Verify download_url is present in response
for item in data["items"]:
assert item["download_url"] is not None
assert "signature=presigned" in item["download_url"]
assert "expires=86400" in item["download_url"]
# Verify file_url is still the original (raw) URL
assert data["items"][0]["file_url"] == videos["v1"].file_url
def test_results_endpoint_handles_empty_videos():
"""验证无视频时正常返回空列表"""
task = GenerationTask(
id="task-1",
project_id="project-1",
asset_library_id="lib-1",
strategy_id="s1",
status=GenerationTaskStatus.RUNNING,
progress=50,
created_by_user_id="user-1",
)
storage_mock = MagicMock(spec=OSSStorageService)
app = _make_app(task, {}, storage_mock)
client = TestClient(app)
response = client.get("/api/v1/generation/tasks/task-1/results")
assert response.status_code == 200
assert response.json()["items"] == []
storage_mock.get_download_url.assert_not_called()