73 lines
2.8 KiB
Python
73 lines
2.8 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Emission-based coverage: every built-in SID has a fixture or drill.
|
|
|
|
Event-rule SIDs are covered by JSON fixtures under tests/fixtures/sids/
|
|
(replayable by operators via `enodia-sentinel rules test <file>`); the other
|
|
engines are covered by drills in tests/sid_drills.py. This module replays
|
|
and runs all of them for real and asserts the emitted sids match the
|
|
registry in enodia_sentinel.sids.
|
|
"""
|
|
import json
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from enodia_sentinel import ruleops, sids
|
|
from enodia_sentinel.config import Config
|
|
from tests.sid_drills import DRILLS
|
|
|
|
FIXTURE_DIR = Path(__file__).resolve().parent / "fixtures" / "sids"
|
|
|
|
|
|
def fixture_sids() -> dict[int, set[int]]:
|
|
"""Replay every fixture; map declared sid to sids actually emitted."""
|
|
out: dict[int, set[int]] = {}
|
|
for path in sorted(FIXTURE_DIR.glob("*.json")):
|
|
declared = int(path.name.split("-", 1)[0])
|
|
event = json.loads(path.read_text())
|
|
alerts = ruleops.test_event(Config(), event)
|
|
out[declared] = {a.sid for a in alerts}
|
|
return out
|
|
|
|
|
|
class TestEventFixtures(unittest.TestCase):
|
|
def test_every_event_rule_sid_has_an_emitting_fixture(self):
|
|
covered = fixture_sids()
|
|
wanted = sids.engine_sids("exec_rule") | sids.engine_sids("syscall_rule")
|
|
for sid in sorted(wanted):
|
|
self.assertIn(sid, covered,
|
|
f"no fixture file for sid {sid} in {FIXTURE_DIR}")
|
|
self.assertIn(sid, covered[sid],
|
|
f"fixture for sid {sid} did not emit it")
|
|
|
|
def test_fixtures_emit_only_registered_sids(self):
|
|
registry = sids.all_sids()
|
|
for declared, emitted in fixture_sids().items():
|
|
self.assertLessEqual(emitted, registry,
|
|
f"fixture {declared} emitted unregistered sids")
|
|
|
|
|
|
def drill_sids() -> dict[int, set[int]]:
|
|
"""Run every non-event drill; map declared sid to sids emitted."""
|
|
return {sid: {a.sid for a in drill()} for sid, drill in sorted(DRILLS.items())}
|
|
|
|
|
|
class TestSidCoverageGate(unittest.TestCase):
|
|
def test_every_non_event_sid_has_an_emitting_drill(self):
|
|
covered = drill_sids()
|
|
event_sids = sids.engine_sids("exec_rule") | sids.engine_sids("syscall_rule")
|
|
wanted = sids.all_sids() - event_sids
|
|
self.assertEqual(set(covered), wanted)
|
|
for sid in sorted(wanted):
|
|
self.assertIn(sid, covered[sid], f"drill for sid {sid} did not emit it")
|
|
|
|
def test_fixtures_and_drills_equal_registry(self):
|
|
emitted = set()
|
|
for sids_for_fixture in fixture_sids().values():
|
|
emitted.update(sids_for_fixture)
|
|
for sids_for_drill in drill_sids().values():
|
|
emitted.update(sids_for_drill)
|
|
self.assertEqual(emitted, sids.all_sids())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|