test_config_schema_v4_1.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. # -*- coding:utf-8 -*-
  2. # type: ignore[index,operator,assignment]
  3. """
  4. Integration test for all config formats including v4.1 providers
  5. @author: GitHub Copilot
  6. """
  7. from __future__ import unicode_literals
  8. from __init__ import unittest
  9. import tempfile
  10. import shutil
  11. import os
  12. import json
  13. from ddns.config import load_configs
  14. class TestAllConfigFormatsIntegration(unittest.TestCase):
  15. """Integration test for all supported config formats"""
  16. def setUp(self):
  17. """Set up test fixtures"""
  18. self.temp_dir = tempfile.mkdtemp()
  19. self.addCleanup(shutil.rmtree, self.temp_dir, ignore_errors=True)
  20. def create_test_file(self, filename, content):
  21. # type: (str, dict) -> str
  22. """Helper method to create a test file with given content"""
  23. file_path = os.path.join(self.temp_dir, filename)
  24. with open(file_path, "w") as f:
  25. if isinstance(content, dict):
  26. f.write(json.dumps(content, indent=2))
  27. else:
  28. f.write(content)
  29. return file_path
  30. def test_all_config_formats_integration(self):
  31. """Test loading v4.1 config formats"""
  32. # Create single object config
  33. single_config = {
  34. "dns": "cloudflare",
  35. "id": "[email protected]",
  36. "token": "single_token",
  37. "ipv4": ["single.example.com"],
  38. "ssl": True,
  39. }
  40. single_file = self.create_test_file("single.json", single_config)
  41. # Create v4.1 providers format
  42. providers_config = {
  43. "$schema": "https://ddns.newfuture.cc/schema/v4.1.json",
  44. "ssl": "auto",
  45. "cache": True,
  46. "providers": [
  47. {"provider": "debug", "token": "provider_token1", "ipv4": ["provider1.example.com"], "ttl": 300},
  48. {"provider": "debug", "token": "provider_token2", "ipv4": ["provider2.example.com"], "ttl": 600},
  49. ],
  50. }
  51. providers_file = self.create_test_file("providers.json", providers_config)
  52. # Mock sys.argv to control CLI parsing
  53. import sys
  54. original_argv = sys.argv
  55. try:
  56. # Set fake argv with our config files
  57. sys.argv = ["ddns", "-c", single_file, "-c", providers_file]
  58. # Load all configs
  59. all_configs = load_configs("test", "1.0.0", "2025-07-17")
  60. # Should have 3 total configs: 1 single + 2 from providers
  61. self.assertEqual(len(all_configs), 3)
  62. # Test single config
  63. self.assertEqual(all_configs[0].dns, "cloudflare")
  64. self.assertEqual(all_configs[0].id, "[email protected]")
  65. self.assertEqual(all_configs[0].ssl, True)
  66. # Test first provider config
  67. self.assertEqual(all_configs[1].dns, "debug")
  68. self.assertEqual(all_configs[1].token, "provider_token1")
  69. self.assertEqual(all_configs[1].ipv4, ["provider1.example.com"])
  70. self.assertEqual(all_configs[1].ttl, 300)
  71. # Inherited from global - handle Python 2.7 unicode strings
  72. self.assertEqual(str(all_configs[1].ssl), "auto")
  73. # Inherited from global
  74. self.assertEqual(all_configs[1].cache, True)
  75. # Test second provider config
  76. self.assertEqual(all_configs[2].dns, "debug")
  77. self.assertEqual(all_configs[2].token, "provider_token2")
  78. self.assertEqual(all_configs[2].ipv4, ["provider2.example.com"])
  79. self.assertEqual(all_configs[2].ttl, 600)
  80. # Inherited from global - handle Python 2.7 unicode strings
  81. self.assertEqual(str(all_configs[2].ssl), "auto")
  82. # Inherited from global
  83. self.assertEqual(all_configs[2].cache, True)
  84. finally:
  85. # Restore original argv
  86. sys.argv = original_argv
  87. def test_v41_backward_compatibility(self):
  88. """Test that v4.1 format is backward compatible with existing
  89. single config"""
  90. # Create a traditional single config
  91. old_config = {
  92. "$schema": "https://ddns.newfuture.cc/schema/v4.0.json",
  93. "dns": "cloudflare",
  94. "id": "[email protected]",
  95. "token": "old_token",
  96. "ipv4": ["old.example.com"],
  97. "ssl": "auto",
  98. }
  99. old_file = self.create_test_file("old_format.json", old_config)
  100. # Mock sys.argv to control CLI parsing
  101. import sys
  102. original_argv = sys.argv
  103. try:
  104. # Set fake argv with our config file
  105. sys.argv = ["ddns", "-c", old_file]
  106. configs = load_configs("test", "1.0.0", "2025-01-01")
  107. # Should load exactly one config
  108. self.assertEqual(len(configs), 1)
  109. config = configs[0]
  110. self.assertEqual(config.dns, "cloudflare")
  111. self.assertEqual(config.id, "[email protected]")
  112. self.assertEqual(config.token, "old_token")
  113. self.assertEqual(str(config.ssl), "auto")
  114. finally:
  115. sys.argv = original_argv
  116. def test_v41_schema_reference(self):
  117. """Test that save_config uses v4.1 schema by default"""
  118. from ddns.config.file import save_config, load_config
  119. test_config = {"dns": "debug", "token": "test"}
  120. save_file = os.path.join(self.temp_dir, "new_config.json")
  121. # Save config
  122. result = save_config(save_file, test_config)
  123. self.assertTrue(result)
  124. # Load it back and check schema
  125. loaded = load_config(save_file)
  126. expected_schema = "https://ddns.newfuture.cc/schema/v4.1.json"
  127. self.assertEqual(loaded["$schema"], expected_schema)
  128. self.assertEqual(loaded["dns"], "debug")
  129. self.assertEqual(loaded["token"], "test")
  130. def test_v40_to_v41_compatibility(self):
  131. """Test v4.0 config is compatible with v4.1 processing"""
  132. from ddns.config.file import load_config
  133. # Create v4.0 schema config
  134. v40_config = {
  135. "$schema": "https://ddns.newfuture.cc/schema/v4.0.json",
  136. "dns": "cloudflare",
  137. "id": "[email protected]",
  138. "token": "v40_token",
  139. "ipv4": ["v40.example.com"],
  140. "ttl": 300,
  141. "ssl": True,
  142. "log": {"level": "DEBUG", "file": "test.log"},
  143. }
  144. v40_file = self.create_test_file("v40_config.json", v40_config)
  145. # Load and check it processes correctly
  146. loaded = load_config(v40_file)
  147. self.assertEqual(loaded["dns"], "cloudflare")
  148. self.assertEqual(loaded["id"], "[email protected]")
  149. self.assertEqual(loaded["token"], "v40_token")
  150. self.assertEqual(loaded["ipv4"], ["v40.example.com"])
  151. self.assertEqual(loaded["ttl"], 300)
  152. self.assertEqual(loaded["ssl"], True)
  153. # Check flattened log properties
  154. self.assertEqual(loaded["log_level"], "DEBUG")
  155. self.assertEqual(loaded["log_file"], "test.log")
  156. def test_v41_providers_complex_inheritance(self):
  157. """Test complex inheritance scenarios in v4.1 providers format"""
  158. from ddns.config.file import load_config
  159. # Create complex v4.1 config with nested objects
  160. complex_config = {
  161. "$schema": "https://ddns.newfuture.cc/schema/v4.1.json",
  162. "ssl": "auto",
  163. "ttl": 600,
  164. "cache": False,
  165. "log": {"level": "INFO", "format": "[%(levelname)s] %(message)s"},
  166. "providers": [
  167. {
  168. "provider": "cloudflare",
  169. "id": "[email protected]",
  170. "token": "cf_token",
  171. "ipv4": ["cf.example.com"],
  172. "ttl": 300, # Override global ttl
  173. "ssl": True, # Override global ssl
  174. },
  175. {
  176. "provider": "debug",
  177. "token": "debug_token",
  178. "ipv4": ["debug.example.com"],
  179. # Uses global ttl and ssl
  180. "log": {"level": "DEBUG"}, # Override log level
  181. },
  182. ],
  183. }
  184. complex_file = self.create_test_file("complex_v41.json", complex_config)
  185. # Load config
  186. configs = load_config(complex_file)
  187. self.assertEqual(len(configs), 2)
  188. # Test first provider inheritance and overrides
  189. cf_config = configs[0]
  190. self.assertEqual(cf_config["dns"], "cloudflare")
  191. self.assertEqual(cf_config["id"], "[email protected]")
  192. self.assertEqual(cf_config["token"], "cf_token")
  193. self.assertEqual(cf_config["ttl"], 300) # Overridden
  194. self.assertEqual(cf_config["ssl"], True) # Overridden
  195. self.assertEqual(cf_config["cache"], False) # Inherited
  196. self.assertEqual(cf_config["log_level"], "INFO") # Inherited
  197. self.assertEqual(cf_config["log_format"], "[%(levelname)s] %(message)s")
  198. # Test second provider inheritance
  199. debug_config = configs[1]
  200. self.assertEqual(debug_config["dns"], "debug")
  201. self.assertEqual(debug_config["token"], "debug_token")
  202. self.assertEqual(debug_config["ttl"], 600) # Inherited
  203. self.assertEqual(debug_config["ssl"], "auto") # Inherited
  204. self.assertEqual(debug_config["cache"], False) # Inherited
  205. self.assertEqual(debug_config["log_level"], "DEBUG") # Overridden
  206. self.assertEqual(debug_config["log_format"], "[%(levelname)s] %(message)s")
  207. def test_v41_providers_error_cases(self):
  208. """Test error handling in v4.1 providers format"""
  209. from ddns.config.file import load_config
  210. # Test providers without provider field
  211. invalid_config1 = {"providers": [{"id": "[email protected]", "token": "token"}]}
  212. invalid_file1 = self.create_test_file("invalid1.json", invalid_config1)
  213. with self.assertRaises(ValueError) as cm:
  214. load_config(invalid_file1)
  215. self.assertIn("provider missing provider field", str(cm.exception))
  216. # Test dns and providers conflict
  217. invalid_config2 = {"dns": "cloudflare", "providers": [{"provider": "debug", "token": "token"}]}
  218. invalid_file2 = self.create_test_file("invalid2.json", invalid_config2)
  219. with self.assertRaises(ValueError) as cm:
  220. load_config(invalid_file2)
  221. self.assertIn("providers and dns fields conflict", str(cm.exception))
  222. if __name__ == "__main__":
  223. unittest.main()