78 lines
3.5 KiB
Python
78 lines
3.5 KiB
Python
"""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()
|