Files
xiaoxia-saas/tests/unit/test_config_oss.py
T
xiaoxia 6eac0b2cf2
Worker Base Image Build / Build Worker Base Images (worker-base-builder-cache, infra/docker/worker-base-builder.Dockerfile, worker-base-builder, builder) (push) Failing after 1m54s
Worker Base Image Build / Build Worker Base Images (worker-base-runtime-cache, infra/docker/worker-base-runtime.Dockerfile, worker-base-runtime, runtime) (push) Failing after 1m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m29s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m4s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m2s
CI/CD Pipeline / Unit Tests (push) Successful in 3m38s
CI/CD Pipeline / Integration Tests (push) Successful in 2m0s
CI/CD Pipeline / Frontend Lint (push) Successful in 28s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 44s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Failing after 2m16s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 8m14s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 2m0s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
chore(ci): 同步main分支CI配置与scripts/ci脚本 - 与develop对齐
同步内容:
1. CI流水线配置(ci-pipeline.yml)与develop对齐
2. PR构建脚本docker_build_only.sh增加buildx→docker build回退
3. pre-build步骤worker基础镜像构建增加buildx回退
4. 单元测试脚本全量覆盖率改为仅报告不阻塞
5. diff-cover依赖加入requirements-dev.txt
6. worker base builder/runtime Dockerfile同步
7. test_config_oss.py clear=False→clear=True修复OSS污染
8. Frontend Lint增加prettier依赖
2026-07-24 10:36:29 +08:00

200 lines
7.0 KiB
Python

"""
config.py OSS 配置字段单元测试
覆盖:
- OSS 相关字段默认值
- 环境变量覆盖
- 字段名与代码引用一致
- pydantic_settings 加载行为
"""
from __future__ import annotations
import importlib.util
import os
import sys
from pathlib import Path
from unittest.mock import patch
import pytest
def _load_settings_class():
"""
直接加载 config.py 模块,绕过 apps/api/__init__.py 的副作用。
apps/api/__init__.py 会导入 main.py,而 main.py 依赖 app 模块。
"""
config_path = Path(__file__).resolve().parents[2] / "apps" / "api" / "app" / "config.py"
spec = importlib.util.spec_from_file_location("config_module", config_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.Settings
def _fresh_settings(**env_overrides: dict[str, str]):
"""
每次创建一个全新的 Settings 实例。
env_overrides 会注入到 os.environ。
"""
env = {
"JWT_SECRET_KEY": "unit-test-secret-key-12345",
**env_overrides,
}
with patch.dict(os.environ, env, clear=True):
Settings = _load_settings_class()
return Settings()
# ---------------------------------------------------------------------------
# 默认值测试
# ---------------------------------------------------------------------------
class TestOSSConfigDefaults:
"""OSS 配置字段默认值必须与代码引用一致。"""
def test_oss_endpoint_default(self):
settings = _fresh_settings()
assert settings.OSS_ENDPOINT == "oss-cn-hangzhou.aliiyuncs.com"
def test_oss_access_key_id_default_empty(self):
settings = _fresh_settings()
assert settings.OSS_ACCESS_KEY_ID == ""
def test_oss_access_key_secret_default_empty(self):
settings = _fresh_settings()
assert settings.OSS_ACCESS_KEY_SECRET == ""
def test_oss_bucket_name_default(self):
settings = _fresh_settings()
assert settings.OSS_BUCKET_NAME == "xiaoxia-autocut"
def test_oss_direct_upload_max_mb_default_is_2000(self):
"""PR #117 修复:默认值从 800 改为 2000。"""
settings = _fresh_settings()
assert settings.OSS_DIRECT_UPLOAD_MAX_MB == 2000
def test_oss_direct_upload_expire_seconds_default(self):
settings = _fresh_settings()
assert settings.OSS_DIRECT_UPLOAD_EXPIRE_SECONDS == 900
# ---------------------------------------------------------------------------
# 环境变量覆盖测试
# ---------------------------------------------------------------------------
class TestOSSConfigEnvOverride:
"""环境变量能正确覆盖 OSS 配置字段。"""
def test_oss_endpoint_override(self):
settings = _fresh_settings(OSS_ENDPOINT="oss-cn-shanghai.aliiyuncs.com")
assert settings.OSS_ENDPOINT == "oss-cn-shanghai.aliiyuncs.com"
def test_oss_access_key_id_override(self):
settings = _fresh_settings(OSS_ACCESS_KEY_ID="test-key-id")
assert settings.OSS_ACCESS_KEY_ID == "test-key-id"
def test_oss_access_key_secret_override(self):
settings = _fresh_settings(OSS_ACCESS_KEY_SECRET="test-key-secret")
assert settings.OSS_ACCESS_KEY_SECRET == "test-key-secret"
def test_oss_bucket_name_override(self):
settings = _fresh_settings(OSS_BUCKET_NAME="test-bucket")
assert settings.OSS_BUCKET_NAME == "test-bucket"
def test_oss_direct_upload_max_mb_override(self):
settings = _fresh_settings(OSS_DIRECT_UPLOAD_MAX_MB="4096")
assert settings.OSS_DIRECT_UPLOAD_MAX_MB == 4096
def test_oss_direct_upload_expire_seconds_override(self):
settings = _fresh_settings(OSS_DIRECT_UPLOAD_EXPIRE_SECONDS="1800")
assert settings.OSS_DIRECT_UPLOAD_EXPIRE_SECONDS == 1800
# ---------------------------------------------------------------------------
# 字段名一致性测试(防止再次出现字段名拼写错误导致 500)
# ---------------------------------------------------------------------------
class TestOSSConfigFieldNameConsistency:
"""
确保 Settings 类包含代码中实际引用的所有字段。
防止类似 OSS_DIRECT_UPLOAD_EXPRESS_SECRET 的拼写错误再次发生。
"""
def test_settings_has_oss_endpoint_field(self):
settings = _fresh_settings()
assert hasattr(settings, "OSS_ENDPOINT")
def test_settings_has_oss_access_key_id_field(self):
settings = _fresh_settings()
assert hasattr(settings, "OSS_ACCESS_KEY_ID")
def test_settings_has_oss_access_key_secret_field(self):
settings = _fresh_settings()
assert hasattr(settings, "OSS_ACCESS_KEY_SECRET")
def test_settings_has_oss_bucket_name_field(self):
settings = _fresh_settings()
assert hasattr(settings, "OSS_BUCKET_NAME")
def test_settings_has_oss_direct_upload_max_mb_field(self):
settings = _fresh_settings()
assert hasattr(settings, "OSS_DIRECT_UPLOAD_MAX_MB")
def test_settings_has_oss_direct_upload_expire_seconds_field(self):
settings = _fresh_settings()
assert hasattr(settings, "OSS_DIRECT_UPLOAD_EXPIRE_SECONDS")
def test_property_aliases_match_field_names(self):
"""确保 property 访问器与字段值一致。"""
settings = _fresh_settings(
OSS_ENDPOINT="ep",
OSS_ACCESS_KEY_ID="kid",
OSS_ACCESS_KEY_SECRET="ksec",
OSS_BUCKET_NAME="bkt",
)
assert settings.oss_endpoint == "ep"
assert settings.oss_access_key_id == "kid"
assert settings.oss_access_key_secret == "ksec"
assert settings.oss_bucket_name == "bkt"
# ---------------------------------------------------------------------------
# extra="ignore" 行为测试
# ---------------------------------------------------------------------------
class TestSettingsExtraIgnore:
"""extra="ignore" 确保未知环境变量不会导致启动失败。"""
def test_unknown_env_var_is_ignored(self):
settings = _fresh_settings(UNKNOWN_RANDOM_VAR="whatever")
assert not hasattr(settings, "UNKNOWN_RANDOM_VAR")
def test_settings_loads_without_error(self):
settings = _fresh_settings()
assert settings.APP_NAME == "xiaoxia-saas"
# ---------------------------------------------------------------------------
# 别名测试(MAX_UPLOAD_SIZE_MB 兼容旧配置)
# ---------------------------------------------------------------------------
class TestOSSConfigAliases:
"""OSS_DIRECT_UPLOAD_MAX_MB 支持 MAX_UPLOAD_SIZE_MB 别名。"""
def test_max_upload_size_mb_alias_works(self):
"""旧环境变量 MAX_UPLOAD_SIZE_MB 仍能生效。"""
env = {
"JWT_SECRET_KEY": "unit-test-secret-key-12345",
"MAX_UPLOAD_SIZE_MB": "3000",
}
with patch.dict(os.environ, env, clear=True):
os.environ.pop("OSS_DIRECT_UPLOAD_MAX_MB", None)
Settings = _load_settings_class()
settings = Settings()
assert settings.OSS_DIRECT_UPLOAD_MAX_MB == 3000