135 lines
5.8 KiB
Python
135 lines
5.8 KiB
Python
import contextlib
|
|
import io
|
|
import unittest
|
|
from http.server import BaseHTTPRequestHandler
|
|
|
|
from app.webui import DASHBOARD, _WebHTTPServer
|
|
|
|
|
|
class WebUITests(unittest.TestCase):
|
|
def test_dashboard_is_english_and_has_mikrosuricata_sections(self):
|
|
self.assertIn('<html lang="en"', DASHBOARD)
|
|
self.assertIn('MikroSuricata', DASHBOARD)
|
|
self.assertNotIn('Sentinel', DASHBOARD)
|
|
for text in (
|
|
"Overview",
|
|
"Live Sessions",
|
|
"Security incidents",
|
|
"RouterOS blocks",
|
|
"Reports",
|
|
"Custom Suricata signatures",
|
|
"Signature Feeds",
|
|
"Providers and rulesets",
|
|
"Traffic history",
|
|
):
|
|
self.assertIn(text, DASHBOARD)
|
|
for polish_text in ("Ładowanie", "Brak alertów", "Źródło", "Blokady RouterOS"):
|
|
self.assertNotIn(polish_text, DASHBOARD)
|
|
|
|
def test_dashboard_uses_external_static_assets(self):
|
|
self.assertIn('/static/libs/tailwindcss/tailwind.min.css', DASHBOARD)
|
|
self.assertIn('/static/css/app.css', DASHBOARD)
|
|
self.assertIn('/static/js/charts.js', DASHBOARD)
|
|
self.assertIn('/static/js/app.js', DASHBOARD)
|
|
self.assertNotIn('<style>', DASHBOARD)
|
|
self.assertNotIn('<script>', DASHBOARD)
|
|
|
|
def test_dashboard_has_primary_sections(self):
|
|
for section in ("overview", "live", "security", "intelligence", "blocks", "reports", "feeds", "rules", "system"):
|
|
self.assertIn(f'data-view="{section}"', DASHBOARD)
|
|
self.assertIn(f'id="view-{section}"', DASHBOARD)
|
|
|
|
def test_reports_expose_time_state_and_download(self):
|
|
for element_id in ("reportWindowBadge", "reportState", "refreshReports", "downloadReport"):
|
|
self.assertIn(f'id="{element_id}"', DASHBOARD)
|
|
|
|
def test_overview_has_persistent_throughput_and_event_charts(self):
|
|
self.assertIn('Traffic throughput', DASHBOARD)
|
|
self.assertIn('id="throughputChart"', DASHBOARD)
|
|
self.assertIn('legend-amber', DASHBOARD)
|
|
self.assertIn('Events & alerts', DASHBOARD)
|
|
self.assertIn('id="trafficChart"', DASHBOARD)
|
|
self.assertIn('id="topClients"', DASHBOARD)
|
|
|
|
def test_intelligence_stages_column_has_dedicated_width_hook(self):
|
|
self.assertIn('<th class="stages-col">Stages</th>', DASHBOARD)
|
|
|
|
def test_security_uses_compact_internal_tabs(self):
|
|
for tab in ("incidents", "analytics", "telemetry"):
|
|
self.assertIn(f'data-subtab-group="security" data-subtab="{tab}"', DASHBOARD)
|
|
self.assertIn(f'data-subtab-panel="security:{tab}"', DASHBOARD)
|
|
|
|
def test_intelligence_uses_compact_internal_tabs(self):
|
|
for tab in ("incidents", "assets", "threat-intel", "forensics"):
|
|
self.assertIn(f'data-subtab-group="intelligence" data-subtab="{tab}"', DASHBOARD)
|
|
self.assertIn(f'data-subtab-panel="intelligence:{tab}"', DASHBOARD)
|
|
self.assertIn("Asset intelligence", DASHBOARD)
|
|
self.assertIn("Threat intelligence repository", DASHBOARD)
|
|
self.assertIn("Forensic PCAP ring", DASHBOARD)
|
|
|
|
def test_system_has_dedicated_redis_status_panel(self):
|
|
self.assertIn('<h2>Redis</h2>', DASHBOARD)
|
|
self.assertIn('id="redisStateBadge"', DASHBOARD)
|
|
self.assertIn('id="redisStatus"', DASHBOARD)
|
|
|
|
def test_live_stream_is_opt_in_and_bounded(self):
|
|
self.assertIn('Continuous streaming is off by default', DASHBOARD)
|
|
self.assertIn('id="toggleLive"', DASHBOARD)
|
|
self.assertIn('id="liveLimit"', DASHBOARD)
|
|
self.assertIn('<option value="200" selected>200 rows</option>', DASHBOARD)
|
|
|
|
def test_dashboard_uses_modal_login_and_persistent_summary_ui(self):
|
|
self.assertIn('id="authModal"', DASHBOARD)
|
|
self.assertIn('id="loginForm"', DASHBOARD)
|
|
self.assertIn('id="snapshotMeta"', DASHBOARD)
|
|
self.assertIn('id="fingerprintRank"', DASHBOARD)
|
|
self.assertIn('id="mobileMenu"', DASHBOARD)
|
|
self.assertNotIn('id="adminToken"', DASHBOARD)
|
|
self.assertNotIn('id="saveToken"', DASHBOARD)
|
|
|
|
def test_signature_feed_ui_supports_bulk_queue(self):
|
|
for element_id in (
|
|
"selectVisibleSources",
|
|
"selectAllFreeSources",
|
|
"clearSourceSelection",
|
|
"queueSelectedSources",
|
|
"sourceQueueStatus",
|
|
"feedRuleCount",
|
|
"ruleUpdateSchedule",
|
|
):
|
|
self.assertIn(f'id="{element_id}"', DASHBOARD)
|
|
|
|
def test_merged_rules_ui_has_explicit_counters(self):
|
|
self.assertIn('id="mergedRuleCount"', DASHBOARD)
|
|
self.assertIn('id="mergedRuleMatchCount"', DASHBOARD)
|
|
|
|
def test_merged_rules_panel_is_collapsed_by_default(self):
|
|
self.assertIn('<details id="mergedRulesPanel" class="panel mt-4 collapsible-panel">', DASHBOARD)
|
|
self.assertNotIn('<details id="mergedRulesPanel" class="panel mt-4 collapsible-panel" open', DASHBOARD)
|
|
|
|
def test_autonomous_ids_operations_are_exposed_in_ui(self):
|
|
for element_id in (
|
|
"ruleIntelRows", "ruleSnapshotRows", "createRuleSnapshot",
|
|
"backupRows", "auditRows", "createBackup",
|
|
):
|
|
self.assertIn(f'id="{element_id}"', DASHBOARD)
|
|
self.assertIn("MITRE ATT&CK", DASHBOARD)
|
|
self.assertIn("Adaptive rule intelligence", DASHBOARD)
|
|
|
|
def test_http_server_suppresses_normal_client_disconnect_traceback(self):
|
|
server = _WebHTTPServer(("127.0.0.1", 0), BaseHTTPRequestHandler)
|
|
stderr = io.StringIO()
|
|
try:
|
|
with contextlib.redirect_stderr(stderr):
|
|
try:
|
|
raise BrokenPipeError(32, "Broken pipe")
|
|
except BrokenPipeError:
|
|
server.handle_error(None, ("127.0.0.1", 12345))
|
|
self.assertEqual("", stderr.getvalue())
|
|
finally:
|
|
server.server_close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|