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
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>
235 lines
9.3 KiB
Python
Executable File
235 lines
9.3 KiB
Python
Executable File
"""P0-staging:OSS 上传崩溃修复测试.
|
||
|
||
测试:
|
||
1. oss_bucket() 传递 connect_timeout 参数
|
||
2. upload_to_oss() 小文件走 put_object_from_file,大文件走分片上传
|
||
3. upload_to_oss() 超时保护(超过总超时返回 None)
|
||
4. upload_to_oss() 异常时返回 None
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import tempfile
|
||
import time
|
||
from pathlib import Path
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
# ── oss_bucket connect_timeout 测试 ───────────────────────────────────────────
|
||
|
||
|
||
class TestOSSBucketConnectTimeout:
|
||
"""测试 oss_bucket() 传递 connect_timeout 参数."""
|
||
|
||
def test_oss_bucket_has_connect_timeout(self):
|
||
"""oss_bucket 应传递 connect_timeout=10s 参数."""
|
||
from video_processing.oss_helpers import oss_bucket
|
||
|
||
mock_bucket_instance = MagicMock()
|
||
with (
|
||
patch.dict(
|
||
os.environ,
|
||
{
|
||
"OSS_ACCESS_KEY_ID": "test-key",
|
||
"OSS_ACCESS_KEY_SECRET": "test-secret",
|
||
"OSS_ENDPOINT": "oss-cn-hangzhou.aliyuncs.com",
|
||
"OSS_BUCKET_NAME": "test-bucket",
|
||
},
|
||
),
|
||
patch("video_processing.oss_helpers.oss2.Auth"),
|
||
patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket_instance) as mock_bucket_cls,
|
||
):
|
||
bucket = oss_bucket()
|
||
|
||
assert bucket is mock_bucket_instance
|
||
# 验证 connect_timeout 关键字参数
|
||
call_kwargs = mock_bucket_cls.call_args[1]
|
||
assert "connect_timeout" in call_kwargs, "oss_bucket 应传递 connect_timeout 参数"
|
||
assert (
|
||
call_kwargs["connect_timeout"] == 10
|
||
), f"connect_timeout 应为 10,实际为 {call_kwargs['connect_timeout']}"
|
||
|
||
def test_oss_bucket_no_config_returns_none(self):
|
||
"""OSS 配置缺失时返回 None."""
|
||
from video_processing.oss_helpers import oss_bucket
|
||
|
||
with patch.dict(os.environ, {}, clear=True):
|
||
bucket = oss_bucket()
|
||
assert bucket is None
|
||
|
||
|
||
# ── upload_to_oss 分片上传测试 ────────────────────────────────────────────────
|
||
|
||
|
||
class TestUploadToOSSMultipart:
|
||
"""测试 upload_to_oss() 根据文件大小选择上传方式."""
|
||
|
||
def _create_temp_file(self, size_bytes: int) -> Path:
|
||
"""创建指定大小的临时文件."""
|
||
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
||
tmp.write(b"x" * size_bytes)
|
||
tmp.close()
|
||
return Path(tmp.name)
|
||
|
||
def test_small_file_uses_put_object(self):
|
||
"""小文件(<100MB)走 put_object_from_file."""
|
||
from video_processing.oss_helpers import upload_to_oss
|
||
|
||
small_file = self._create_temp_file(10 * 1024 * 1024) # 10MB
|
||
try:
|
||
mock_bucket = MagicMock()
|
||
|
||
with (
|
||
patch.dict(
|
||
os.environ,
|
||
{
|
||
"OSS_ACCESS_KEY_ID": "test-key",
|
||
"OSS_ACCESS_KEY_SECRET": "test-secret",
|
||
"OSS_ENDPOINT": "oss-cn-hangzhou.aliyuncs.com",
|
||
"OSS_BUCKET_NAME": "test-bucket",
|
||
},
|
||
),
|
||
patch("video_processing.oss_helpers.oss2.Auth"),
|
||
patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket),
|
||
patch("video_processing.oss_helpers.oss2.resumable_upload") as mock_resumable,
|
||
):
|
||
url = upload_to_oss(small_file, "test/small.mp4")
|
||
|
||
# 验证调用了 put_object_from_file
|
||
mock_bucket.put_object_from_file.assert_called_once()
|
||
# 验证没调用分片上传
|
||
mock_resumable.assert_not_called()
|
||
# 验证返回 URL
|
||
assert url == "https://test-bucket.oss-cn-hangzhou.aliyuncs.com/test/small.mp4"
|
||
finally:
|
||
small_file.unlink()
|
||
|
||
def test_large_file_uses_resumable_upload(self):
|
||
"""大文件(>=100MB)走 resumable_upload 分片上传."""
|
||
from video_processing.oss_helpers import upload_to_oss
|
||
|
||
large_file = self._create_temp_file(100 * 1024 * 1024) # 100MB
|
||
try:
|
||
mock_bucket = MagicMock()
|
||
|
||
with (
|
||
patch.dict(
|
||
os.environ,
|
||
{
|
||
"OSS_ACCESS_KEY_ID": "test-key",
|
||
"OSS_ACCESS_KEY_SECRET": "test-secret",
|
||
"OSS_ENDPOINT": "oss-cn-hangzhou.aliyuncs.com",
|
||
"OSS_BUCKET_NAME": "test-bucket",
|
||
},
|
||
),
|
||
patch("video_processing.oss_helpers.oss2.Auth"),
|
||
patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket),
|
||
patch("video_processing.oss_helpers.oss2.resumable_upload") as mock_resumable,
|
||
):
|
||
url = upload_to_oss(large_file, "test/large.mp4")
|
||
|
||
# 验证调用了分片上传
|
||
mock_resumable.assert_called_once()
|
||
# 验证没调用 put_object_from_file
|
||
mock_bucket.put_object_from_file.assert_not_called()
|
||
# 验证分片参数
|
||
call_kwargs = mock_resumable.call_args[1]
|
||
assert call_kwargs["multipart_threshold"] == 100 * 1024 * 1024
|
||
assert call_kwargs["part_size"] == 8 * 1024 * 1024
|
||
assert call_kwargs["num_threads"] == 3
|
||
# 验证返回 URL
|
||
assert url == "https://test-bucket.oss-cn-hangzhou.aliyuncs.com/test/large.mp4"
|
||
finally:
|
||
large_file.unlink()
|
||
|
||
|
||
# ── upload_to_oss 超时测试 ────────────────────────────────────────────────────
|
||
|
||
|
||
class TestUploadToOSSTimeout:
|
||
"""测试 upload_to_oss() 超时保护."""
|
||
|
||
def test_upload_timeout_returns_none(self):
|
||
"""上传超过总超时时返回 None."""
|
||
from video_processing.oss_helpers import upload_to_oss
|
||
|
||
small_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
||
small_file.write(b"x" * 1024) # 1KB
|
||
small_file.close()
|
||
file_path = Path(small_file.name)
|
||
|
||
def slow_upload(*args, **kwargs):
|
||
"""模拟慢速上传,超过超时时间."""
|
||
time.sleep(2)
|
||
|
||
mock_bucket = MagicMock()
|
||
mock_bucket.put_object_from_file.side_effect = slow_upload
|
||
|
||
try:
|
||
with (
|
||
patch.dict(
|
||
os.environ,
|
||
{
|
||
"OSS_ACCESS_KEY_ID": "test-key",
|
||
"OSS_ACCESS_KEY_SECRET": "test-secret",
|
||
"OSS_ENDPOINT": "oss-cn-hangzhou.aliyuncs.com",
|
||
"OSS_BUCKET_NAME": "test-bucket",
|
||
},
|
||
),
|
||
patch("video_processing.oss_helpers.oss2.Auth"),
|
||
patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket),
|
||
patch("video_processing.oss_helpers.OSS_UPLOAD_TOTAL_TIMEOUT", 1), # 1秒超时
|
||
):
|
||
url = upload_to_oss(file_path, "test/slow.mp4")
|
||
# 超时应返回 None
|
||
assert url is None, "上传超时应返回 None"
|
||
finally:
|
||
file_path.unlink()
|
||
|
||
def test_upload_exception_returns_none(self):
|
||
"""上传异常时返回 None."""
|
||
from video_processing.oss_helpers import upload_to_oss
|
||
|
||
small_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
||
small_file.write(b"x" * 1024)
|
||
small_file.close()
|
||
file_path = Path(small_file.name)
|
||
|
||
mock_bucket = MagicMock()
|
||
mock_bucket.put_object_from_file.side_effect = RuntimeError("Network error")
|
||
|
||
try:
|
||
with (
|
||
patch.dict(
|
||
os.environ,
|
||
{
|
||
"OSS_ACCESS_KEY_ID": "test-key",
|
||
"OSS_ACCESS_KEY_SECRET": "test-secret",
|
||
"OSS_ENDPOINT": "oss-cn-hangzhou.aliyuncs.com",
|
||
"OSS_BUCKET_NAME": "test-bucket",
|
||
},
|
||
),
|
||
patch("video_processing.oss_helpers.oss2.Auth"),
|
||
patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket),
|
||
):
|
||
url = upload_to_oss(file_path, "test/error.mp4")
|
||
assert url is None, "上传异常应返回 None"
|
||
finally:
|
||
file_path.unlink()
|
||
|
||
def test_upload_no_bucket_returns_none(self):
|
||
"""OSS 未配置时返回 None."""
|
||
from video_processing.oss_helpers import upload_to_oss
|
||
|
||
small_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
||
small_file.write(b"x" * 1024)
|
||
small_file.close()
|
||
file_path = Path(small_file.name)
|
||
|
||
try:
|
||
with patch.dict(os.environ, {}, clear=True):
|
||
url = upload_to_oss(file_path, "test/noconfig.mp4")
|
||
assert url is None
|
||
finally:
|
||
file_path.unlink()
|