test_config_init_multi.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # coding=utf-8
  2. # type: ignore[index,operator,assignment]
  3. """
  4. Unit tests for multi-config functionality
  5. @author: GitHub Copilot
  6. """
  7. from __init__ import unittest, patch
  8. import tempfile
  9. import json
  10. import os
  11. import sys
  12. from ddns.config import load_configs
  13. from ddns.config.file import load_config as load_file_config, _process_multi_providers
  14. class TestMultiConfig(unittest.TestCase):
  15. def setUp(self):
  16. self.temp_dir = tempfile.mkdtemp()
  17. self.original_argv = sys.argv[:]
  18. def tearDown(self):
  19. sys.argv = self.original_argv
  20. # Clean up temp directory
  21. import shutil
  22. shutil.rmtree(self.temp_dir, ignore_errors=True)
  23. def test_file_loader_single_object(self):
  24. """Test that file loader works with single object configs"""
  25. config_data = {"dns": "cloudflare", "id": "[email protected]", "token": "secret123"}
  26. config_path = os.path.join(self.temp_dir, "single_config.json")
  27. with open(config_path, "w") as f:
  28. json.dump(config_data, f)
  29. result = load_file_config(config_path)
  30. self.assertIsInstance(result, dict)
  31. self.assertEqual(result["dns"], "cloudflare")
  32. self.assertEqual(result["id"], "[email protected]")
  33. self.assertEqual(result["token"], "secret123")
  34. def test_cli_multiple_configs(self):
  35. """Test CLI support for multiple config files"""
  36. # Create two config files
  37. config1_data = {"dns": "cloudflare", "id": "[email protected]", "token": "secret1"}
  38. config2_data = {"dns": "dnspod", "id": "[email protected]", "token": "secret2"}
  39. config1_path = os.path.join(self.temp_dir, "config1.json")
  40. config2_path = os.path.join(self.temp_dir, "config2.json")
  41. with open(config1_path, "w") as f:
  42. json.dump(config1_data, f)
  43. with open(config2_path, "w") as f:
  44. json.dump(config2_data, f)
  45. # Mock CLI args
  46. sys.argv = ["ddns", "-c", config1_path, "-c", config2_path]
  47. with patch("ddns.config.cli.load_config") as mock_cli:
  48. mock_cli.return_value = {"config": [config1_path, config2_path]}
  49. with patch("ddns.config.env.load_config") as mock_env:
  50. mock_env.return_value = {}
  51. configs = load_configs("test", "1.0", "2023-01-01")
  52. self.assertEqual(len(configs), 2)
  53. self.assertEqual(configs[0].dns, "cloudflare")
  54. self.assertEqual(configs[1].dns, "dnspod")
  55. def test_env_multiple_configs_integration(self):
  56. """Test environment variable support for multiple config files using real CLI/env loading"""
  57. # Create two config files
  58. config1_data = {"dns": "debug", "id": "[email protected]", "token": "secret1"}
  59. config2_data = {"dns": "debug", "id": "[email protected]", "token": "secret2"}
  60. config1_path = os.path.join(self.temp_dir, "config1.json")
  61. config2_path = os.path.join(self.temp_dir, "config2.json")
  62. with open(config1_path, "w") as f:
  63. json.dump(config1_data, f)
  64. with open(config2_path, "w") as f:
  65. json.dump(config2_data, f)
  66. # Test via environment variable
  67. old_env = os.environ.get("DDNS_CONFIG")
  68. try:
  69. os.environ["DDNS_CONFIG"] = "{},{}".format(config1_path, config2_path)
  70. sys.argv = ["ddns"]
  71. configs = load_configs("test", "1.0", "2023-01-01")
  72. self.assertEqual(len(configs), 2)
  73. self.assertEqual(configs[0].dns, "debug")
  74. self.assertEqual(configs[1].dns, "debug")
  75. self.assertEqual(configs[0].id, "[email protected]")
  76. self.assertEqual(configs[1].id, "[email protected]")
  77. finally:
  78. if old_env is not None:
  79. os.environ["DDNS_CONFIG"] = old_env
  80. elif "DDNS_CONFIG" in os.environ:
  81. del os.environ["DDNS_CONFIG"]
  82. def test_multi_provider_proxy_individual_settings(self):
  83. """测试每个provider可以有独立的proxy设置"""
  84. config = {
  85. "providers": [
  86. {
  87. "provider": "cloudflare",
  88. "id": "[email protected]",
  89. "token": "cf_token",
  90. "ipv4": ["cf.example.com"],
  91. "proxy": "http://127.0.0.1:1080",
  92. },
  93. {
  94. "provider": "alidns",
  95. "id": "access_key",
  96. "token": "secret_key",
  97. "ipv4": ["ali.example.com"],
  98. "proxy": "http://proxy.internal:8080;DIRECT",
  99. },
  100. {
  101. "provider": "dnspod",
  102. "id": "12345",
  103. "token": "dnspod_token",
  104. "ipv4": ["dp.example.com"],
  105. # 没有proxy设置,应该继承全局或为None
  106. },
  107. ],
  108. "cache": True,
  109. "ttl": 600,
  110. }
  111. result = _process_multi_providers(config)
  112. # 验证返回3个独立配置
  113. self.assertEqual(len(result), 3)
  114. # 验证cloudflare配置
  115. cf_config = result[0]
  116. self.assertEqual(cf_config["dns"], "cloudflare")
  117. self.assertEqual(cf_config["proxy"], "http://127.0.0.1:1080")
  118. self.assertEqual(cf_config["ipv4"], ["cf.example.com"])
  119. # 验证alidns配置
  120. ali_config = result[1]
  121. self.assertEqual(ali_config["dns"], "alidns")
  122. self.assertEqual(ali_config["proxy"], "http://proxy.internal:8080;DIRECT")
  123. self.assertEqual(ali_config["ipv4"], ["ali.example.com"])
  124. # 验证dnspod配置(无proxy)
  125. dp_config = result[2]
  126. self.assertEqual(dp_config["dns"], "dnspod")
  127. self.assertNotIn("proxy", dp_config) # 没有设置proxy
  128. self.assertEqual(dp_config["ipv4"], ["dp.example.com"])
  129. # 验证全局配置继承
  130. for config_item in result:
  131. self.assertEqual(config_item["cache"], True)
  132. self.assertEqual(config_item["ttl"], 600)
  133. def test_multi_provider_with_global_proxy_override(self):
  134. """测试provider级别的proxy覆盖全局proxy"""
  135. config = {
  136. "proxy": "http://global.proxy:8080", # 全局proxy
  137. "providers": [
  138. {
  139. "provider": "cloudflare",
  140. "id": "[email protected]",
  141. "token": "cf_token",
  142. "ipv4": ["cf.example.com"],
  143. "proxy": "http://127.0.0.1:1080", # provider级别proxy覆盖全局
  144. },
  145. {
  146. "provider": "alidns",
  147. "id": "access_key",
  148. "token": "secret_key",
  149. "ipv4": ["ali.example.com"],
  150. # 没有provider级别proxy,使用全局proxy
  151. },
  152. ],
  153. }
  154. result = _process_multi_providers(config)
  155. # cloudflare使用provider级别的proxy
  156. cf_config = result[0]
  157. self.assertEqual(cf_config["proxy"], "http://127.0.0.1:1080")
  158. # alidns使用全局proxy
  159. ali_config = result[1]
  160. self.assertEqual(ali_config["proxy"], "http://global.proxy:8080")
  161. def test_multi_provider_proxy_array_format(self):
  162. """测试provider级别的数组格式proxy配置"""
  163. config = {
  164. "providers": [
  165. {
  166. "provider": "cloudflare",
  167. "id": "[email protected]",
  168. "token": "cf_token",
  169. "ipv4": ["cf.example.com"],
  170. "proxy": ["http://127.0.0.1:1080", "DIRECT"],
  171. }
  172. ]
  173. }
  174. result = _process_multi_providers(config)
  175. cf_config = result[0]
  176. self.assertEqual(cf_config["proxy"], ["http://127.0.0.1:1080", "DIRECT"])
  177. def test_env_multi_proxy_remote_config_array_conversion(self):
  178. """测试环境变量配置多代理远程配置,验证代理参数正确转换为数组"""
  179. # 创建远程配置文件,包含多种代理格式
  180. remote_config_data = {
  181. "providers": [
  182. {
  183. "provider": "debug",
  184. "id": "[email protected]",
  185. "token": "remote_token1",
  186. "ipv4": ["remote1.example.com"],
  187. "proxy": "http://proxy1.example.com:8080;http://proxy2.example.com:8080;DIRECT",
  188. },
  189. {
  190. "provider": "debug",
  191. "id": "[email protected]",
  192. "token": "remote_token2",
  193. "ipv4": ["remote2.example.com"],
  194. "proxy": ["http://array.proxy1.com:3128", "http://array.proxy2.com:3128", "DIRECT"],
  195. },
  196. ],
  197. "proxy": "http://global.proxy1.com:8080;http://global.proxy2.com:8080;DIRECT",
  198. }
  199. # 创建本地文件模拟远程配置
  200. remote_config_path = os.path.join(self.temp_dir, "remote_multi_proxy.json")
  201. with open(remote_config_path, "w") as f:
  202. json.dump(remote_config_data, f)
  203. # 测试场景1:环境变量代理覆盖配置文件代理
  204. with patch("ddns.config.load_env_config") as mock_env:
  205. with patch("ddns.config.load_cli_config") as mock_cli:
  206. # Mock环境变量配置,包含config和proxy设置
  207. mock_env.return_value = {
  208. "config": "file:///{}".format(remote_config_path),
  209. "proxy": "http://env.proxy1.com:9090;http://env.proxy2.com:9090;DIRECT",
  210. }
  211. # Mock CLI配置为空,避免干扰
  212. mock_cli.return_value = {}
  213. configs = load_configs("test", "1.0", "2023-01-01")
  214. self.assertEqual(len(configs), 2)
  215. # 验证第一个配置的代理转换 - 现在检查实际的代理值
  216. config1 = configs[0]
  217. self.assertEqual(config1.dns, "debug")
  218. self.assertEqual(config1.id, "[email protected]")
  219. # 当前实现中,配置文件中的代理设置优先,环境变量作为fallback
  220. # 所以这里验证配置文件中的代理被正确转换为数组
  221. expected_proxy1 = ["http://proxy1.example.com:8080", "http://proxy2.example.com:8080", "DIRECT"]
  222. self.assertEqual(config1.proxy, expected_proxy1)
  223. # 验证第二个配置的代理转换
  224. config2 = configs[1]
  225. self.assertEqual(config2.dns, "debug")
  226. self.assertEqual(config2.id, "[email protected]")
  227. # 第二个配置应该保持数组格式
  228. expected_proxy2 = ["http://array.proxy1.com:3128", "http://array.proxy2.com:3128", "DIRECT"]
  229. self.assertEqual(config2.proxy, expected_proxy2)
  230. # 测试场景2:不设置环境变量代理时,配置文件中的代理被正确转换
  231. with patch("ddns.config.load_env_config") as mock_env:
  232. with patch("ddns.config.load_cli_config") as mock_cli:
  233. # Mock环境变量配置,只设置config不设置proxy
  234. mock_env.return_value = {"config": remote_config_path}
  235. # Mock CLI配置为空,避免干扰
  236. mock_cli.return_value = {}
  237. sys.argv = ["ddns"]
  238. configs = load_configs("test", "1.0", "2023-01-01")
  239. # 验证第一个配置:字符串格式代理被转换为数组
  240. config1 = configs[0]
  241. expected_proxy1 = ["http://proxy1.example.com:8080", "http://proxy2.example.com:8080", "DIRECT"]
  242. self.assertEqual(config1.proxy, expected_proxy1)
  243. # 验证第二个配置:数组格式代理保持不变
  244. config2 = configs[1]
  245. expected_proxy2 = ["http://array.proxy1.com:3128", "http://array.proxy2.com:3128", "DIRECT"]
  246. self.assertEqual(config2.proxy, expected_proxy2)
  247. if __name__ == "__main__":
  248. unittest.main()