| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- # coding=utf-8
- """
- Unit tests for ddns.config.cli module
- @author: GitHub Copilot
- """
- from __init__ import unittest
- import sys
- import io
- from ddns.config.cli import load_config, str_bool, log_level # noqa: E402
- class TestCliConfig(unittest.TestCase):
- def setUp(self):
- encode = sys.stdout.encoding
- if encode is not None and encode.lower() != "utf-8" and hasattr(sys.stdout, "buffer"):
- # 兼容windows 和部分ASCII编码的老旧系统
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
- sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
- self.original_argv = sys.argv[:]
- def tearDown(self):
- sys.argv = self.original_argv
- def test_str_bool_function(self):
- """Test str_bool function with various inputs"""
- # Test boolean inputs
- self.assertTrue(str_bool(True))
- self.assertFalse(str_bool(False))
- self.assertFalse(str_bool(None))
- # Test string inputs that should be True
- for value in ["yes", "true", "t", "y", "1", "YES", "TRUE", "T", "Y"]:
- self.assertTrue(str_bool(value), "Value '{}' should be True".format(value))
- # Test string inputs that should be False
- for value in ["no", "false", "f", "n", "0", "NO", "FALSE", "F", "N"]:
- self.assertFalse(str_bool(value), "Value '{}' should be False".format(value))
- # Test string inputs that should remain unchanged
- self.assertEqual(str_bool("maybe"), "maybe")
- self.assertEqual(str_bool("auto"), "auto")
- self.assertEqual(str_bool(""), "")
- self.assertEqual(str_bool("off"), "off")
- self.assertEqual(str_bool("on"), "on")
- # Test non-string inputs
- self.assertTrue(str_bool(1))
- self.assertFalse(str_bool(0))
- self.assertTrue(str_bool([1, 2, 3]))
- self.assertFalse(str_bool([]))
- self.assertTrue(str_bool(1.0))
- self.assertFalse(str_bool(0.0))
- # Test edge cases
- self.assertEqual(str_bool(" true "), " true ")
- self.assertTrue(str_bool("True"))
- self.assertEqual(str_bool("path/to/file"), "path/to/file")
- self.assertEqual(str_bool("中文"), "中文")
- def test_log_level_function(self):
- """Test log_level function with various inputs"""
- self.assertEqual(log_level("DEBUG"), 10)
- self.assertEqual(log_level("INFO"), 20)
- self.assertEqual(log_level("WARNING"), 30)
- self.assertEqual(log_level("ERROR"), 40)
- self.assertEqual(log_level("CRITICAL"), 50)
- self.assertEqual(log_level("debug"), 10)
- self.assertEqual(log_level("critical"), 50)
- self.assertEqual(log_level("NOTSET"), 0)
- def test_load_config_basic_args(self):
- """Test load_config with basic command line arguments"""
- sys.argv = ["ddns", "--dns", "cloudflare", "--id", "[email protected]", "--token", "secret123", "--debug"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["dns"], "cloudflare")
- self.assertEqual(config["id"], "[email protected]")
- self.assertEqual(config["token"], "secret123")
- self.assertTrue(config["debug"])
- def test_load_config_with_arrays(self):
- """Test load_config with array arguments"""
- sys.argv = ["ddns", "--ipv4", "example.com", "test.com", "--proxy", "http://proxy1.com", "http://proxy2.com"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["ipv4"], ["example.com", "test.com"])
- self.assertEqual(config["proxy"], ["http://proxy1.com", "http://proxy2.com"])
- def test_load_config_ssl_comprehensive(self):
- """Test comprehensive SSL configuration options"""
- # Test --ssl without value (should default to True)
- sys.argv = ["ddns", "--ssl"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertTrue(config["ssl"])
- # Test --ssl with boolean-like values
- ssl_bool_cases = [
- ("true", True),
- ("false", False),
- ("yes", True),
- ("no", False),
- ("1", True),
- ("0", False),
- ("TRUE", True),
- ("FALSE", False),
- ("Y", True),
- ("N", False),
- ]
- for ssl_value, expected in ssl_bool_cases:
- sys.argv = ["ddns", "--ssl", ssl_value]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["ssl"], expected, "SSL value '{}' should be {}".format(ssl_value, expected))
- # Test --ssl with special values (auto, file paths)
- ssl_special_cases = [
- ("auto", "auto"),
- ("/path/to/cert.pem", "/path/to/cert.pem"),
- ("./cert.crt", "./cert.crt"),
- ("C:\\certs\\cert.pem", "C:\\certs\\cert.pem"),
- ("", ""),
- ("custom_value", "custom_value"),
- ]
- for ssl_value, expected in ssl_special_cases:
- sys.argv = ["ddns", "--ssl", ssl_value]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["ssl"], expected, "SSL value '{}' should be {}".format(ssl_value, expected))
- # Test --no-ssl flag
- sys.argv = ["ddns", "--no-ssl"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertFalse(config["ssl"])
- # Test --no-ssl overrides --ssl
- sys.argv = ["ddns", "--ssl", "true", "--no-ssl"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertFalse(config["ssl"])
- def test_load_config_log_settings(self):
- sys.argv = [
- "ddns",
- "--log-level",
- "DEBUG",
- "--log-file",
- "/var/log/ddns.log",
- "--log-format",
- "%(asctime)s %(message)s",
- ]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["log_level"], 10)
- self.assertEqual(config["log_file"], "/var/log/ddns.log")
- self.assertEqual(config["log_format"], "%(asctime)s %(message)s")
- sys.argv = [
- "ddns",
- "--log_level",
- "INFO",
- "--log_file",
- "/tmp/ddns.log",
- "--log_datefmt",
- "%Y-%m-%d %H:%M:%S",
- ]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["log_level"], 20)
- self.assertEqual(config["log_file"], "/tmp/ddns.log")
- self.assertEqual(config["log_datefmt"], "%Y-%m-%d %H:%M:%S")
- def test_load_config_system_exit_flags(self):
- sys.argv = ["ddns", "--version"]
- with self.assertRaises(SystemExit):
- load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- sys.argv = ["ddns", "--help"]
- with self.assertRaises(SystemExit):
- load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- def test_new_config_action_simple(self):
- """Test NewConfigAction behavior in a simple way"""
- from ddns.config.cli import NewConfigAction
- # Create an action and test its basic properties
- action = NewConfigAction(["--new-config"], "new_config", nargs="?")
- # Test that the action has the right configuration
- self.assertEqual(action.option_strings, ["--new-config"])
- self.assertEqual(action.dest, "new_config")
- self.assertEqual(action.nargs, "?")
- def test_load_config_other_flags(self):
- sys.argv = ["ddns", "--ttl", "300", "--line", "unicom"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["ttl"], 300)
- self.assertEqual(config["line"], "unicom")
- sys.argv = ["ddns", "--config", "/path/to/config.json"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["config"], ["/path/to/config.json"])
- def test_load_config_index_rules(self):
- sys.argv = [
- "ddns",
- "--index4",
- "url:http://ip.example.com",
- "interface:eth0",
- "--index6",
- "url:http://ipv6.example.com",
- ]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["index4"], ["url:http://ip.example.com", "interface:eth0"])
- self.assertEqual(config["index6"], ["url:http://ipv6.example.com"])
- def test_load_config_dns_providers(self):
- # 测试CLI中实际支持的DNS providers
- cli_supported_providers = [
- "51dns",
- "alidns",
- "aliesa",
- "callback",
- "cloudflare",
- "debug",
- "dnscom",
- "dnspod_com",
- "dnspod",
- "he",
- "huaweidns",
- "noip",
- "tencentcloud",
- ]
- for provider in cli_supported_providers:
- sys.argv = ["ddns", "--dns", provider]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["dns"], provider)
- def test_load_config_debug_mode(self):
- sys.argv = ["ddns", "--debug"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertTrue(config["debug"])
- self.assertEqual(config["log_level"], 10)
- self.assertFalse(config["cache"])
- def test_load_config_minimal_setup(self):
- sys.argv = ["ddns"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertNotIn("dns", config)
- self.assertNotIn("id", config)
- self.assertNotIn("token", config)
- self.assertFalse(config.get("debug", False))
- def test_extend_action_functionality(self):
- sys.argv = ["ddns", "--ipv4", "example.com", "test.com", "--ipv4", "another.com"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["ipv4"], ["example.com", "test.com", "another.com"])
- sys.argv = ["ddns", "--proxy", "proxy1.com", "--proxy", "proxy2.com", "proxy3.com", "--proxy", "proxy4.com"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["proxy"], ["proxy1.com", "proxy2.com", "proxy3.com", "proxy4.com"])
- sys.argv = ["ddns", "--ipv4", "--ipv6", "--proxy"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["ipv4"], [])
- self.assertEqual(config["ipv6"], [])
- self.assertEqual(config["proxy"], [])
- def test_load_config_combined_flags(self):
- sys.argv = [
- "ddns",
- "--dns",
- "cloudflare",
- "--id",
- "[email protected]",
- "--token",
- "secret123",
- "--ipv4",
- "example.com",
- "test.com",
- "--ipv6",
- "ipv6.example.com",
- "--ttl",
- "600",
- "--line",
- "default",
- "--cache",
- "true",
- "--ssl",
- "auto",
- "--proxy",
- "http://proxy.example.com:8080",
- "--log-level",
- "INFO",
- "--log-file",
- "/var/log/ddns.log",
- ]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["dns"], "cloudflare")
- self.assertEqual(config["id"], "[email protected]")
- self.assertEqual(config["token"], "secret123")
- self.assertEqual(config["ipv4"], ["example.com", "test.com"])
- self.assertEqual(config["ipv6"], ["ipv6.example.com"])
- self.assertEqual(config["ttl"], 600)
- self.assertEqual(config["line"], "default")
- self.assertTrue(config["cache"])
- self.assertEqual(config["ssl"], "auto")
- self.assertEqual(config["proxy"], ["http://proxy.example.com:8080"])
- self.assertEqual(config["log_level"], 20)
- self.assertEqual(config["log_file"], "/var/log/ddns.log")
- def test_load_config_invalid_inputs(self):
- sys.argv = ["ddns", "--dns", "invalid_provider"]
- with self.assertRaises(SystemExit):
- load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- sys.argv = ["ddns", "--ttl", "invalid_ttl"]
- with self.assertRaises(SystemExit):
- load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- sys.argv = ["ddns", "--log-level", "INVALID"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["log_level"], "Level INVALID")
- def test_load_config_value_types(self):
- # Test cache with different value types
- test_cases = [
- ("true", True),
- ("false", False),
- ("1", True),
- ("0", False),
- ("yes", True),
- ("no", False),
- ("/path/to/cache", "/path/to/cache"),
- ]
- for cache_value, expected in test_cases:
- sys.argv = ["ddns", "--cache", cache_value]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["cache"], expected)
- def test_load_config_special_characters(self):
- sys.argv = [
- "ddns",
- "--id",
- "[email protected]",
- "--token",
- "secret!@#$%^&*()",
- "--ipv4",
- "sub-domain.example.com",
- "--line",
- "移动",
- ]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["id"], "[email protected]")
- self.assertEqual(config["token"], "secret!@#$%^&*()")
- self.assertEqual(config["ipv4"], ["sub-domain.example.com"])
- self.assertEqual(config["line"], "移动")
- def test_load_config_cache_with_different_values(self):
- """Test load_config cache parameter with different value types"""
- test_cases = [
- ("true", True),
- ("false", False),
- ("1", True),
- ("0", False),
- ("yes", True),
- ("no", False),
- ("/path/to/cache", "/path/to/cache"),
- ("auto", "auto"),
- ]
- for cache_value, expected in test_cases:
- sys.argv = ["ddns", "--cache", cache_value]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["cache"], expected, "Cache value '{}' should be {}".format(cache_value, expected))
- def test_load_config_empty_values(self):
- """Test load_config with empty string values"""
- sys.argv = ["ddns", "--id", "", "--token", "", "--line", "", "--ssl", "", "--cache", ""]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["id"], "")
- self.assertEqual(config["token"], "")
- self.assertEqual(config["ssl"], "")
- self.assertEqual(config["cache"], "")
- # Test argument precedence
- sys.argv = ["ddns", "--dns", "cloudflare", "--dns", "alidns", "--ttl", "300", "--ttl", "600"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["dns"], "alidns")
- self.assertEqual(config["ttl"], 600)
- def test_load_config_paths_and_numeric(self):
- # Test various path formats
- sys.argv = [
- "ddns",
- "--config",
- "/absolute/path/config.json",
- "--log-file",
- "./relative/path/ddns.log",
- "--ssl",
- "~/.ssl/cert.pem",
- ]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["config"], ["/absolute/path/config.json"])
- self.assertEqual(config["log_file"], "./relative/path/ddns.log")
- self.assertEqual(config["ssl"], "~/.ssl/cert.pem")
- # Test multiple configs
- sys.argv = [
- "ddns",
- "--config",
- "/path/to/config1.json",
- "--config",
- "/path/to/config2.json",
- ]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["config"], ["/path/to/config1.json", "/path/to/config2.json"])
- # Test numeric strings
- sys.argv = ["ddns", "--id", "123456", "--token", "987654321", "--line", "100"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["id"], "123456")
- self.assertEqual(config["token"], "987654321")
- self.assertEqual(config["line"], "100")
- def test_extend_action_class(self):
- from ddns.config.cli import ExtendAction
- from argparse import Namespace, ArgumentParser
- parser = ArgumentParser()
- action = ExtendAction(option_strings=["--test"], dest="test")
- namespace = Namespace()
- # Test with single value
- action(parser, namespace, "value1", "--test")
- self.assertEqual(namespace.test, ["value1"])
- # Test with additional single value
- action(parser, namespace, "value2", "--test")
- self.assertEqual(namespace.test, ["value1", "value2"])
- # Test with list value
- action(parser, namespace, ["value3", "value4"], "--test")
- self.assertEqual(namespace.test, ["value1", "value2", "value3", "value4"])
- # Test with empty list
- namespace2 = Namespace()
- action(parser, namespace2, [], "--test")
- self.assertEqual(namespace2.test, [])
- action(parser, namespace2, "value1", "--test")
- self.assertEqual(namespace2.test, ["value1"])
- def test_comprehensive_scenario(self):
- sys.argv = [
- "ddns",
- "--dns",
- "cloudflare",
- "--id",
- "[email protected]",
- "--token",
- "cf_token_123456789",
- "--ipv4",
- "home.example.com",
- "work.example.com",
- "--ipv6",
- "home-ipv6.example.com",
- "--index4",
- "url:http://ip.example.com",
- "interface:eth0",
- "--index6",
- "url:http://ipv6.example.com",
- "--ttl",
- "300",
- "--line",
- "default",
- "--proxy",
- "http://proxy.corp.com:8080",
- "--cache",
- "/var/cache/ddns",
- "--ssl",
- "true",
- "--log-level",
- "INFO",
- "--log-file",
- "/var/log/ddns.log",
- "--log-format",
- "%(asctime)s [%(levelname)s] %(message)s",
- "--log-datefmt",
- "%Y-%m-%d %H:%M:%S",
- ]
- config = load_config("DDNS Client", "Dynamic DNS updater", "2.0.0", "2025-07-06")
- self.assertEqual(config["dns"], "cloudflare")
- self.assertEqual(config["id"], "[email protected]")
- self.assertEqual(config["token"], "cf_token_123456789")
- self.assertEqual(config["ipv4"], ["home.example.com", "work.example.com"])
- self.assertEqual(config["ipv6"], ["home-ipv6.example.com"])
- self.assertEqual(config["index4"], ["url:http://ip.example.com", "interface:eth0"])
- self.assertEqual(config["index6"], ["url:http://ipv6.example.com"])
- self.assertEqual(config["ttl"], 300)
- self.assertEqual(config["line"], "default")
- self.assertEqual(config["proxy"], ["http://proxy.corp.com:8080"])
- self.assertEqual(config["cache"], "/var/cache/ddns")
- self.assertEqual(config["ssl"], True)
- self.assertEqual(config["log_level"], 20)
- self.assertEqual(config["log_file"], "/var/log/ddns.log")
- self.assertEqual(config["log_format"], "%(asctime)s [%(levelname)s] %(message)s")
- self.assertEqual(config["log_datefmt"], "%Y-%m-%d %H:%M:%S")
- self.assertFalse(config["debug"])
- self.assertNotIn("new_config", config)
- def test_load_config_endpoint_parameter(self):
- # 测试endpoint参数,这在当前测试中缺失
- sys.argv = ["ddns", "--endpoint", "https://api.example.com/v1"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["endpoint"], "https://api.example.com/v1")
- def test_load_config_hidden_parameters(self):
- # 测试隐藏的日志参数变体(带点号和横线的)
- sys.argv = ["ddns", "--log.file", "/test/log.txt", "--log.level", "ERROR"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["log_file"], "/test/log.txt")
- self.assertEqual(config["log_level"], 40) # ERROR level
- sys.argv = ["ddns", "--log-file", "/test2/log.txt", "--log-level", "WARNING"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["log_file"], "/test2/log.txt")
- self.assertEqual(config["log_level"], 30) # WARNING level
- sys.argv = ["ddns", "--log.format", "custom format", "--log.datefmt", "%Y-%m-%d"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["log_format"], "custom format")
- self.assertEqual(config["log_datefmt"], "%Y-%m-%d")
- sys.argv = ["ddns", "--log-format", "custom format2", "--log-datefmt", "%H:%M:%S"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertEqual(config["log_format"], "custom format2")
- self.assertEqual(config["log_datefmt"], "%H:%M:%S")
- def test_log_level_with_integers(self):
- # 测试log_level函数处理整数输入
- self.assertEqual(log_level(10), "DEBUG")
- self.assertEqual(log_level(20), "INFO")
- self.assertEqual(log_level(50), "CRITICAL")
- def test_load_config_cache_none_in_debug(self):
- # 测试debug模式下cache为None时的行为
- sys.argv = ["ddns", "--debug", "--cache", "true"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertTrue(config["debug"])
- self.assertEqual(config["log_level"], 10) # DEBUG
- self.assertTrue(config["cache"]) # 明确设置的cache应该保持
- # 测试debug模式下没有设置cache时
- sys.argv = ["ddns", "--debug"]
- config = load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
- self.assertTrue(config["debug"])
- self.assertEqual(config["log_level"], 10) # DEBUG
- self.assertFalse(config["cache"]) # debug模式下默认禁用cache
- if __name__ == "__main__":
- unittest.main()
|