Files
xiaoxia-saas/tests/unit/test_oneclick_gen_p0_fixes.py
xiaoxia c873bb635f
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 55s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m13s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m43s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m46s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m51s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 3m6s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m59s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m14s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 30s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m53s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m6s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m6s
CI/CD Pipeline / Build Staging API Image (push) Successful in 7m12s
AI Code Review / AI Code Review (pull_request) Failing after 6m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 39s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 8m57s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m2s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 8m55s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m9s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Integration Tests (push) Successful in 3m12s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m13s
CI/CD Pipeline / Unit Tests (push) Successful in 14m24s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 12m30s
CI/CD Pipeline / CI Gate (pull_request) Successful in 8s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 552h54m45s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 552h54m47s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 552h54m46s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 552h54m48s
CI/CD Pipeline / Production Browser E2E (push) Failing after 552h55m35s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 552h54m48s
CI/CD Pipeline / Build Production Web Image (push) Failing after 552h55m37s
CI/CD Pipeline / Canary Release to Production (push) Failing after 552h55m36s
CI/CD Pipeline / Build Production API Image (push) Failing after 552h55m37s
CI/CD Pipeline / Deploy Production (push) Failing after 552h55m36s
CI/CD Pipeline / CI Gate (push) Failing after 552h55m37s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 553h3m51s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 553h3m53s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 553h3m55s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 553h3m57s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 553h7m15s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 553h7m17s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 553h7m21s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 553h8m45s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 553h8m47s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 553h9m58s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 553h8m49s
CI/CD Pipeline / PR Build API Image (push) Failing after 553h10m2s
CI/CD Pipeline / Frontend Lint (push) Failing after 553h10m2s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 553h10m4s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 553h28m38s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 553h29m29s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 553h38m21s
CI/CD Pipeline / PR Build Web Image (push) Failing after 553h43m52s
refactor: 统一渲染链路,预览即所得(-3370行) (#1479)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-08-24 14:16:00 +08:00

168 lines
6.3 KiB
Python
Executable File
Raw Permalink 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/P1 修复单元测试 — 一键生成 P0 问题 + P1 校验.
覆盖:
P0-2: OSS 上传失败抛异常 + URL 可访问性校验
P0-3: FFmpeg 失败时完整 stderr 日志
P1: template_id 存在性校验 + asset_ids 归属校验
"""
from __future__ import annotations
import logging
import subprocess
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
# 添加 worker app 到 sys.path
_WORKER_ROOT = Path(__file__).resolve().parents[2] / "apps" / "worker"
if str(_WORKER_ROOT) not in sys.path:
sys.path.insert(0, str(_WORKER_ROOT))
# ── P0-1: _download_library_assets ────────────────────────────────────────────
class TestOSSUploadAndVerify:
"""P0-2: OSS 上传失败抛异常 + URL 可访问性校验."""
def test_verify_url_accessible_success(self):
"""URL 可访问时返回 True."""
from worker_app.tasks.generation import _verify_url_accessible
mock_response = MagicMock()
mock_response.status = 200
mock_response.__enter__ = MagicMock(return_value=mock_response)
mock_response.__exit__ = MagicMock(return_value=False)
with patch("urllib.request.OpenerDirector.open", return_value=mock_response):
assert _verify_url_accessible("https://example.com/test.mp4") is True
def test_verify_url_accessible_failure(self):
"""URL 不可访问时返回 False."""
from worker_app.tasks.generation import _verify_url_accessible
with patch("urllib.request.OpenerDirector.open", side_effect=Exception("connection refused")):
assert _verify_url_accessible("https://example.com/test.mp4") is False
def test_verify_url_404(self):
"""URL 返回 404 时返回 False."""
from worker_app.tasks.generation import _verify_url_accessible
mock_response = MagicMock()
mock_response.status = 404
mock_response.__enter__ = MagicMock(return_value=mock_response)
mock_response.__exit__ = MagicMock(return_value=False)
with patch("urllib.request.OpenerDirector.open", return_value=mock_response):
assert _verify_url_accessible("https://example.com/test.mp4") is False
# ── P0-3: FFmpeg stderr 日志 ─────────────────────────────────────────────────
class TestFFmpegStderrLogging:
"""P0-3: FFmpeg 失败时完整 stderr 打到日志."""
def test_run_ffmpeg_logs_stderr_on_failure(self, caplog):
"""run_ffmpeg 失败时记录 stderr 到日志."""
from video_processing.ffmpeg_utils import run_ffmpeg
error = subprocess.CalledProcessError(
returncode=183,
cmd=["ffmpeg", "-y", "-i", "input.mp4", "output.mp4"],
output="",
stderr="Error message from ffmpeg: filter graph error details here",
)
with patch("subprocess.run", side_effect=error):
with caplog.at_level(logging.ERROR):
with pytest.raises(subprocess.CalledProcessError):
run_ffmpeg(["ffmpeg", "-y", "-i", "input.mp4", "output.mp4"])
assert "FFmpeg 命令失败" in caplog.text
assert "exit_code=183" in caplog.text
assert "filter graph error" in caplog.text
def test_run_ffmpeg_success(self):
"""run_ffmpeg 成功时正常返回."""
from video_processing.ffmpeg_utils import run_ffmpeg
mock_result = MagicMock()
mock_result.stdout = "output"
mock_result.stderr = ""
with patch("subprocess.run", return_value=mock_result):
stdout, stderr = run_ffmpeg(["ffmpeg", "-version"])
assert stdout == "output"
assert stderr == ""
# ── P1: template_id + asset_ids 校验 ─────────────────────────────────────────
class TestP1Validations:
"""P1: template_id 存在性校验 + asset_ids 归属校验."""
def test_validate_template_exists_success(self):
"""模板存在时不抛异常."""
from worker_app.tasks.generation import _validate_template_exists
mock_template = MagicMock()
mock_template.id = "tmpl_001"
mock_template.name = "Test Template"
mock_template.is_active = True
mock_template.status = "active"
mock_template.version = 1
session = MagicMock()
# EditTemplateModel 查询返回 None(走旧模板系统 fallback)
edit_query = MagicMock()
edit_filter = MagicMock()
edit_query.filter.return_value = edit_filter
edit_filter.first.return_value = None
# TemplateModel 查询返回 mock_template
old_query = MagicMock()
old_filter = MagicMock()
old_query.filter.return_value = old_filter
old_filter.first.return_value = mock_template
def _query_side_effect(model):
# 根据 model 类型返回不同的 query mock
name = getattr(model, "__name__", "")
if "EditTemplate" in name:
return edit_query
return old_query
session.query.side_effect = _query_side_effect
with patch("worker_app.tasks.generation.SessionLocal", return_value=session):
_validate_template_exists("tmpl_001") # 不抛异常
def test_validate_template_exists_not_found(self):
"""模板不存在时抛 ValueError."""
from worker_app.tasks.generation import _validate_template_exists
session = MagicMock()
# 两个系统查询都返回 None
def _query_side_effect(model):
q = MagicMock()
f = MagicMock()
q.filter.return_value = f
f.first.return_value = None
return q
session.query.side_effect = _query_side_effect
with patch("worker_app.tasks.generation.SessionLocal", return_value=session):
with pytest.raises(ValueError, match="模板不存在"):
_validate_template_exists("tmpl_nonexistent")
# ── P1: 一键生成 clip 级效果层映射 ───────────────────────────────────────────