""" config.py OSS 配置字段单元测试 覆盖: - OSS 相关字段默认值 - 环境变量覆盖 - 字段名与代码引用一致 - pydantic_settings 加载行为 """ from __future__ import annotations import importlib.util import os from pathlib import Path from unittest.mock import patch 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.aliyuncs.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.aliyuncs.com") assert settings.OSS_ENDPOINT == "oss-cn-shanghai.aliyuncs.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=False): os.environ.pop("OSS_DIRECT_UPLOAD_MAX_MB", None) Settings = _load_settings_class() settings = Settings() assert settings.OSS_DIRECT_UPLOAD_MAX_MB == 3000