diff --git a/api/schema.py b/api/schema.py index 0688dd9..d36bf54 100644 --- a/api/schema.py +++ b/api/schema.py @@ -28,13 +28,29 @@ def is_wecker_ringing() -> bool: return False +def _project_root() -> Path: + return Path(__file__).parent.parent.absolute() + + def _default_command() -> str: - """Return the default shell command to run wecker.py.""" - project_root = Path(__file__).parent.parent.absolute() + """Return the default shell command to run wecker.py from crontab.""" + project_root = _project_root() python_exec = sys.executable return f"cd {project_root} && {python_exec} wecker.py >> wecker.log 2>&1" +def _start_wecker_process() -> subprocess.Popen: + """Start wecker.py without invoking a shell.""" + project_root = _project_root() + return subprocess.Popen( + [sys.executable, str(project_root / "wecker.py")], + cwd=project_root, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True, + ) + + @strawberry.type class Alarm: id: str @@ -106,8 +122,7 @@ class Mutation: Returns True if started, False if already ringing.""" if is_wecker_ringing(): return False - cmd = _default_command() - subprocess.Popen(cmd, shell=True) + _start_wecker_process() return True @strawberry.field diff --git a/tests/test_api.py b/tests/test_api.py index 80c7069..0950517 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -233,8 +233,10 @@ def test_start_ringing_starts_process_when_not_ringing(): assert result is True mock_popen.assert_called_once() args, kwargs = mock_popen.call_args - assert "wecker.py" in args[0] - assert kwargs.get("shell") is True + assert any("wecker.py" in str(arg) for arg in args[0]) + assert kwargs.get("shell") is not True + assert kwargs.get("cwd") is not None + assert kwargs.get("start_new_session") is True def test_start_ringing_ignores_when_already_ringing():