# SPDX-License-Identifier: GPL-3.0-or-later """Tray action layer: drives CLI + systemctl through an injectable runner.""" import json import subprocess import unittest from enodia_sentinel.config import Config from enodia_sentinel.tray import actions def _proc(rc=0, out="", err=""): return subprocess.CompletedProcess(args=[], returncode=rc, stdout=out, stderr=err) class _Recorder: """Runner that returns scripted results and records the commands seen.""" def __init__(self, results): self._results = list(results) self.calls = [] def __call__(self, cmd, timeout): self.calls.append(list(cmd)) return self._results.pop(0) class TestActions(unittest.TestCase): def test_dashboard_url_is_loopback_with_port(self): cfg = Config() cfg.web_port = 9999 self.assertEqual(actions.dashboard_url(cfg), "https://127.0.0.1:9999/") def test_fetch_status_parses_json_even_on_nonzero_exit(self): runner = _Recorder([_proc(rc=1, out=json.dumps({"running": True}))]) self.assertEqual(actions.fetch_status(runner), {"running": True}) def test_fetch_status_returns_none_on_garbage(self): runner = _Recorder([_proc(out="not json")]) self.assertIsNone(actions.fetch_status(runner)) def test_start_daemon_ok(self): runner = _Recorder([_proc(rc=0)]) res = actions.start_daemon(runner) self.assertTrue(res.ok) self.assertEqual(runner.calls[0], ["systemctl", "start", "enodia-sentinel.service"]) def test_stop_daemon_failure_carries_stderr_tail(self): runner = _Recorder([_proc(rc=1, err="line1\nAccess denied")]) res = actions.stop_daemon(runner) self.assertFalse(res.ok) self.assertIn("Access denied", res.message) def test_open_dashboard_starts_web_if_inactive(self): # is-active -> inactive, then start -> ok runner = _Recorder([_proc(out="inactive"), _proc(rc=0)]) opened = [] res = actions.open_dashboard(Config(), runner, opener=opened.append) self.assertTrue(res.ok) self.assertEqual(runner.calls[0], ["systemctl", "is-active", "enodia-sentinel-web.service"]) self.assertEqual(runner.calls[1], ["systemctl", "start", "enodia-sentinel-web.service"]) self.assertEqual(opened, ["https://127.0.0.1:8787/"]) def test_open_dashboard_skips_start_when_active(self): runner = _Recorder([_proc(out="active")]) opened = [] res = actions.open_dashboard(Config(), runner, opener=opened.append) self.assertTrue(res.ok) self.assertEqual(len(runner.calls), 1) self.assertEqual(opened, ["https://127.0.0.1:8787/"]) def test_run_check_counts_findings(self): runner = _Recorder([_proc(out=json.dumps([{"signature": "rsh"}, {"signature": "suid"}]))]) res = actions.run_check(runner) self.assertTrue(res.ok) self.assertIn("2", res.message) def test_run_check_no_findings(self): runner = _Recorder([_proc(out="[]")]) res = actions.run_check(runner) self.assertTrue(res.ok) self.assertIn("no findings", res.message.lower())