test_settings.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. from core.settings import AppSettings
  2. def test_app_settings_validate_scope_defaults_to_all(monkeypatch) -> None:
  3. monkeypatch.delenv("ZHUCE6_VALIDATE_SCOPE", raising=False)
  4. settings = AppSettings.from_env()
  5. assert settings.validate_scope == "all"
  6. def test_app_settings_rejects_legacy_validate_scope(monkeypatch) -> None:
  7. monkeypatch.setenv("ZHUCE6_VALIDATE_SCOPE", "legacy_invalid_scope")
  8. settings = AppSettings.from_env()
  9. assert settings.validate_scope == "all"
  10. def test_app_settings_account_survival_default_cohort_is_ten(monkeypatch) -> None:
  11. monkeypatch.delenv("ZHUCE6_ACCOUNT_SURVIVAL_COHORT_SIZE", raising=False)
  12. settings = AppSettings.from_env()
  13. assert settings.account_survival_cohort_size == 10
  14. def test_app_settings_d1_cleanup_defaults(monkeypatch) -> None:
  15. monkeypatch.delenv("ZHUCE6_D1_CLEANUP_ENABLED", raising=False)
  16. monkeypatch.delenv("ZHUCE6_D1_CLEANUP_INTERVAL", raising=False)
  17. monkeypatch.delenv("ZHUCE6_D1_DATABASE_ID", raising=False)
  18. monkeypatch.delenv("ZHUCE6_D1_MAIL_RETENTION_HOURS", raising=False)
  19. monkeypatch.delenv("ZHUCE6_D1_ADDRESS_RETENTION_HOURS", raising=False)
  20. settings = AppSettings.from_env()
  21. assert settings.d1_cleanup_enabled is True
  22. assert settings.d1_cleanup_interval == 1800
  23. assert settings.d1_database_id == ""
  24. assert settings.d1_mail_retention_hours == 2
  25. assert settings.d1_address_retention_hours == 24
  26. def test_app_settings_d1_cleanup_env_overrides(monkeypatch) -> None:
  27. monkeypatch.setenv("ZHUCE6_D1_CLEANUP_ENABLED", "false")
  28. monkeypatch.setenv("ZHUCE6_D1_CLEANUP_INTERVAL", "900")
  29. monkeypatch.setenv("ZHUCE6_D1_DATABASE_ID", "db-test")
  30. monkeypatch.setenv("ZHUCE6_D1_MAIL_RETENTION_HOURS", "6")
  31. monkeypatch.setenv("ZHUCE6_D1_ADDRESS_RETENTION_HOURS", "48")
  32. settings = AppSettings.from_env()
  33. assert settings.d1_cleanup_enabled is False
  34. assert settings.d1_cleanup_interval == 900
  35. assert settings.d1_database_id == "db-test"
  36. assert settings.d1_mail_retention_hours == 6
  37. assert settings.d1_address_retention_hours == 48
  38. def test_app_settings_reads_proxy_pool_direct_urls(monkeypatch) -> None:
  39. monkeypatch.setenv("ZHUCE6_PROXY_POOL_DIRECT_URLS", "socks5://1.2.3.4:1080;http://5.6.7.8:8080")
  40. settings = AppSettings.from_env()
  41. assert settings.proxy_pool_direct_urls == "socks5://1.2.3.4:1080;http://5.6.7.8:8080"
  42. def test_app_settings_matches_shell_default_values(monkeypatch, tmp_path) -> None:
  43. keys = [
  44. "ZHUCE6_CONFIG_DIR",
  45. "ZHUCE6_STATE_DIR",
  46. "ZHUCE6_LOG_DIR",
  47. "ZHUCE6_POOL_DIR",
  48. "ZHUCE6_REGISTER_LOG_FILE",
  49. "ZHUCE6_RUNTIME_STATE_FILE",
  50. "ZHUCE6_ACCOUNT_SURVIVAL_STATE_FILE",
  51. "ZHUCE6_RESPONSES_SURVIVAL_STATE_FILE",
  52. "ZHUCE6_ROTATE_INTERVAL",
  53. "ZHUCE6_CPA_RUNTIME_RECONCILE_ENABLED",
  54. "ZHUCE6_CPA_RUNTIME_RECONCILE_COOLDOWN_SECONDS",
  55. "ZHUCE6_CPA_RUNTIME_RECONCILE_RESTART_ENABLED",
  56. "ZHUCE6_REGISTER_PROXY",
  57. "ZHUCE6_REGISTER_MAIL_PROVIDER",
  58. "ZHUCE6_REGISTER_BATCH_THREADS",
  59. "ZHUCE6_REGISTER_BATCH_TARGET_COUNT",
  60. "ZHUCE6_REGISTER_BATCH_INTERVAL_SECONDS",
  61. "ZHUCE6_ACCOUNT_SURVIVAL_ENABLED",
  62. "ZHUCE6_DASHBOARD_ALLOWED_ORIGINS",
  63. "ZHUCE6_ENABLE_PROXY_POOL",
  64. "ZHUCE6_PROXY_POOL_CONFIG",
  65. "ZHUCE6_PROXY_POOL_SIZE",
  66. "ZHUCE6_PROXY_POOL_REGIONS",
  67. "ZHUCE6_REGISTER_FRESH_PROXY_REGIONS",
  68. "ZHUCE6_CFMAIL_ROTATION_WINDOW",
  69. "ZHUCE6_CFMAIL_ROTATION_BLACKLIST_THRESHOLD",
  70. "ZHUCE6_CFMAIL_ROTATION_MAX_SUCCESSES",
  71. "ZHUCE6_ACCOUNT_SURVIVAL_PROXY",
  72. "ZHUCE6_RESPONSES_SURVIVAL_RECENT_WINDOW_SECONDS",
  73. "ZHUCE6_RESPONSES_SURVIVAL_REQUIRE_PROVENANCE",
  74. "ZHUCE6_WARMUP_MIN_AGE_SECONDS",
  75. "ZHUCE6_WARMUP_MIN_SUCCESSFUL_PROBES",
  76. "ZHUCE6_VALIDATE_SCOPE",
  77. ]
  78. for key in keys:
  79. monkeypatch.delenv(key, raising=False)
  80. monkeypatch.setenv("ZHUCE6_PROJECT_ROOT", str(tmp_path))
  81. settings = AppSettings.from_env()
  82. assert settings.rotate_interval == 120
  83. assert settings.recycle_rewarm_cooldown_seconds == 1800
  84. assert settings.cpa_runtime_reconcile_enabled is True
  85. assert settings.cpa_runtime_reconcile_cooldown_seconds == 300
  86. assert settings.cpa_runtime_reconcile_restart_enabled is False
  87. assert settings.register_proxy == "http://127.0.0.1:7899"
  88. assert settings.register_mail_provider == "cfmail"
  89. assert settings.register_batch_threads == 1
  90. assert settings.register_batch_target_count == 20
  91. assert settings.register_batch_interval_seconds == 10800
  92. assert settings.account_survival_enabled is True
  93. assert settings.dashboard_allowed_origins == ()
  94. assert settings.enable_proxy_pool is True
  95. assert settings.proxy_pool_size == 20
  96. assert settings.proxy_pool_regions == ("tw", "sg", "jp", "hk", "us")
  97. assert settings.register_fresh_proxy_regions == ("tw", "sg")
  98. assert settings.proxy_pool_config == (tmp_path / "clash_config.yaml").resolve()
  99. assert settings.cfmail_rotation_window == 10
  100. assert settings.cfmail_rotation_blacklist_threshold == 6
  101. assert settings.cfmail_rotation_max_successes == 2
  102. assert settings.account_survival_proxy == "http://127.0.0.1:7899"
  103. assert settings.responses_survival_state_file == (tmp_path / "state" / "responses_survival_tracker.json").resolve()
  104. assert settings.responses_survival_recent_window_seconds == 1800
  105. assert settings.responses_survival_require_provenance is True
  106. assert settings.warmup_min_age_seconds == 600
  107. assert settings.warmup_min_successful_probes == 2
  108. assert settings.config_dir == (tmp_path / "config").resolve()
  109. assert settings.state_dir == (tmp_path / "state").resolve()
  110. assert settings.log_dir == (tmp_path / "logs").resolve()
  111. assert settings.pool_dir == (tmp_path / "pool").resolve()
  112. assert settings.validate_scope == "all"
  113. assert not hasattr(settings, "main_" + "pool_target")
  114. assert not hasattr(settings, "rotate_probe_max_count")
  115. assert not hasattr(settings, "promotion_probe_proxy")
  116. assert not hasattr(settings, "promotion_probe_timeout_seconds")
  117. def test_app_settings_reads_dashboard_allowed_origins(monkeypatch) -> None:
  118. monkeypatch.setenv(
  119. "ZHUCE6_DASHBOARD_ALLOWED_ORIGINS",
  120. "http://127.0.0.1:8317, http://localhost:3000/ , https://dash.example.com",
  121. )
  122. settings = AppSettings.from_env()
  123. assert settings.dashboard_allowed_origins == (
  124. "http://127.0.0.1:8317",
  125. "http://localhost:3000",
  126. "https://dash.example.com",
  127. )
  128. def test_app_settings_validate_cfmail_env(monkeypatch) -> None:
  129. for key in [
  130. "ZHUCE6_CFMAIL_API_TOKEN",
  131. "ZHUCE6_CFMAIL_CF_AUTH_EMAIL",
  132. "ZHUCE6_CFMAIL_CF_AUTH_KEY",
  133. "ZHUCE6_CFMAIL_CF_ACCOUNT_ID",
  134. "ZHUCE6_CFMAIL_CF_ZONE_ID",
  135. "ZHUCE6_CFMAIL_WORKER_NAME",
  136. "ZHUCE6_CFMAIL_ZONE_NAME",
  137. ]:
  138. monkeypatch.delenv(key, raising=False)
  139. settings = AppSettings.from_env()
  140. missing = settings.validate_cfmail_env()
  141. assert missing == [
  142. "ZHUCE6_CFMAIL_API_TOKEN",
  143. "ZHUCE6_CFMAIL_CF_ACCOUNT_ID",
  144. "ZHUCE6_CFMAIL_CF_ZONE_ID",
  145. "ZHUCE6_CFMAIL_WORKER_NAME",
  146. "ZHUCE6_CFMAIL_ZONE_NAME",
  147. ]
  148. def test_app_settings_validate_cfmail_env_accepts_legacy_email_key_pair(monkeypatch) -> None:
  149. monkeypatch.delenv("ZHUCE6_CFMAIL_API_TOKEN", raising=False)
  150. monkeypatch.setenv("ZHUCE6_CFMAIL_CF_AUTH_EMAIL", "cf@example.com")
  151. monkeypatch.setenv("ZHUCE6_CFMAIL_CF_AUTH_KEY", "global-key")
  152. monkeypatch.setenv("ZHUCE6_CFMAIL_CF_ACCOUNT_ID", "account-1")
  153. monkeypatch.setenv("ZHUCE6_CFMAIL_CF_ZONE_ID", "zone-1")
  154. monkeypatch.setenv("ZHUCE6_CFMAIL_WORKER_NAME", "worker-one")
  155. monkeypatch.setenv("ZHUCE6_CFMAIL_ZONE_NAME", "example.com")
  156. settings = AppSettings.from_env()
  157. assert settings.validate_cfmail_env() == []
  158. def test_app_settings_reads_sub2api_backend(monkeypatch) -> None:
  159. monkeypatch.setenv("ZHUCE6_BACKEND", "sub2api")
  160. monkeypatch.setenv("ZHUCE6_SUB2API_BASE_URL", "http://127.0.0.1:8080")
  161. monkeypatch.setenv("ZHUCE6_SUB2API_ADMIN_EMAIL", "admin@sub2api.local")
  162. monkeypatch.setenv("ZHUCE6_SUB2API_ADMIN_PASSWORD", "secret")
  163. monkeypatch.setenv("ZHUCE6_SUB2API_API_KEY", "api-key")
  164. settings = AppSettings.from_env()
  165. assert settings.backend == "sub2api"
  166. assert settings.sub2api_base_url == "http://127.0.0.1:8080"
  167. assert settings.sub2api_admin_email == "admin@sub2api.local"
  168. assert settings.sub2api_admin_password == "secret"
  169. assert settings.sub2api_api_key == "api-key"
  170. def test_app_settings_no_longer_exposes_legacy_cpa_container_fields(monkeypatch, tmp_path) -> None:
  171. monkeypatch.setenv("ZHUCE6_PROJECT_ROOT", str(tmp_path))
  172. monkeypatch.setenv("ZHUCE6_CPA_MANAGEMENT_MODE", "legacy")
  173. monkeypatch.setenv("ZHUCE6_CPA_REMOTE_MODE", "false")
  174. monkeypatch.setenv("ZHUCE6_CPA_CONTAINER", "legacy-container")
  175. monkeypatch.setenv("ZHUCE6_CPA_AUTH_DIR", "/legacy/auth")
  176. settings = AppSettings.from_env()
  177. assert not hasattr(settings, "cpa_container")
  178. assert not hasattr(settings, "cpa_auth_dir")
  179. assert not hasattr(settings, "cpa_management_mode")
  180. def test_app_settings_ignores_legacy_cpa_management_env(monkeypatch, tmp_path) -> None:
  181. monkeypatch.setenv("ZHUCE6_PROJECT_ROOT", str(tmp_path))
  182. monkeypatch.setenv("ZHUCE6_CPA_MANAGEMENT_MODE", "legacy")
  183. monkeypatch.setenv("ZHUCE6_CPA_REMOTE_MODE", "false")
  184. monkeypatch.setenv("ZHUCE6_CPA_CONTAINER", "legacy-container")
  185. monkeypatch.setenv("ZHUCE6_CPA_AUTH_DIR", "/legacy/auth")
  186. monkeypatch.setenv("ZHUCE6_CPA_MANAGEMENT_BASE_URL", "http://127.0.0.1:9000/v0/management")
  187. monkeypatch.setenv("ZHUCE6_CPA_MANAGEMENT_KEY", "key-1")
  188. settings = AppSettings.from_env()
  189. assert settings.cpa_management_base_url == "http://127.0.0.1:9000/v0/management"
  190. assert settings.cpa_management_key == "key-1"