# -*- coding:utf-8 -*- # type: ignore[index,operator,assignment] """ Integration test for all config formats including v4.1 providers @author: GitHub Copilot """ from __future__ import unicode_literals from __init__ import unittest import tempfile import shutil import os import json from ddns.config import load_configs class TestAllConfigFormatsIntegration(unittest.TestCase): """Integration test for all supported config formats""" def setUp(self): """Set up test fixtures""" self.temp_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.temp_dir, ignore_errors=True) def create_test_file(self, filename, content): # type: (str, dict) -> str """Helper method to create a test file with given content""" file_path = os.path.join(self.temp_dir, filename) with open(file_path, "w") as f: if isinstance(content, dict): f.write(json.dumps(content, indent=2)) else: f.write(content) return file_path def test_all_config_formats_integration(self): """Test loading v4.1 config formats""" # Create single object config single_config = { "dns": "cloudflare", "id": "single@example.com", "token": "single_token", "ipv4": ["single.example.com"], "ssl": True, } single_file = self.create_test_file("single.json", single_config) # Create v4.1 providers format providers_config = { "$schema": "https://ddns.newfuture.cc/schema/v4.1.json", "ssl": "auto", "cache": True, "providers": [ {"provider": "debug", "token": "provider_token1", "ipv4": ["provider1.example.com"], "ttl": 300}, {"provider": "debug", "token": "provider_token2", "ipv4": ["provider2.example.com"], "ttl": 600}, ], } providers_file = self.create_test_file("providers.json", providers_config) # Mock sys.argv to control CLI parsing import sys original_argv = sys.argv try: # Set fake argv with our config files sys.argv = ["ddns", "-c", single_file, "-c", providers_file] # Load all configs all_configs = load_configs("test", "1.0.0", "2025-07-17") # Should have 3 total configs: 1 single + 2 from providers self.assertEqual(len(all_configs), 3) # Test single config self.assertEqual(all_configs[0].dns, "cloudflare") self.assertEqual(all_configs[0].id, "single@example.com") self.assertEqual(all_configs[0].ssl, True) # Test first provider config self.assertEqual(all_configs[1].dns, "debug") self.assertEqual(all_configs[1].token, "provider_token1") self.assertEqual(all_configs[1].ipv4, ["provider1.example.com"]) self.assertEqual(all_configs[1].ttl, 300) # Inherited from global - handle Python 2.7 unicode strings self.assertEqual(str(all_configs[1].ssl), "auto") # Inherited from global self.assertEqual(all_configs[1].cache, True) # Test second provider config self.assertEqual(all_configs[2].dns, "debug") self.assertEqual(all_configs[2].token, "provider_token2") self.assertEqual(all_configs[2].ipv4, ["provider2.example.com"]) self.assertEqual(all_configs[2].ttl, 600) # Inherited from global - handle Python 2.7 unicode strings self.assertEqual(str(all_configs[2].ssl), "auto") # Inherited from global self.assertEqual(all_configs[2].cache, True) finally: # Restore original argv sys.argv = original_argv def test_v41_backward_compatibility(self): """Test that v4.1 format is backward compatible with existing single config""" # Create a traditional single config old_config = { "$schema": "https://ddns.newfuture.cc/schema/v4.0.json", "dns": "cloudflare", "id": "old@example.com", "token": "old_token", "ipv4": ["old.example.com"], "ssl": "auto", } old_file = self.create_test_file("old_format.json", old_config) # Mock sys.argv to control CLI parsing import sys original_argv = sys.argv try: # Set fake argv with our config file sys.argv = ["ddns", "-c", old_file] configs = load_configs("test", "1.0.0", "2025-01-01") # Should load exactly one config self.assertEqual(len(configs), 1) config = configs[0] self.assertEqual(config.dns, "cloudflare") self.assertEqual(config.id, "old@example.com") self.assertEqual(config.token, "old_token") self.assertEqual(str(config.ssl), "auto") finally: sys.argv = original_argv def test_v41_schema_reference(self): """Test that save_config uses v4.1 schema by default""" from ddns.config.file import save_config, load_config test_config = {"dns": "debug", "token": "test"} save_file = os.path.join(self.temp_dir, "new_config.json") # Save config result = save_config(save_file, test_config) self.assertTrue(result) # Load it back and check schema loaded = load_config(save_file) expected_schema = "https://ddns.newfuture.cc/schema/v4.1.json" self.assertEqual(loaded["$schema"], expected_schema) self.assertEqual(loaded["dns"], "debug") self.assertEqual(loaded["token"], "test") def test_v40_to_v41_compatibility(self): """Test v4.0 config is compatible with v4.1 processing""" from ddns.config.file import load_config # Create v4.0 schema config v40_config = { "$schema": "https://ddns.newfuture.cc/schema/v4.0.json", "dns": "cloudflare", "id": "v40@example.com", "token": "v40_token", "ipv4": ["v40.example.com"], "ttl": 300, "ssl": True, "log": {"level": "DEBUG", "file": "test.log"}, } v40_file = self.create_test_file("v40_config.json", v40_config) # Load and check it processes correctly loaded = load_config(v40_file) self.assertEqual(loaded["dns"], "cloudflare") self.assertEqual(loaded["id"], "v40@example.com") self.assertEqual(loaded["token"], "v40_token") self.assertEqual(loaded["ipv4"], ["v40.example.com"]) self.assertEqual(loaded["ttl"], 300) self.assertEqual(loaded["ssl"], True) # Check flattened log properties self.assertEqual(loaded["log_level"], "DEBUG") self.assertEqual(loaded["log_file"], "test.log") def test_v41_providers_complex_inheritance(self): """Test complex inheritance scenarios in v4.1 providers format""" from ddns.config.file import load_config # Create complex v4.1 config with nested objects complex_config = { "$schema": "https://ddns.newfuture.cc/schema/v4.1.json", "ssl": "auto", "ttl": 600, "cache": False, "log": {"level": "INFO", "format": "[%(levelname)s] %(message)s"}, "providers": [ { "provider": "cloudflare", "id": "provider1@example.com", "token": "cf_token", "ipv4": ["cf.example.com"], "ttl": 300, # Override global ttl "ssl": True, # Override global ssl }, { "provider": "debug", "token": "debug_token", "ipv4": ["debug.example.com"], # Uses global ttl and ssl "log": {"level": "DEBUG"}, # Override log level }, ], } complex_file = self.create_test_file("complex_v41.json", complex_config) # Load config configs = load_config(complex_file) self.assertEqual(len(configs), 2) # Test first provider inheritance and overrides cf_config = configs[0] self.assertEqual(cf_config["dns"], "cloudflare") self.assertEqual(cf_config["id"], "provider1@example.com") self.assertEqual(cf_config["token"], "cf_token") self.assertEqual(cf_config["ttl"], 300) # Overridden self.assertEqual(cf_config["ssl"], True) # Overridden self.assertEqual(cf_config["cache"], False) # Inherited self.assertEqual(cf_config["log_level"], "INFO") # Inherited self.assertEqual(cf_config["log_format"], "[%(levelname)s] %(message)s") # Test second provider inheritance debug_config = configs[1] self.assertEqual(debug_config["dns"], "debug") self.assertEqual(debug_config["token"], "debug_token") self.assertEqual(debug_config["ttl"], 600) # Inherited self.assertEqual(debug_config["ssl"], "auto") # Inherited self.assertEqual(debug_config["cache"], False) # Inherited self.assertEqual(debug_config["log_level"], "DEBUG") # Overridden self.assertEqual(debug_config["log_format"], "[%(levelname)s] %(message)s") def test_v41_providers_error_cases(self): """Test error handling in v4.1 providers format""" from ddns.config.file import load_config # Test providers without provider field invalid_config1 = {"providers": [{"id": "missing_provider@example.com", "token": "token"}]} invalid_file1 = self.create_test_file("invalid1.json", invalid_config1) with self.assertRaises(ValueError) as cm: load_config(invalid_file1) self.assertIn("provider missing provider field", str(cm.exception)) # Test dns and providers conflict invalid_config2 = {"dns": "cloudflare", "providers": [{"provider": "debug", "token": "token"}]} invalid_file2 = self.create_test_file("invalid2.json", invalid_config2) with self.assertRaises(ValueError) as cm: load_config(invalid_file2) self.assertIn("providers and dns fields conflict", str(cm.exception)) if __name__ == "__main__": unittest.main()