refactor(prober): generalise ActiveProbe registry to absorb Ipv6LeakProbe

ActiveProbe.run/syslog_fields/publish_payload now accept port=None so
non-port-iterating probes can live in the registry. Ipv6LeakProbe replaces
the hand-rolled _ipv6_leak_phase special case in worker.py; it runs last
via priority=999. _probe_cycle no longer has an ad-hoc phase call.

Fixes three stale test files (test_prober_bus, test_prober_rotation,
test_prober_worker) that were broken since the 916b21b6 registry refactor.
This commit is contained in:
2026-05-21 14:27:48 -04:00
parent b80e621904
commit bd4700770b
12 changed files with 409 additions and 420 deletions

View File

@@ -21,33 +21,64 @@ def _restore_registry():
class TestRegistryContents:
def test_all_three_probes_registered(self):
def test_all_probes_registered(self):
names = {cls.probe_name for cls in ActiveProbeMeta.all()}
assert names == {"jarm", "hassh", "tcpfp"}
assert names == {"jarm", "hassh", "tcpfp", "ipv6_leak"}
def test_sorted_by_priority_then_name(self):
order = [cls.probe_name for cls in ActiveProbeMeta.all()]
assert order == ["hassh", "jarm", "tcpfp"] # all priority=100, alphabetical
# hassh/jarm/tcpfp all priority=100 (alphabetical), ipv6_leak priority=999 last
assert order == ["hassh", "jarm", "tcpfp", "ipv6_leak"]
def test_priority10_probe_sorts_first(self):
class _FastProbe(ActiveProbe):
probe_name = "_fast_test_probe"
default_ports = [9999]
default_ports: list[int | None] = [9999]
event_type = "_fast_event"
priority = 10
def run(self, ip: str, port: int, timeout: float) -> dict[str, Any] | None:
def run(self, ip: str, port: int | None, timeout: float) -> dict[str, Any] | None:
return None
def syslog_fields(self, ip: str, port: int, result: dict[str, Any]) -> tuple[dict[str, Any], str]:
def syslog_fields(self, ip: str, port: int | None, result: dict[str, Any]) -> tuple[dict[str, Any], str]:
return {}, ""
def publish_payload(self, ip: str, port: int, result: dict[str, Any]) -> dict[str, Any]:
def publish_payload(self, ip: str, port: int | None, result: dict[str, Any]) -> dict[str, Any]:
return {}
order = [cls.probe_name for cls in ActiveProbeMeta.all()]
assert order[0] == "_fast_test_probe"
assert set(order[1:]) == {"hassh", "jarm", "tcpfp"}
assert set(order[1:]) == {"hassh", "jarm", "tcpfp", "ipv6_leak"}
def test_port_none_probe_dispatched_with_none_port(self):
"""_run_probe must call run(ip, None, timeout) for a port-free probe."""
calls: list[tuple] = []
class _NullPortProbe(ActiveProbe):
probe_name = "_null_port_test"
default_ports: list[int | None] = [None]
event_type = "_null_event"
priority = 10
def run(self, ip: str, port: int | None, timeout: float) -> dict[str, Any] | None:
calls.append((ip, port))
return None
def syslog_fields(self, ip: str, port: int | None, result: dict[str, Any]) -> tuple[dict[str, Any], str]:
return {}, ""
def publish_payload(self, ip: str, port: int | None, result: dict[str, Any]) -> dict[str, Any]:
return {}
from pathlib import Path
from decnet.prober.worker import _run_probe
_run_probe(
_NullPortProbe(), "10.0.0.1", {},
Path("/dev/null"), Path("/dev/null"),
timeout=1.0, publish_fn=None, record_rotation=None,
)
assert calls == [("10.0.0.1", None)]
def test_base_class_not_registered(self):
assert "ActiveProbe" not in ActiveProbeMeta._registry