fix(cover): validate video URL before MediaKit call
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m5s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m9s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m12s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m14s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 3m5s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m26s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m38s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m33s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m50s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m5s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m9s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m12s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m14s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 3m5s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m26s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m38s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m33s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m50s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
Root cause: MediaKit extract-frames times out (120s) when the video URL returns 404 (file doesn't exist on OSS). The timeout error message is misleading and wastes 2 minutes. Fix: - Add HTTP HEAD check before calling MediaKit to verify the video URL is accessible (returns 200) - If URL returns non-200, immediately return clear error: '预览视频URL不可访问 (HTTP 404)。请重新生成预览视频后再试。' - If HEAD request fails (network error), return clear error with exception type - Moved 'import requests as http_requests' to module top level Tests: - Updated test_mediakit_cover.py: mock http_requests.head in 4 tests that pass primary_video_url - Added new test: test_call_ai_cover_video_url_unreachable
This commit is contained in:
@@ -11,6 +11,8 @@ import json
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
import requests as http_requests
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from packages.domain.config_schemas import DEFAULT_EDIT_PLAN_CONFIG
|
||||
@@ -398,6 +400,25 @@ def _call_ai_cover_service(
|
||||
|
||||
client = get_mediakit_client()
|
||||
if client.is_available:
|
||||
# 先检查视频 URL 是否可访问,避免 MediaKit 下载失败后超时
|
||||
try:
|
||||
head_resp = http_requests.head(primary_video_url, timeout=10, allow_redirects=True)
|
||||
if head_resp.status_code != 200:
|
||||
logger.error(
|
||||
"封面视频URL不可访问: plan_id=%s url=%s status=%d",
|
||||
plan_id, primary_video_url, head_resp.status_code
|
||||
)
|
||||
raise RuntimeError(
|
||||
f"封面生成失败: 预览视频URL不可访问 (HTTP {head_resp.status_code})。"
|
||||
f"请重新生成预览视频后再试。"
|
||||
)
|
||||
except http_requests.RequestException as e:
|
||||
logger.error("封面视频URL连通性检查失败: plan_id=%s url=%s error=%s", plan_id, primary_video_url, e)
|
||||
raise RuntimeError(
|
||||
f"封面生成失败: 无法访问预览视频 ({e.__class__.__name__})。"
|
||||
f"请重新生成预览视频后再试。"
|
||||
) from e
|
||||
|
||||
try:
|
||||
logger.info("调用 MediaKit 抽帧: plan_id=%s video=%s", plan_id, primary_video_url[:80])
|
||||
frames = client.extract_frames(
|
||||
|
||||
@@ -142,9 +142,13 @@ class TestMediaKitClient:
|
||||
class TestAICoverService:
|
||||
"""AI 封面服务测试."""
|
||||
|
||||
@patch("packages.shared.ai_service.http_requests.head")
|
||||
@patch("packages.shared.mediakit_client.get_mediakit_client")
|
||||
def test_call_ai_cover_with_mediakit_success(self, mock_get_client):
|
||||
def test_call_ai_cover_with_mediakit_success(self, mock_get_client, mock_head):
|
||||
"""MediaKit 抽帧成功."""
|
||||
# Mock HEAD request to verify URL is accessible
|
||||
mock_head.return_value.status_code = 200
|
||||
|
||||
mock_client = Mock()
|
||||
mock_client.is_available = True
|
||||
mock_client.extract_frames.return_value = [{"image_url": "https://example.com/frame.jpg", "timestamp": 3.5}]
|
||||
@@ -166,9 +170,34 @@ class TestAICoverService:
|
||||
|
||||
mock_client.extract_frames.assert_called_once()
|
||||
|
||||
@patch("packages.shared.ai_service.http_requests.head")
|
||||
@patch("packages.shared.mediakit_client.get_mediakit_client")
|
||||
def test_call_ai_cover_with_mediakit_failure_raises(self, mock_get_client):
|
||||
def test_call_ai_cover_video_url_unreachable(self, mock_get_client, mock_head):
|
||||
"""视频 URL 不可访问时抛出 RuntimeError."""
|
||||
# Mock HEAD request to return 404
|
||||
mock_head.return_value.status_code = 404
|
||||
|
||||
mock_client = Mock()
|
||||
mock_client.is_available = True
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
from packages.shared.ai_service import _call_ai_cover_service
|
||||
|
||||
with pytest.raises(RuntimeError, match="预览视频URL不可访问"):
|
||||
_call_ai_cover_service(
|
||||
plan_id="plan-123",
|
||||
asset_ids=["asset-1"],
|
||||
cover_type="ai_frame",
|
||||
primary_video_url="https://example.com/nonexistent.mp4",
|
||||
)
|
||||
|
||||
@patch("packages.shared.ai_service.http_requests.head")
|
||||
@patch("packages.shared.mediakit_client.get_mediakit_client")
|
||||
def test_call_ai_cover_with_mediakit_failure_raises(self, mock_get_client, mock_head):
|
||||
"""MediaKit 失败时抛出 RuntimeError(不再降级到 stub)."""
|
||||
# Mock HEAD request to return 200 (URL is accessible, but MediaKit fails)
|
||||
mock_head.return_value.status_code = 200
|
||||
|
||||
mock_client = Mock()
|
||||
mock_client.is_available = True
|
||||
mock_client.extract_frames.side_effect = Exception("API error")
|
||||
@@ -243,9 +272,12 @@ class TestAICoverService:
|
||||
primary_video_url="https://example.com/video.mp4",
|
||||
)
|
||||
|
||||
@patch("packages.shared.ai_service.http_requests.head")
|
||||
@patch("packages.shared.mediakit_client.get_mediakit_client")
|
||||
def test_call_ai_cover_empty_frames_raises(self, mock_get_client):
|
||||
def test_call_ai_cover_empty_frames_raises(self, mock_get_client, mock_head):
|
||||
"""MediaKit 返回空帧列表时抛出 RuntimeError(不再降级)."""
|
||||
mock_head.return_value.status_code = 200 # URL accessible
|
||||
|
||||
mock_client = Mock()
|
||||
mock_client.is_available = True
|
||||
mock_client.extract_frames.return_value = []
|
||||
|
||||
Reference in New Issue
Block a user