Expand TUI and package recovery planning
This commit is contained in:
parent
cab60cd633
commit
51d52b5229
10 changed files with 511 additions and 41 deletions
|
|
@ -10,7 +10,10 @@ from pathlib import Path
|
|||
from enodia_sentinel.cli import main
|
||||
from enodia_sentinel.config import Config
|
||||
from enodia_sentinel.tui import (
|
||||
TUI_COMMANDS,
|
||||
VIEWS,
|
||||
TuiState,
|
||||
_header_text,
|
||||
collect_model,
|
||||
complete_command,
|
||||
render_lines,
|
||||
|
|
@ -30,13 +33,18 @@ class TestTuiCore(unittest.TestCase):
|
|||
|
||||
def test_command_completion_unique_and_common_prefix(self):
|
||||
self.assertEqual(complete_command("inc"), "incidents")
|
||||
self.assertEqual(complete_command("resp"), "response")
|
||||
self.assertEqual(complete_command("r"), "r")
|
||||
self.assertEqual(complete_command("zzz"), "zzz")
|
||||
|
||||
def test_run_tui_command_switches_views_and_filters(self):
|
||||
state = TuiState()
|
||||
run_tui_command(state, "incidents")
|
||||
self.assertEqual(state.view, "incidents")
|
||||
for view in VIEWS:
|
||||
if view == "help":
|
||||
continue
|
||||
with self.subTest(view=view):
|
||||
run_tui_command(state, view)
|
||||
self.assertEqual(state.view, view)
|
||||
run_tui_command(state, "filter ssh")
|
||||
self.assertEqual(state.filter_text, "ssh")
|
||||
run_tui_command(state, "clear")
|
||||
|
|
@ -44,8 +52,14 @@ class TestTuiCore(unittest.TestCase):
|
|||
run_tui_command(state, "quit")
|
||||
self.assertTrue(state.should_quit)
|
||||
|
||||
def test_collect_model_and_render_status(self):
|
||||
def test_collect_model_contains_dashboard_parity_sections(self):
|
||||
model = collect_model(self.cfg)
|
||||
for key in (
|
||||
"status", "alerts", "incidents", "timeline", "posture", "integrity",
|
||||
"rules", "events", "response",
|
||||
):
|
||||
self.assertIn(key, model)
|
||||
|
||||
state = TuiState(model=model)
|
||||
lines = render_lines(state, width=80)
|
||||
self.assertTrue(any("Daemon:" in line for line in lines))
|
||||
|
|
@ -66,6 +80,92 @@ class TestTuiCore(unittest.TestCase):
|
|||
state.filter_text = "nomatch"
|
||||
self.assertEqual(render_lines(state), ["No rows match filter: nomatch"])
|
||||
|
||||
def test_render_posture_integrity_events_and_response_views(self):
|
||||
model = {
|
||||
"posture": {
|
||||
"count": 1,
|
||||
"counts": {"HIGH": 1},
|
||||
"findings": [{
|
||||
"severity": "HIGH",
|
||||
"sid": 100040,
|
||||
"signature": "ssh_root_login",
|
||||
"detail": "PermitRootLogin yes",
|
||||
}],
|
||||
},
|
||||
"timeline": [{
|
||||
"incident": {
|
||||
"id": "inc-1",
|
||||
"severity": "CRITICAL",
|
||||
"alert_count": 1,
|
||||
"signatures": ["reverse_shell"],
|
||||
},
|
||||
"timeline": [{
|
||||
"time": "2026-07-09T01:02:03-07:00",
|
||||
"severity": "CRITICAL",
|
||||
"signatures": ["reverse_shell"],
|
||||
"pids": [4242],
|
||||
}],
|
||||
}],
|
||||
"integrity": {
|
||||
"status": "review",
|
||||
"generated_at": 1.0,
|
||||
"checks": {"watchdog": "ok", "fim_baseline": "missing"},
|
||||
"watchdog": {
|
||||
"ok": True,
|
||||
"message": "heartbeat fresh",
|
||||
"heartbeat_age": 2,
|
||||
"max_age": 120,
|
||||
"daemon_running": True,
|
||||
},
|
||||
"anchors": {
|
||||
"fim_baseline": {"status": "missing", "path": "/tmp/fim.json"},
|
||||
"pkgdb": {"status": "ok", "fingerprint_prefix": "abc123"},
|
||||
"pacman": {"keyring_present": True, "siglevel_status": "ok"},
|
||||
},
|
||||
"sentinel_footprint": {"present": 2, "configured": 3, "missing": 1},
|
||||
"reconciliation": {
|
||||
"total": 1,
|
||||
"stale": 1,
|
||||
"stale_items": [{
|
||||
"kind": "fim",
|
||||
"target": "/etc/passwd",
|
||||
"reason": "ttl expired",
|
||||
}],
|
||||
},
|
||||
},
|
||||
"events": ["2026-07-09T01:02:03 event happened"],
|
||||
"response": [{
|
||||
"incident_id": "inc-1",
|
||||
"mode": "dry-run",
|
||||
"summary": {
|
||||
"severity": "CRITICAL",
|
||||
"signatures": ["reverse_shell"],
|
||||
"action_count": 1,
|
||||
},
|
||||
"actions": [{
|
||||
"id": "A001",
|
||||
"category": "evidence",
|
||||
"risk": "low",
|
||||
"title": "Freeze Sentinel evidence bundle",
|
||||
"command": ["tar", "-czf", "/tmp/evidence.tgz"],
|
||||
}],
|
||||
}],
|
||||
}
|
||||
|
||||
posture = render_lines(TuiState(view="posture", model=model), width=100)
|
||||
self.assertTrue(any("ssh_root_login" in line for line in posture))
|
||||
timeline = render_lines(TuiState(view="timeline", model=model), width=100)
|
||||
self.assertTrue(any("inc-1" in line for line in timeline))
|
||||
self.assertTrue(any("pids=4242" in line for line in timeline))
|
||||
integrity = render_lines(TuiState(view="integrity", model=model), width=100)
|
||||
self.assertTrue(any("Overall:" in line for line in integrity))
|
||||
self.assertTrue(any("ttl expired" in line for line in integrity))
|
||||
events = render_lines(TuiState(view="events", model=model), width=100)
|
||||
self.assertEqual(events, ["2026-07-09T01:02:03 event happened"])
|
||||
response = render_lines(TuiState(view="response", model=model), width=100)
|
||||
self.assertTrue(any("inc-1" in line for line in response))
|
||||
self.assertTrue(any("tar -czf /tmp/evidence.tgz" in line for line in response))
|
||||
|
||||
def test_cli_completion_outputs_scripts(self):
|
||||
for shell in ("bash", "zsh"):
|
||||
with self.subTest(shell=shell):
|
||||
|
|
@ -76,6 +176,21 @@ class TestTuiCore(unittest.TestCase):
|
|||
self.assertIn("enodia-sentinel", buf.getvalue())
|
||||
self.assertIn("tui", buf.getvalue())
|
||||
|
||||
def test_help_lists_all_commands(self):
|
||||
help_lines = render_lines(TuiState(view="help"), width=100)
|
||||
for command in TUI_COMMANDS:
|
||||
self.assertTrue(any(command in line for line in help_lines), command)
|
||||
|
||||
def test_header_scales_to_terminal_width(self):
|
||||
state = TuiState(view="integrity")
|
||||
for width in (60, 100, 180):
|
||||
with self.subTest(width=width):
|
||||
header = _header_text(state, width)
|
||||
self.assertLessEqual(len(header), width - 1)
|
||||
self.assertIn("view=integrity", _header_text(state, 60))
|
||||
self.assertIn("6 Int", _header_text(state, 100))
|
||||
self.assertIn("6 Integrity", _header_text(state, 180))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue