"""The CLI and TUI cross the same terminal-attached action interface.""" import argparse import runpy import shlex import subprocess import sys from pathlib import Path from unittest.mock import patch import pytest sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from fenris.control import MONITOR_HELPER, MonitorError, run_monitor from fenris.tui import FenrisTuiApp @pytest.mark.parametrize("uid", [0, 1000]) @pytest.mark.parametrize("args", [("enable", "--now"), ("disable", "--now"), ("collect",)]) def test_fixed_helper_and_terminal_attachment(uid, args): with patch("fenris.control.os.geteuid", return_value=uid), \ patch("fenris.control.subprocess.run", return_value=subprocess.CompletedProcess([], 0)) as run: run_monitor(*args) expected = [MONITOR_HELPER, *args] if uid: expected.insert(0, "pkexec") # Inherited stdin/out/err keep authentication on the user's terminal. # No frontend deadline can cut short a valid 90-second Collection run. run.assert_called_once_with(expected) @pytest.mark.parametrize("code", [1, 126, 127, -15]) def test_failure_is_not_retried_and_root_equivalent_preserves_arguments(code): args = ("baseline", "set", '{"model": "Drive $(whoami)", "tbw": 100}') with patch("fenris.control.os.geteuid", return_value=1000), \ patch("fenris.control.subprocess.run", return_value=subprocess.CompletedProcess([], code)) as run: with pytest.raises(MonitorError) as failure: run_monitor(*args) run.assert_called_once() assert failure.value.exit_code == (code if code > 0 else 128 - code) root_command = str(failure.value).split("run in your terminal: ")[1] assert shlex.split(root_command) == ["sudo", MONITOR_HELPER, *args] @pytest.mark.parametrize("uid,missing", [(0, MONITOR_HELPER), (1000, "pkexec")]) def test_missing_command_is_identified(uid, missing): with patch("fenris.control.os.geteuid", return_value=uid), \ patch("fenris.control.subprocess.run", side_effect=FileNotFoundError(2, "Missing", missing)): with pytest.raises(MonitorError, match="Command not found") as failure: run_monitor("collect") assert missing in str(failure.value) assert failure.value.exit_code == 127 assert ("sudo" in str(failure.value)) == bool(uid) def test_interrupt_reports_uncertain_outcome(): with patch("fenris.control.subprocess.run", side_effect=KeyboardInterrupt): with pytest.raises(MonitorError, match="Check fenris status") as failure: run_monitor("collect") assert failure.value.exit_code == 130 def test_cli_and_tui_show_the_same_failure(tmp_path, capsys): # The launcher may prepend an installed runtime while loading; isolate it. with patch.object(sys, "path", sys.path.copy()): cli = runpy.run_path(str(Path(__file__).parent.parent / "scripts" / "fenris")) with patch("fenris.control.os.geteuid", return_value=1000), \ patch("fenris.control.subprocess.run", return_value=subprocess.CompletedProcess([], 126)): with pytest.raises(SystemExit) as exit_info: cli["cmd_monitor_resume"](argparse.Namespace()) assert exit_info.value.code == 126 cli_message = capsys.readouterr().err.strip() app = FenrisTuiApp(store_path=tmp_path / "missing.db") with patch.object(app, "suspend"), patch.object(app, "_refresh") as refresh, \ patch.object(app, "notify") as notify: app.action_resume() notify.assert_called_once_with(cli_message, severity="error") refresh.assert_called_once()