test_config_config.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. # coding=utf-8
  2. """
  3. Unit tests for ddns.config.config module
  4. @author: GitHub Copilot
  5. """
  6. from __init__ import unittest
  7. from ddns.config.config import Config, split_array_string, SIMPLE_ARRAY_PARAMS # noqa: E402
  8. class TestSplitArrayString(unittest.TestCase):
  9. def test_split_array_string_comprehensive(self):
  10. """Test split_array_string with various input types"""
  11. # List input
  12. self.assertEqual(split_array_string(["item1", "item2"]), ["item1", "item2"])
  13. # Empty values
  14. self.assertEqual(split_array_string(""), [])
  15. self.assertEqual(split_array_string(None), []) # type: ignore[assignment]
  16. self.assertEqual(split_array_string(False), []) # type: ignore[assignment]
  17. self.assertEqual(split_array_string(0), []) # type: ignore[assignment]
  18. # Non-string, non-list input
  19. self.assertEqual(split_array_string(123), [123]) # type: ignore[assignment]
  20. self.assertEqual(split_array_string(True), [True]) # type: ignore[assignment]
  21. # Comma and semicolon separated
  22. self.assertEqual(split_array_string("item1,item2,item3"), ["item1", "item2", "item3"])
  23. self.assertEqual(split_array_string("item1, item2 , item3"), ["item1", "item2", "item3"])
  24. self.assertEqual(split_array_string("item1;item2;item3"), ["item1", "item2", "item3"])
  25. self.assertEqual(split_array_string("item1,,item3,"), ["item1", "item3"])
  26. # Single value and mixed separators
  27. self.assertEqual(split_array_string("single_item"), ["single_item"])
  28. self.assertEqual(split_array_string("item1,item2;item3"), ["item1", "item2;item3"])
  29. # Special prefixes (no split)
  30. self.assertEqual(split_array_string("regex:192\\.168\\..*,public"), ["regex:192\\.168\\..*,public"])
  31. self.assertEqual(split_array_string("cmd:curl -s ip.sb,public"), ["cmd:curl -s ip.sb,public"])
  32. self.assertEqual(
  33. split_array_string("shell:ip -6 addr | grep global,public"), ["shell:ip -6 addr | grep global,public"]
  34. )
  35. self.assertEqual(split_array_string("public,regex:192\\.168\\..*"), ["public", "regex:192\\.168\\..*"])
  36. class TestConfig(unittest.TestCase):
  37. def test_config_initialization_comprehensive(self):
  38. """Test Config initialization with various sources and priority"""
  39. # Empty initialization
  40. config = Config()
  41. self.assertEqual(config.dns, "") # Default is empty string, not "debug"
  42. self.assertEqual(config.id, "")
  43. self.assertEqual(config.index4, ["default"])
  44. self.assertEqual(config.index6, ["default"])
  45. self.assertEqual(config.ipv4, [])
  46. self.assertIsNone(config.ttl)
  47. self.assertTrue(config.cache)
  48. self.assertEqual(config.ssl, "auto")
  49. self.assertEqual(config.log_level, 20)
  50. self.assertEqual(config.log_datefmt, "%Y-%m-%dT%H:%M:%S")
  51. # CLI configuration
  52. cli_config = {
  53. "dns": "cloudflare",
  54. "id": "[email protected]",
  55. "token": "secret123",
  56. "ttl": "300",
  57. "cache": "false",
  58. "ssl": "true",
  59. "log_level": "DEBUG",
  60. }
  61. config = Config(cli_config=cli_config)
  62. self.assertEqual(config.dns, "cloudflare")
  63. self.assertEqual(config.ttl, 300)
  64. self.assertFalse(config.cache)
  65. self.assertTrue(config.ssl)
  66. self.assertEqual(config.log_level, 10)
  67. # JSON configuration
  68. json_config = {
  69. "dns": "alidns",
  70. "ipv4": ["example.com"],
  71. "proxy": ["http://proxy1.com"],
  72. "ttl": 600,
  73. "log_level": "WARNING",
  74. }
  75. config = Config(json_config=json_config)
  76. self.assertEqual(config.dns, "alidns")
  77. self.assertEqual(config.ipv4, ["example.com"])
  78. self.assertEqual(config.ttl, 600)
  79. self.assertEqual(config.log_level, 30)
  80. # Priority test: CLI > JSON > ENV
  81. cli_config = {"dns": "cli_dns", "id": "cli_id"}
  82. json_config = {"dns": "json_dns", "id": "json_id", "token": "json_token"}
  83. env_config = {"dns": "env_dns", "id": "env_id", "token": "env_token", "line": "env_line"}
  84. config = Config(cli_config=cli_config, json_config=json_config, env_config=env_config)
  85. self.assertEqual(config.dns, "cli_dns")
  86. self.assertEqual(config.id, "cli_id")
  87. self.assertEqual(config.token, "json_token")
  88. self.assertEqual(config.line, "env_line")
  89. def test_config_array_and_conversion_parameters(self):
  90. """Test array parameter processing and type conversions"""
  91. # Array parameters
  92. cli_config = {
  93. "ipv4": "domain1.com,domain2.com,domain3.com",
  94. "ipv6": "ipv6domain1.com;ipv6domain2.com",
  95. "proxy": ["http://proxy1.com", "http://proxy2.com"],
  96. "index4": "public,regex:192\\.168\\..*",
  97. "index6": "public;regex:fe80::.*",
  98. }
  99. config = Config(cli_config=cli_config)
  100. self.assertEqual(config.ipv4, ["domain1.com", "domain2.com", "domain3.com"])
  101. self.assertEqual(config.ipv6, ["ipv6domain1.com", "ipv6domain2.com"])
  102. self.assertEqual(config.proxy, ["http://proxy1.com", "http://proxy2.com"])
  103. self.assertEqual(config.index4, ["public", "regex:192\\.168\\..*"])
  104. self.assertEqual(config.index6, ["public", "regex:fe80::.*"])
  105. # TTL conversion
  106. config = Config(cli_config={"ttl": "300"})
  107. self.assertEqual(config.ttl, 300)
  108. self.assertIsInstance(config.ttl, int)
  109. config = Config(cli_config={"ttl": 600})
  110. self.assertEqual(config.ttl, 600)
  111. config = Config(cli_config={"ttl": b"150"})
  112. self.assertEqual(config.ttl, 150)
  113. # Proxy special values - simplified (no conversion in config)
  114. config = Config(cli_config={"proxy": ["http://proxy1.com", "DIRECT", "http://proxy2.com"]})
  115. self.assertEqual(config.proxy, ["http://proxy1.com", "DIRECT", "http://proxy2.com"])
  116. config = Config(cli_config={"proxy": ["NONE", "direct", "none", "NoNe"]})
  117. self.assertEqual(config.proxy, ["NONE", "direct", "none", "NoNe"])
  118. # Log level conversion
  119. test_cases = [("DEBUG", 10), ("INFO", 20), ("WARNING", 30), ("ERROR", 40), ("CRITICAL", 50), ("NOTSET", 0)]
  120. for level_str, expected_int in test_cases:
  121. config = Config(cli_config={"log_level": level_str})
  122. self.assertEqual(config.log_level, expected_int)
  123. config = Config(cli_config={"log_level": "debug"})
  124. self.assertEqual(config.log_level, 10)
  125. config = Config(cli_config={"log_level": 25})
  126. self.assertEqual(config.log_level, 25)
  127. def test_config_boolean_and_special_values(self):
  128. """Test boolean value conversions and special handling"""
  129. # Boolean conversions for cache and ssl
  130. test_cases = [
  131. ("true", True),
  132. ("false", False),
  133. ("yes", True),
  134. ("no", False),
  135. ("1", True),
  136. ("0", False),
  137. ("y", True),
  138. ("n", False),
  139. ("auto", "auto"),
  140. ("path/to/cert", "path/to/cert"),
  141. ]
  142. for str_val, expected in test_cases:
  143. config = Config(cli_config={"ssl": str_val})
  144. self.assertEqual(config.ssl, expected)
  145. if expected in [True, False]:
  146. config = Config(cli_config={"cache": str_val})
  147. self.assertEqual(config.cache, expected)
  148. # SSL special values
  149. ssl_cases = [("auto", "auto"), ("/path/to/cert.pem", "/path/to/cert.pem"), ("true", True), ("false", False)]
  150. for env_value, expected in ssl_cases:
  151. config = Config(env_config={"ssl": env_value})
  152. self.assertEqual(config.ssl, expected)
  153. # Index parameters with False values
  154. config = Config(cli_config={"index4": "false", "index6": "none"})
  155. self.assertFalse(config.index4)
  156. self.assertFalse(config.index6)
  157. def test_config_dict_and_utility_methods(self):
  158. """Test Config attributes and utility functions"""
  159. config = Config(cli_config={"dns": "cloudflare", "id": "test_id", "token": "secret", "ttl": 300})
  160. # Test individual attributes
  161. self.assertEqual(config.dns, "cloudflare")
  162. self.assertEqual(config.id, "test_id")
  163. self.assertEqual(config.token, "secret")
  164. self.assertEqual(config.ttl, 300)
  165. # Test SIMPLE_ARRAY_PARAMS constant
  166. self.assertIn("ipv4", SIMPLE_ARRAY_PARAMS)
  167. self.assertIn("ipv6", SIMPLE_ARRAY_PARAMS)
  168. self.assertIn("proxy", SIMPLE_ARRAY_PARAMS)
  169. self.assertIn("index4", SIMPLE_ARRAY_PARAMS)
  170. self.assertIn("index6", SIMPLE_ARRAY_PARAMS)
  171. # Test empty arrays vs none
  172. config = Config(cli_config={"ipv4": [], "ipv6": []})
  173. self.assertEqual(config.ipv4, [])
  174. self.assertEqual(config.ipv6, [])
  175. def test_config_md5_and_edge_cases(self):
  176. """Test MD5 functionality and edge cases"""
  177. # MD5 functionality
  178. config1 = Config(cli_config={"dns": "cloudflare", "id": "test"})
  179. config2 = Config(cli_config={"dns": "cloudflare", "id": "test"})
  180. config3 = Config(cli_config={"dns": "alidns", "id": "test"})
  181. self.assertEqual(config1.md5(), config2.md5())
  182. self.assertNotEqual(config1.md5(), config3.md5())
  183. md5_hash = config1.md5()
  184. self.assertEqual(len(md5_hash), 32)
  185. self.assertTrue(all(c in "0123456789abcdef" for c in md5_hash))
  186. # Edge cases and complex scenario
  187. cli_config = {
  188. "dns": "cloudflare",
  189. "id": "[email protected]",
  190. "token": "cf_token_123",
  191. "ipv4": "home.example.com,work.example.com",
  192. "ttl": 300,
  193. "cache": "auto",
  194. "ssl": "/path/to/cert.pem",
  195. "log_level": "INFO",
  196. }
  197. json_config = {
  198. "dns": "alidns",
  199. "token": "json_token",
  200. "proxy": ["proxy1.example.com:8080"],
  201. "cache": True,
  202. "ssl": "false",
  203. }
  204. env_config = {"line": "default", "endpoint": "https://api.example.com", "log_format": "%(message)s"}
  205. config = Config(cli_config=cli_config, json_config=json_config, env_config=env_config)
  206. self.assertEqual(config.dns, "cloudflare")
  207. self.assertEqual(config.token, "cf_token_123")
  208. self.assertEqual(config.ipv4, ["home.example.com", "work.example.com"])
  209. self.assertEqual(config.ttl, 300)
  210. self.assertEqual(config.cache, "auto")
  211. self.assertEqual(config.ssl, "/path/to/cert.pem")
  212. self.assertEqual(config.line, "default")
  213. self.assertEqual(config.endpoint, "https://api.example.com")
  214. # Invalid TTL handling and bytes conversion
  215. config = Config(cli_config={"ttl": None})
  216. self.assertIsNone(config.ttl)
  217. # Bytes to int conversion
  218. config = Config(cli_config={"ttl": b"300"})
  219. self.assertEqual(config.ttl, 300)
  220. def test_config_environment_variables_comprehensive(self):
  221. """Test comprehensive environment variable handling"""
  222. # Environment variable types and conversions
  223. env_config = {
  224. "dns": "dnspod",
  225. "id": "12345",
  226. "token": "secret_token",
  227. "ttl": "600",
  228. "cache": "1",
  229. "ssl": "0",
  230. "log_level": "DEBUG",
  231. "ipv4": ["domain1.com", "domain2.com"],
  232. "proxy": ["proxy1:8080", "proxy2:8080"],
  233. }
  234. config = Config(env_config=env_config)
  235. self.assertEqual(config.dns, "dnspod")
  236. self.assertEqual(config.ttl, 600)
  237. self.assertTrue(config.cache)
  238. self.assertFalse(config.ssl)
  239. self.assertEqual(config.log_level, 10)
  240. self.assertEqual(config.ipv4, ["domain1.com", "domain2.com"])
  241. # Environment variable priority over defaults
  242. env_config = {
  243. "dns": "custom_provider",
  244. "cache": "false",
  245. "ssl": "true",
  246. "log_level": "CRITICAL",
  247. "index4": "public",
  248. "index6": "public",
  249. }
  250. config = Config(env_config=env_config)
  251. self.assertEqual(config.dns, "custom_provider")
  252. self.assertFalse(config.cache)
  253. self.assertTrue(config.ssl)
  254. self.assertEqual(config.log_level, 50)
  255. self.assertEqual(config.index4, ["public"])
  256. self.assertEqual(config.index6, ["public"])
  257. # Empty and None environment values
  258. env_config = {"id": "", "token": None, "endpoint": "", "line": None}
  259. config = Config(env_config=env_config)
  260. self.assertEqual(config.id, "")
  261. self.assertIsNone(config.token)
  262. self.assertEqual(config.endpoint, "")
  263. self.assertIsNone(config.line)
  264. # TTL type conversion edge cases
  265. test_cases = [("300", 300), (300, 300), ("0", 0), (0, 0), (None, None)]
  266. for env_value, expected in test_cases:
  267. config = Config(env_config={"ttl": env_value})
  268. self.assertEqual(config.ttl, expected)
  269. def test_config_array_parameters_comprehensive(self):
  270. """Test array parameters with various input forms"""
  271. # Test special prefixes and complex scenarios
  272. prefix_cases = [
  273. (
  274. {"index4": "regex:192\\.168\\..*,10\\..*", "index6": "regex:fe80::.*,::1"},
  275. {"index4": ["regex:192\\.168\\..*,10\\..*"], "index6": ["regex:fe80::.*,::1"]},
  276. ),
  277. (
  278. {"index4": "cmd:curl -s ip.sb,backup", "index6": "cmd:curl -s ipv6.icanhazip.com,backup"},
  279. {"index4": ["cmd:curl -s ip.sb,backup"], "index6": ["cmd:curl -s ipv6.icanhazip.com,backup"]},
  280. ),
  281. (
  282. {
  283. "index4": "shell:ip route get 8.8.8.8 | awk '{print $7}';backup",
  284. "index6": "shell:ip -6 addr | grep global;backup",
  285. },
  286. {
  287. "index4": ["shell:ip route get 8.8.8.8 | awk '{print $7}';backup"],
  288. "index6": ["shell:ip -6 addr | grep global;backup"],
  289. },
  290. ),
  291. ]
  292. for cli_config, expected in prefix_cases:
  293. config = Config(cli_config=cli_config)
  294. for key, value in expected.items():
  295. self.assertEqual(getattr(config, key), value)
  296. # Normal splitting when prefix is in the middle
  297. normal_cases = [
  298. (
  299. {"index4": "public,regex:192\\.168\\..*", "index6": "public,cmd:curl -s ipv6.icanhazip.com"},
  300. {"index4": ["public", "regex:192\\.168\\..*"], "index6": ["public", "cmd:curl -s ipv6.icanhazip.com"]},
  301. ),
  302. (
  303. {"index4": "public,default", "index6": "public;default"},
  304. {"index4": ["public", "default"], "index6": ["public", "default"]},
  305. ),
  306. ]
  307. for cli_config, expected in normal_cases:
  308. config = Config(cli_config=cli_config)
  309. for key, value in expected.items():
  310. self.assertEqual(getattr(config, key), value)
  311. # CLI array parameters with various input forms
  312. test_cases = [
  313. ({"proxy": "192.168.1.1:8080;direct"}, {"proxy": ["192.168.1.1:8080", "direct"]}),
  314. ({"index4": "public,default"}, {"index4": ["public", "default"]}),
  315. (
  316. {"proxy": "proxy1.com:8080;DIRECT;proxy2.com:3128"},
  317. {"proxy": ["proxy1.com:8080", "DIRECT", "proxy2.com:3128"]},
  318. ),
  319. ({"proxy": ["192.168.1.1:8080;direct"]}, {"proxy": ["192.168.1.1:8080", "direct"]}),
  320. (
  321. {"proxy": ["192.168.1.1:8080", "direct", "proxy2.com:3128"]},
  322. {"proxy": ["192.168.1.1:8080", "direct", "proxy2.com:3128"]},
  323. ),
  324. ({"index4": ["public", "default", "custom"]}, {"index4": ["public", "default", "custom"]}),
  325. ({"proxy": []}, {"proxy": []}),
  326. ]
  327. for cli_config, expected in test_cases:
  328. config = Config(cli_config=cli_config)
  329. for key, value in expected.items():
  330. self.assertEqual(
  331. getattr(config, key), value, "Failed for config: {} on key: {}".format(cli_config, key)
  332. )
  333. class TestIsFalse(unittest.TestCase):
  334. def test_is_false_comprehensive(self):
  335. """Test is_false utility function"""
  336. from ddns.config.config import is_false
  337. # String values that return True
  338. for value in ["false", "FALSE", " false ", "none", "NONE", " none "]:
  339. self.assertTrue(is_false(value))
  340. # String values that return False
  341. for value in ["true", "0", "1", "", "anything"]:
  342. self.assertFalse(is_false(value))
  343. # Non-string values
  344. self.assertTrue(is_false(False))
  345. for value in [True, 0, 1, None, [], {}]:
  346. self.assertFalse(is_false(value))
  347. if __name__ == "__main__":
  348. unittest.main()