| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- # -*- coding:utf-8 -*-
- """
- Unit tests for ddns.scheduler.schtasks module
- @author: NewFuture
- """
- import platform
- from __init__ import patch, unittest
- from ddns.scheduler.schtasks import SchtasksScheduler
- from ddns.util.try_run import try_run
- class TestSchtasksScheduler(unittest.TestCase):
- """Test SchtasksScheduler functionality"""
- def setUp(self):
- """Set up test fixtures"""
- self.scheduler = SchtasksScheduler()
- def test_task_name_property(self):
- """Test task name property"""
- expected_name = "DDNS"
- self.assertEqual(self.scheduler.NAME, expected_name)
- @patch("ddns.scheduler.schtasks.try_run")
- def test_get_status_installed_enabled(self, mock_run_command):
- """Test get_status when task is installed and enabled"""
- # Mock XML output from schtasks query
- xml_output = """<?xml version=\"1.0\" encoding=\"UTF-16\"?>
- <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
- <Settings>
- <Enabled>true</Enabled>
- </Settings>
- <Triggers>
- <TimeTrigger>
- <Repetition>
- <Interval>PT5M</Interval>
- </Repetition>
- </TimeTrigger>
- </Triggers>
- <Actions>
- <Exec>
- <Command>pythonw.exe</Command>
- <Arguments>-m ddns</Arguments>
- </Exec>
- </Actions>
- </Task>"""
- mock_run_command.return_value = xml_output
- status = self.scheduler.get_status()
- expected_status = {"scheduler": "schtasks", "installed": True, "enabled": True, "interval": 5}
- # Check main fields
- self.assertEqual(status["scheduler"], expected_status["scheduler"])
- self.assertEqual(status["enabled"], expected_status["enabled"])
- self.assertEqual(status["interval"], expected_status["interval"])
- @patch("ddns.scheduler.schtasks.try_run")
- def test_get_status_not_installed(self, mock_run_command):
- """Test get_status when task is not installed"""
- # Mock try_run to return None (task not found)
- mock_run_command.return_value = None
- status = self.scheduler.get_status()
- expected_status = {"scheduler": "schtasks", "installed": False}
- # Check main fields - only scheduler and installed are included when task not found
- self.assertEqual(status["scheduler"], expected_status["scheduler"])
- self.assertEqual(status["installed"], expected_status["installed"])
- # When task is not installed, enabled and interval are not included in status
- @patch("ddns.scheduler.schtasks.try_run")
- def test_is_installed_true(self, mock_run_command):
- """Test is_installed returns True when task exists"""
- mock_run_command.return_value = "TaskName: DDNS\nStatus: Ready"
- result = self.scheduler.is_installed()
- self.assertTrue(result)
- @patch("ddns.scheduler.schtasks.try_run")
- def test_is_installed_false(self, mock_run_command):
- """Test is_installed returns False when task doesn't exist"""
- mock_run_command.return_value = None
- result = self.scheduler.is_installed()
- self.assertFalse(result)
- @patch.object(SchtasksScheduler, "_schtasks")
- def test_install_success(self, mock_schtasks):
- """Test successful installation"""
- mock_schtasks.return_value = True
- result = self.scheduler.install(5, {"dns": "debug", "ipv4": ["test.com"]})
- self.assertTrue(result)
- mock_schtasks.assert_called_once()
- @patch.object(SchtasksScheduler, "_schtasks")
- def test_uninstall_success(self, mock_schtasks):
- """Test successful uninstall"""
- mock_schtasks.return_value = True
- result = self.scheduler.uninstall()
- self.assertTrue(result)
- mock_schtasks.assert_called_once()
- @patch.object(SchtasksScheduler, "_schtasks")
- def test_enable_success(self, mock_schtasks):
- """Test successful enable"""
- mock_schtasks.return_value = True
- result = self.scheduler.enable()
- self.assertTrue(result)
- mock_schtasks.assert_called_once()
- @patch.object(SchtasksScheduler, "_schtasks")
- def test_disable_success(self, mock_schtasks):
- """Test successful disable"""
- mock_schtasks.return_value = True
- result = self.scheduler.disable()
- self.assertTrue(result)
- mock_schtasks.assert_called_once()
- def test_build_ddns_command(self):
- """Test _build_ddns_command functionality"""
- ddns_args = {"dns": "debug", "ipv4": ["test.example.com"], "config": ["config.json"], "ttl": 300}
- command = self.scheduler._build_ddns_command(ddns_args)
- self.assertIsInstance(command, list)
- command_str = " ".join(command)
- self.assertIn("python", command_str.lower())
- self.assertIn("-m", command)
- self.assertIn("ddns", command)
- self.assertIn("--dns", command)
- self.assertIn("debug", command)
- self.assertIn("test.example.com", command)
- def test_xml_extraction(self):
- """Test _extract_xml functionality"""
- xml_text = "<Settings><Enabled>true</Enabled></Settings>"
- result = self.scheduler._extract_xml(xml_text, "Enabled")
- self.assertEqual(result, "true")
- # Test non-existent tag
- result = self.scheduler._extract_xml(xml_text, "NonExistent")
- self.assertIsNone(result)
- @unittest.skipUnless(platform.system().lower() == "windows", "Windows-specific test")
- def test_real_schtasks_availability(self):
- """Test if schtasks is available on Windows systems"""
- if platform.system().lower() == "windows":
- # On Windows, test basic status call
- status = self.scheduler.get_status()
- self.assertIsInstance(status, dict)
- self.assertIn("scheduler", status)
- self.assertEqual(status["scheduler"], "schtasks")
- else:
- self.skipTest("Windows-specific test")
- @unittest.skipUnless(platform.system().lower() == "windows", "Windows-specific test")
- def test_real_schtasks_integration(self):
- """Test real schtasks integration with actual system calls"""
- if platform.system().lower() != "windows":
- self.skipTest("Windows-specific test")
- # Test real schtasks query for non-existent task
- status = self.scheduler.get_status()
- self.assertIsInstance(status, dict)
- self.assertEqual(status["scheduler"], "schtasks")
- self.assertIsInstance(status["installed"], bool)
- # Test schtasks help (safe read-only operation)
- # Note: We rely on the try_run function for actual subprocess calls
- # since it has proper timeout and error handling
- help_result = try_run(["schtasks", "/?"])
- if help_result:
- self.assertIn("schtasks", help_result.lower())
- @unittest.skipUnless(platform.system().lower() == "windows", "Windows-specific test")
- def test_real_scheduler_methods_safe(self):
- """Test real scheduler methods that don't modify system state"""
- if platform.system().lower() != "windows":
- self.skipTest("Windows-specific test")
- # Test is_installed (safe read-only operation)
- installed = self.scheduler.is_installed()
- self.assertIsInstance(installed, bool)
- # Test build command
- ddns_args = {"dns": "debug", "ipv4": ["test.example.com"]}
- command = self.scheduler._build_ddns_command(ddns_args)
- self.assertIsInstance(command, list)
- command_str = " ".join(command)
- self.assertIn("python", command_str.lower())
- # Test get status (safe read-only operation)
- status = self.scheduler.get_status()
- # Basic keys should always be present
- basic_required_keys = ["scheduler", "installed"]
- for key in basic_required_keys:
- self.assertIn(key, status)
- # If task is installed, additional keys should be present
- if status.get("installed", False):
- additional_keys = ["enabled", "interval"]
- for key in additional_keys:
- self.assertIn(key, status)
- # Test XML extraction utility
- test_xml = "<Settings><Enabled>true</Enabled></Settings>"
- enabled = self.scheduler._extract_xml(test_xml, "Enabled")
- self.assertEqual(enabled, "true")
- # No VBS generation anymore; ensure command building returns a list
- ddns_args = {"dns": "debug", "ipv4": ["test.example.com"]}
- cmd = self.scheduler._build_ddns_command(ddns_args)
- self.assertIsInstance(cmd, list)
- # Test enable/disable without actual installation (should handle gracefully)
- # These operations will fail if task doesn't exist, but should return boolean
- enable_result = self.scheduler.enable()
- self.assertIsInstance(enable_result, bool)
- disable_result = self.scheduler.disable()
- self.assertIsInstance(disable_result, bool)
- @unittest.skipUnless(platform.system().lower() == "windows", "Windows-specific integration test")
- def test_real_lifecycle_install_enable_disable_uninstall(self):
- """
- Real-life integration test: Full lifecycle of install → enable → disable → uninstall
- This test actually interacts with Windows Task Scheduler
- WARNING: This test modifies system state and should only run on test systems
- Test phases:
- 1. Clean state verification (uninstall if exists)
- 2. Install task and verify installation
- 3. Disable task and verify disabled state
- 4. Enable task and verify enabled state
- 5. Uninstall task and verify removal
- """
- if platform.system().lower() != "windows":
- self.skipTest("Windows-specific integration test")
- try:
- # PHASE 1: Ensure clean state - uninstall if exists
- if self.scheduler.is_installed():
- self.scheduler.uninstall()
- # Verify initial state - task should not exist
- initial_status = self.scheduler.get_status()
- self.assertEqual(initial_status["scheduler"], "schtasks")
- self.assertFalse(initial_status["installed"], "Task should not be installed initially")
- # PHASE 2: Install the task
- ddns_args = {
- "dns": "debug",
- "ipv4": ["test-integration.example.com"],
- "config": ["config.json"],
- "ttl": 300,
- }
- install_result = self.scheduler.install(interval=10, ddns_args=ddns_args)
- self.assertTrue(install_result, "Installation should succeed")
- # Verify installation
- post_install_status = self.scheduler.get_status()
- self.assertTrue(post_install_status["installed"], "Task should be installed after install()")
- self.assertTrue(post_install_status["enabled"], "Task should be enabled after install()")
- self.assertEqual(post_install_status["interval"], 10, "Task interval should match")
- # Command may be pythonw -m ddns ... or compiled exe path; both are acceptable
- self.assertTrue(
- (post_install_status.get("command") or "").lower().find("python") >= 0
- or (post_install_status.get("command") or "").lower().endswith(".exe")
- )
- # PHASE 3: Test disable functionality
- disable_result = self.scheduler.disable()
- self.assertTrue(disable_result, "Disable should succeed")
- # Verify disabled state
- post_disable_status = self.scheduler.get_status()
- self.assertTrue(post_disable_status["installed"], "Task should still be installed after disable")
- self.assertFalse(post_disable_status["enabled"], "Task should be disabled after disable()")
- # PHASE 4: Test enable functionality
- enable_result = self.scheduler.enable()
- self.assertTrue(enable_result, "Enable should succeed")
- # Verify enabled state
- post_enable_status = self.scheduler.get_status()
- self.assertTrue(post_enable_status["installed"], "Task should still be installed after enable")
- self.assertTrue(post_enable_status["enabled"], "Task should be enabled after enable()")
- # PHASE 5: Test uninstall functionality
- uninstall_result = self.scheduler.uninstall()
- self.assertTrue(uninstall_result, "Uninstall should succeed")
- # Verify final state - task should be gone
- final_status = self.scheduler.get_status()
- self.assertFalse(final_status["installed"], "Task should not be installed after uninstall()")
- # Double-check with is_installed()
- self.assertFalse(self.scheduler.is_installed(), "is_installed() should return False after uninstall")
- except Exception as e:
- # If test fails, ensure cleanup
- try:
- if self.scheduler.is_installed():
- self.scheduler.uninstall()
- except Exception:
- pass # Best effort cleanup
- raise e
- finally:
- try:
- if self.scheduler.is_installed():
- self.scheduler.uninstall()
- except Exception:
- pass # Best effort cleanup
- if __name__ == "__main__":
- unittest.main()
|