diff --git a/skillopt_sleep/scheduler.py b/skillopt_sleep/scheduler.py index 44dfa4b8..83f3f23e 100644 --- a/skillopt_sleep/scheduler.py +++ b/skillopt_sleep/scheduler.py @@ -95,7 +95,8 @@ def _list_win_tasks() -> List[str]: continue parts = line.split(",") if len(parts) > 0: - name = parts[0].strip('"') + # schtasks prints the full task path, e.g. "\SkillOpt-Sleep-...". + name = parts[0].strip('"').lstrip("\\") if name.startswith("SkillOpt-Sleep-"): tasks.append(name) return tasks diff --git a/tests/test_scheduler_windows.py b/tests/test_scheduler_windows.py index 151777a1..fb997ba9 100644 --- a/tests/test_scheduler_windows.py +++ b/tests/test_scheduler_windows.py @@ -74,3 +74,52 @@ class Proc: self.assertTrue(cmd[3].startswith("SkillOpt-Sleep-")) self.assertEqual(cmd[4], "/f") mock_remove.assert_called_once() + + +# ``schtasks /query /fo csv`` prints each task's full path, so a task created +# as ``SkillOpt-Sleep-`` in the root folder is listed with a leading +# backslash (#396). +_SCHTASKS_CSV = ( + '"TaskName","Next Run Time","Status"\r\n' + '"\\OneDrive Startup Task","N/A","Ready"\r\n' + '"\\SkillOpt-Sleep-0123456789abcdef0123","12/11/2026 3:17:00 AM","Ready"\r\n' + '"\\SkillOpt-Sleep-fedcba9876543210fedc","12/11/2026 3:17:00 AM","Ready"\r\n' +) +_SKILLOPT_TASKS = [ + "SkillOpt-Sleep-0123456789abcdef0123", + "SkillOpt-Sleep-fedcba9876543210fedc", +] + + +class TestSchedulerWindowsTaskList(unittest.TestCase): + @staticmethod + def _fake_run(calls): + def fake_run(cmd, **kwargs): + calls.append(cmd) + + class Proc: + returncode = 0 + stdout = _SCHTASKS_CSV if cmd[1] == "/query" else "SUCCESS" + stderr = "" + return Proc() + return fake_run + + @mock.patch("sys.platform", "win32") + @mock.patch("shutil.which", return_value="C:\\Windows\\System32\\schtasks.exe") + def test_list_scheduled_finds_tasks_listed_with_their_path(self, mock_which): + from skillopt_sleep.scheduler import list_scheduled + + with mock.patch("subprocess.run", side_effect=self._fake_run([])): + self.assertEqual(list_scheduled(), _SKILLOPT_TASKS) + + @mock.patch("sys.platform", "win32") + @mock.patch("shutil.which", return_value="C:\\Windows\\System32\\schtasks.exe") + def test_unschedule_all_deletes_every_skillopt_task(self, mock_which): + from skillopt_sleep.scheduler import unschedule + + calls = [] + with mock.patch("subprocess.run", side_effect=self._fake_run(calls)): + ok, _msg = unschedule(all_projects=True) + self.assertTrue(ok) + deleted = [cmd[3] for cmd in calls if cmd[1] == "/delete"] + self.assertEqual(deleted, _SKILLOPT_TASKS)