Files
xiaoxia-saas/tests/unit/test_oss_helpers_download.py
T
xiaoxia 840fd43cb3
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 1m6s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m50s
CI/CD Pipeline / Unit Tests (push) Successful in 3m17s
CI/CD Pipeline / Integration Tests (push) Successful in 1m23s
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 49s
CI Build & Deploy Pipeline / Build Staging API Image (push) Successful in 5m53s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Successful in 19s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Successful in 7m41s
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Successful in 1m57s
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Successful in 4m9s
fix(worker): download_asset支持HTTP URL下载,兼容预签名file_url (#487)
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-17 20:46:29 +08:00

190 lines
7.7 KiB
Python
Executable File

"""oss_helpers.download_asset 单元测试 — HTTP URL / OSS key 双路径覆盖."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
@pytest.fixture
def tmp_asset_dir(tmp_path: Path) -> Path:
d = tmp_path / "assets"
d.mkdir()
return d
# ── HTTP URL 下载路径 ──────────────────────────────────────────────────────
class TestDownloadAssetHttpUrl:
"""传入完整 https:// URL 时走 requests HTTP 下载,不走 oss2 SDK."""
@patch("apps.worker.video_processing.oss_helpers.requests.get")
def test_http_url_download_success(self, mock_get, tmp_asset_dir):
"""HTTP URL 下载成功."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_resp = MagicMock()
mock_resp.status_code = 200
mock_resp.iter_content.return_value = [b"fake-video-data" * 100]
mock_get.return_value = mock_resp
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("https://oss.example.com/assets/test.mp4?sign=xxx", local_path)
assert result is True
assert local_path.exists()
assert local_path.stat().st_size > 0
mock_get.assert_called_once()
# 不走 oss2 bucket
assert "oss_bucket" not in str(mock_get.call_args)
@patch("apps.worker.video_processing.oss_helpers.requests.get")
def test_http_url_download_403_returns_false(self, mock_get, tmp_asset_dir):
"""HTTP URL 返回 403,下载失败返回 False."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_resp = MagicMock()
mock_resp.status_code = 403
mock_resp.raise_for_status.side_effect = Exception("403 Forbidden")
mock_get.return_value = mock_resp
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("https://oss.example.com/assets/expired.mp4?sign=old", local_path)
assert result is False
@patch("apps.worker.video_processing.oss_helpers.requests.get")
def test_http_url_connection_error_returns_false(self, mock_get, tmp_asset_dir):
"""HTTP URL 连接超时,返回 False."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_get.side_effect = Exception("Connection timeout")
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("https://unreachable.example.com/test.mp4", local_path)
assert result is False
@patch("apps.worker.video_processing.oss_helpers.requests.get")
def test_http_url_streams_in_chunks(self, mock_get, tmp_asset_dir):
"""HTTP 下载使用流式 + 8MB chunk,避免大文件内存溢出."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_resp = MagicMock()
mock_resp.status_code = 200
mock_resp.iter_content.return_value = [b"chunk1", b"chunk2", b"chunk3"]
mock_get.return_value = mock_resp
local_path = tmp_asset_dir / "test.mp4"
download_asset("https://oss.example.com/assets/big.mp4", local_path)
mock_resp.iter_content.assert_called_once()
call_kwargs = mock_resp.iter_content.call_args
assert call_kwargs.kwargs.get("chunk_size") == 8 * 1024 * 1024
# ── OSS key 下载路径 ──────────────────────────────────────────────────────
class TestDownloadAssetOssKey:
"""传入 OSS 存储键时走 oss2 SDK,不走 HTTP."""
@patch("apps.worker.video_processing.oss_helpers.requests.get")
@patch("apps.worker.video_processing.oss_helpers.oss_bucket")
def test_oss_key_uses_sdk_not_http(self, mock_oss_bucket, mock_requests_get, tmp_asset_dir):
"""OSS key 路径走 oss2 SDK,不调用 requests.get."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_bucket = MagicMock()
mock_bucket.get_object_to_file.side_effect = lambda key, path: Path(path).write_bytes(b"oss-data")
mock_oss_bucket.return_value = mock_bucket
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("assets/user123/test.mp4", local_path)
assert result is True
mock_bucket.get_object_to_file.assert_called_once()
mock_requests_get.assert_not_called()
@patch("apps.worker.video_processing.oss_helpers.oss_bucket")
def test_oss_bucket_none_returns_false(self, mock_oss_bucket, tmp_asset_dir):
"""OSS 未配置时返回 False."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_oss_bucket.return_value = None
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("assets/test.mp4", local_path)
assert result is False
@patch("apps.worker.video_processing.oss_helpers.oss_bucket")
def test_oss_sdk_exception_returns_false(self, mock_oss_bucket, tmp_asset_dir):
"""OSS SDK 抛异常时返回 False,不崩溃."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_bucket = MagicMock()
mock_bucket.get_object_to_file.side_effect = Exception("NoSuchKey")
mock_oss_bucket.return_value = mock_bucket
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("assets/nonexistent.mp4", local_path)
assert result is False
# ── 路径选择逻辑 ──────────────────────────────────────────────────────────
class TestDownloadAssetRouteSelection:
"""URL vs OSS key 的路由选择."""
@patch("apps.worker.video_processing.oss_helpers._download_via_http")
@patch("apps.worker.video_processing.oss_helpers.oss_bucket")
def test_https_url_routes_to_http(self, mock_oss_bucket, mock_http_download, tmp_asset_dir):
"""https:// 开头走 HTTP 下载."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_http_download.return_value = True
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("https://oss.example.com/a/b.mp4", local_path)
assert result is True
mock_http_download.assert_called_once()
mock_oss_bucket.assert_not_called()
@patch("apps.worker.video_processing.oss_helpers._download_via_http")
@patch("apps.worker.video_processing.oss_helpers.oss_bucket")
def test_http_url_routes_to_http(self, mock_oss_bucket, mock_http_download, tmp_asset_dir):
"""http:// 开头也走 HTTP 下载."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_http_download.return_value = True
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("http://oss.example.com/a/b.mp4", local_path)
assert result is True
mock_http_download.assert_called_once()
mock_oss_bucket.assert_not_called()
@patch("apps.worker.video_processing.oss_helpers._download_via_http")
@patch("apps.worker.video_processing.oss_helpers.oss_bucket")
def test_plain_key_routes_to_oss_sdk(self, mock_oss_bucket, mock_http_download, tmp_asset_dir):
"""普通路径走 oss2 SDK."""
from apps.worker.video_processing.oss_helpers import download_asset
mock_bucket = MagicMock()
mock_bucket.get_object_to_file.side_effect = lambda key, path: Path(path).write_bytes(b"x")
mock_oss_bucket.return_value = mock_bucket
local_path = tmp_asset_dir / "test.mp4"
result = download_asset("user/123/asset.mp4", local_path)
assert result is True
mock_http_download.assert_not_called()