1
0

test_config_cli_task.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. # coding=utf-8
  2. """
  3. Unit tests for ddns task subcommand functionality
  4. @author: GitHub Copilot
  5. """
  6. from __init__ import unittest, patch
  7. import sys
  8. import io
  9. from ddns.config.cli import load_config
  10. class TestTaskSubcommand(unittest.TestCase):
  11. """Test task subcommand functionality"""
  12. def setUp(self):
  13. encode = sys.stdout.encoding
  14. if encode is not None and encode.lower() != "utf-8" and hasattr(sys.stdout, "buffer"):
  15. # 兼容windows 和部分ASCII编码的老旧系统
  16. sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
  17. sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
  18. self.original_argv = sys.argv[:]
  19. # Initialize test attributes for captured arguments
  20. self.captured_basic_status_args = None
  21. self.captured_basic_install_args = None
  22. self.captured_enable_args = None
  23. self.captured_disable_args = None
  24. self.captured_delete_args = None
  25. self.captured_force_args = None
  26. self.captured_args = None
  27. self.captured_status_args = None
  28. def tearDown(self):
  29. sys.argv = self.original_argv
  30. def test_task_subcommand_help(self):
  31. """Test task subcommand help parsing"""
  32. sys.argv = ["ddns", "task", "--help"]
  33. # Test that SystemExit is raised with help
  34. with self.assertRaises(SystemExit) as cm:
  35. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  36. # Help should exit with code 0
  37. self.assertEqual(cm.exception.code, 0)
  38. def test_task_subcommand_status(self):
  39. """Test task subcommand status parsing"""
  40. sys.argv = ["ddns", "task", "--status"]
  41. # Mock the scheduler.get_status function to avoid actual system operations
  42. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  43. mock_scheduler = mock_get_scheduler.return_value
  44. mock_scheduler.get_status.return_value = {
  45. "installed": True,
  46. "scheduler": "schtasks",
  47. "interval": 5,
  48. "enabled": True,
  49. "command": "test command",
  50. }
  51. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  52. def capture_args(args):
  53. self.captured_basic_status_args = args
  54. mock_handle.side_effect = capture_args
  55. try:
  56. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  57. except SystemExit:
  58. pass
  59. # Verify the captured arguments
  60. args = self.captured_basic_status_args
  61. self.assertIsNotNone(args)
  62. if args is not None: # Type checker satisfaction
  63. self.assertTrue(args["status"])
  64. def test_task_subcommand_install(self):
  65. """Test task subcommand install parsing"""
  66. sys.argv = ["ddns", "task", "--install", "10", "--config", "test.json"]
  67. # Mock the scheduler.install function to avoid actual system operations
  68. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  69. mock_scheduler = mock_get_scheduler.return_value
  70. mock_scheduler.install.return_value = True
  71. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  72. def capture_args(args):
  73. self.captured_basic_install_args = args
  74. mock_handle.side_effect = capture_args
  75. try:
  76. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  77. except SystemExit:
  78. pass
  79. # Verify the captured arguments
  80. args = self.captured_basic_install_args
  81. self.assertIsNotNone(args)
  82. if args is not None: # Type checker satisfaction
  83. self.assertEqual(args["install"], 10)
  84. self.assertEqual(args["config"], ["test.json"])
  85. def test_task_subcommand_enable(self):
  86. """Test task subcommand enable parsing"""
  87. sys.argv = ["ddns", "task", "--enable"]
  88. # Mock the scheduler.enable function
  89. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  90. mock_scheduler = mock_get_scheduler.return_value
  91. mock_scheduler.enable.return_value = True
  92. mock_scheduler.is_installed.return_value = True
  93. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  94. def capture_args(args):
  95. self.captured_enable_args = args
  96. mock_handle.side_effect = capture_args
  97. try:
  98. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  99. except SystemExit:
  100. pass
  101. # Verify the captured arguments
  102. args = self.captured_enable_args
  103. self.assertIsNotNone(args)
  104. if args is not None: # Type checker satisfaction
  105. self.assertTrue(args["enable"])
  106. def test_task_subcommand_disable(self):
  107. """Test task subcommand disable parsing"""
  108. sys.argv = ["ddns", "task", "--disable"]
  109. # Mock the scheduler.disable function
  110. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  111. mock_scheduler = mock_get_scheduler.return_value
  112. mock_scheduler.disable.return_value = True
  113. mock_scheduler.is_installed.return_value = True
  114. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  115. def capture_args(args):
  116. self.captured_disable_args = args
  117. mock_handle.side_effect = capture_args
  118. try:
  119. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  120. except SystemExit:
  121. pass
  122. # Verify the captured arguments
  123. args = self.captured_disable_args
  124. self.assertIsNotNone(args)
  125. if args is not None: # Type checker satisfaction
  126. self.assertTrue(args["disable"])
  127. def test_task_subcommand_delete(self):
  128. """Test task subcommand delete/uninstall parsing"""
  129. sys.argv = ["ddns", "task", "--uninstall"]
  130. # Mock the scheduler operations to avoid actual system operations
  131. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  132. mock_scheduler = mock_get_scheduler.return_value
  133. mock_scheduler.uninstall.return_value = True
  134. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  135. def capture_args(args):
  136. self.captured_delete_args = args
  137. mock_handle.side_effect = capture_args
  138. try:
  139. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  140. except SystemExit:
  141. pass
  142. # Verify the captured arguments
  143. args = self.captured_delete_args
  144. self.assertIsNotNone(args)
  145. if args is not None: # Type checker satisfaction
  146. self.assertTrue(args["uninstall"])
  147. def test_task_subcommand_force_install(self):
  148. """Test task subcommand install parsing with custom interval"""
  149. sys.argv = ["ddns", "task", "--install", "5"]
  150. # Mock the scheduler.install function
  151. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  152. mock_scheduler = mock_get_scheduler.return_value
  153. mock_scheduler.install.return_value = True
  154. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  155. def capture_args(args):
  156. self.captured_force_args = args
  157. mock_handle.side_effect = capture_args
  158. try:
  159. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  160. except SystemExit:
  161. pass
  162. # Verify the captured arguments
  163. args = self.captured_force_args
  164. self.assertIsNotNone(args)
  165. if args is not None: # Type checker satisfaction
  166. self.assertEqual(args["install"], 5)
  167. def test_task_subcommand_with_ddns_args(self):
  168. """Test task subcommand accepts same arguments as main DDNS command"""
  169. sys.argv = [
  170. "ddns",
  171. "task",
  172. "--install",
  173. "10",
  174. "--config",
  175. "test.json",
  176. "--proxy",
  177. "http://proxy.example.com:8080",
  178. "--debug",
  179. "--ttl",
  180. "300",
  181. ]
  182. # Mock the scheduler.install function to avoid actual system operations
  183. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  184. mock_scheduler = mock_get_scheduler.return_value
  185. mock_scheduler.install.return_value = True
  186. # Mock _handle_task_command directly to capture its behavior
  187. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  188. def capture_args(args):
  189. # Save the args for verification
  190. self.captured_args = args
  191. mock_handle.side_effect = capture_args
  192. try:
  193. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  194. except SystemExit:
  195. # Expected due to task command execution
  196. pass
  197. # Verify that _handle_task_command was called
  198. mock_handle.assert_called_once()
  199. # Verify the captured arguments
  200. args = self.captured_args
  201. self.assertIsNotNone(args)
  202. if args is not None: # Type checker satisfaction
  203. self.assertEqual(args["install"], 10)
  204. self.assertEqual(args["config"], ["test.json"])
  205. self.assertEqual(args["proxy"], ["http://proxy.example.com:8080"])
  206. self.assertTrue(args["debug"])
  207. self.assertEqual(args["ttl"], 300)
  208. def test_task_subcommand_with_provider_args(self):
  209. """Test task subcommand with provider-specific arguments"""
  210. sys.argv = [
  211. "ddns",
  212. "task",
  213. "--install",
  214. "5",
  215. "--config",
  216. "cloudflare.json",
  217. "--dns",
  218. "cloudflare",
  219. "--token",
  220. "test-token",
  221. "--id",
  222. "test-id",
  223. ]
  224. # Mock the scheduler.install function to avoid actual system operations
  225. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  226. mock_scheduler = mock_get_scheduler.return_value
  227. mock_scheduler.install.return_value = True
  228. with patch("ddns.config.cli.sys.exit"):
  229. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  230. # Verify that install was called with correct arguments
  231. mock_scheduler.install.assert_called_once()
  232. call_args = mock_scheduler.install.call_args
  233. # Check interval (first positional argument)
  234. self.assertEqual(call_args[0][0], 5)
  235. # Check ddns_args contains provider arguments (second positional argument)
  236. ddns_args = call_args[0][1]
  237. self.assertEqual(ddns_args["dns"], "cloudflare")
  238. self.assertEqual(ddns_args["token"], "test-token")
  239. self.assertEqual(ddns_args["id"], "test-id")
  240. self.assertEqual(ddns_args["config"], ["cloudflare.json"])
  241. def test_task_subcommand_status_with_ddns_args(self):
  242. """Test task status command doesn't need ddns_args but accepts other params"""
  243. sys.argv = ["ddns", "task", "--status", "--config", "test.json", "--debug"]
  244. # Mock the scheduler.get_status function to avoid actual system operations
  245. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  246. mock_scheduler = mock_get_scheduler.return_value
  247. mock_scheduler.get_status.return_value = {
  248. "installed": True,
  249. "scheduler": "schtasks",
  250. "interval": 5,
  251. "enabled": True,
  252. "command": "test command",
  253. }
  254. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  255. def capture_args(args):
  256. self.captured_status_args = args
  257. mock_handle.side_effect = capture_args
  258. try:
  259. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  260. except SystemExit:
  261. pass
  262. # Verify the captured arguments include debug flag
  263. args = self.captured_status_args
  264. self.assertIsNotNone(args)
  265. if args is not None: # Type checker satisfaction
  266. self.assertTrue(args["status"])
  267. self.assertTrue(args["debug"])
  268. self.assertEqual(args["config"], ["test.json"])
  269. def test_task_subcommand_scheduler_default(self):
  270. """Test task subcommand scheduler default value"""
  271. sys.argv = ["ddns", "task", "--status"]
  272. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  273. mock_scheduler = mock_get_scheduler.return_value
  274. mock_scheduler.get_status.return_value = {"installed": False, "scheduler": "auto"}
  275. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  276. captured_args = [None] # Use list to make it mutable in Python 2
  277. def capture_args(args):
  278. captured_args[0] = args
  279. mock_handle.side_effect = capture_args
  280. try:
  281. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  282. except SystemExit:
  283. pass
  284. self.assertIsNotNone(captured_args[0])
  285. if captured_args[0] is not None:
  286. self.assertEqual(captured_args[0].get("scheduler"), "auto")
  287. def test_task_subcommand_scheduler_explicit_values(self):
  288. """Test task subcommand scheduler with explicit values"""
  289. test_schedulers = ["auto", "systemd", "cron", "launchd", "schtasks"]
  290. for scheduler in test_schedulers:
  291. try:
  292. sys.argv = ["ddns", "task", "--status", "--scheduler", scheduler]
  293. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  294. mock_scheduler = mock_get_scheduler.return_value
  295. mock_scheduler.get_status.return_value = {"installed": False, "scheduler": scheduler}
  296. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  297. captured_args = [None] # Use list to make it mutable in Python 2
  298. def capture_args(args):
  299. captured_args[0] = args
  300. mock_handle.side_effect = capture_args
  301. try:
  302. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  303. except SystemExit:
  304. pass
  305. self.assertIsNotNone(
  306. captured_args[0], "Failed to capture args for scheduler: {}".format(scheduler)
  307. )
  308. if captured_args[0] is not None:
  309. self.assertEqual(
  310. captured_args[0].get("scheduler"),
  311. scheduler,
  312. "Expected scheduler {} but got {}".format(scheduler, captured_args[0].get("scheduler")),
  313. )
  314. except Exception as e:
  315. self.fail("Failed for scheduler {}: {}".format(scheduler, e))
  316. def test_task_subcommand_scheduler_with_install(self):
  317. """Test task subcommand scheduler parameter with install command"""
  318. sys.argv = ["ddns", "task", "--install", "15", "--scheduler", "cron", "--dns", "debug"]
  319. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  320. mock_scheduler = mock_get_scheduler.return_value
  321. mock_scheduler.install.return_value = True
  322. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  323. captured_args = [None] # Use list to make it mutable in Python 2
  324. def capture_args(args):
  325. captured_args[0] = args
  326. mock_handle.side_effect = capture_args
  327. try:
  328. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  329. except SystemExit:
  330. pass
  331. self.assertIsNotNone(captured_args[0])
  332. if captured_args[0] is not None:
  333. self.assertEqual(captured_args[0].get("scheduler"), "cron")
  334. self.assertEqual(captured_args[0].get("install"), 15)
  335. self.assertEqual(captured_args[0].get("dns"), "debug")
  336. def test_task_subcommand_scheduler_excluded_from_ddns_args(self):
  337. """Test scheduler parameter is excluded from ddns_args in _handle_task_command"""
  338. sys.argv = ["ddns", "task", "--install", "10", "--scheduler", "systemd", "--dns", "debug", "--id", "test-id"]
  339. with patch("ddns.config.cli.get_scheduler") as mock_get_scheduler:
  340. mock_scheduler = mock_get_scheduler.return_value
  341. mock_scheduler.install.return_value = True
  342. with patch("ddns.config.cli._handle_task_command") as mock_handle:
  343. args = [None] # Use list to make it mutable in Python 2
  344. def capture_args(cargs):
  345. args[0] = cargs
  346. mock_handle.side_effect = capture_args
  347. try:
  348. load_config("Test DDNS", "Test doc", "1.0.0", "2025-07-04")
  349. except SystemExit:
  350. pass
  351. self.assertIsNotNone(args[0])
  352. # Verify scheduler is in args
  353. self.assertEqual(args[0].get("scheduler"), "systemd") # type: ignore
  354. # Simulate what _handle_task_command does with ddns_args
  355. exclude = {"status", "install", "uninstall", "enable", "disable", "command", "scheduler"}
  356. options = {k: v for k, v in args[0].items() if k not in exclude and v is not None} # type: ignore
  357. # Verify scheduler is excluded from ddns_args but other params are included
  358. self.assertNotIn("scheduler", options)
  359. self.assertNotIn("install", options) # Also excluded
  360. self.assertIn("dns", options)
  361. self.assertIn("id", options)
  362. self.assertEqual(options["dns"], "debug")
  363. self.assertEqual(options["id"], "test-id")
  364. if __name__ == "__main__":
  365. unittest.main()