Files
DECNET/tests/bus/test_topics.py
anti 0fbb07c2ec feat(workers): bus-backed Workers panel (registry, control, installed flag)
Ships the backend half of Config → Workers:

* Worker registry aggregates `system.*.health` + `system.bus.health`
  heartbeats into a last-seen dict; OK / STALE / UNKNOWN tiers drop
  out of a 90s window (3× the 30s heartbeat interval).
* `GET /api/v1/workers` returns the snapshot plus `bus_connected`
  (so the UI can explain "all UNKNOWN" when the bus socket is down)
  and a per-row `installed` flag populated from
  `systemctl list-unit-files decnet-*.service` (cached 30s).
* `POST /api/v1/workers/{name}/stop` publishes a stop intent on
  `system.<name>.control`; workers listen via the shared control
  listener in `bus/publish.py`.
* Heartbeat + control listener wired into collector / profiler /
  sniffer / prober / mutator worker loops. API self-heartbeats too
  so the panel always has one ground-truth row.
* Topic helper `system_control(name)` + tests covering builder
  validation, control listener shutdown path, and the API surface
  (auth gating, bus-connected field, unknown-name 404).

Adds `StartFailure` / `StartAllResponse` models in anticipation of
the upcoming start endpoints (DEBT-034).
2026-04-22 14:10:39 -04:00

75 lines
2.7 KiB
Python

"""Tests for the topic hierarchy builders."""
from __future__ import annotations
import pytest
from decnet.bus import topics
def test_topology_mutation_builder() -> None:
topic = topics.topology_mutation("abc123", topics.MUTATION_APPLIED)
assert topic == "topology.abc123.mutation.applied"
def test_topology_status_builder() -> None:
assert topics.topology_status("t-1") == "topology.t-1.status"
def test_decky_builder() -> None:
assert topics.decky("d-42", topics.DECKY_STATE) == "decky.d-42.state"
assert topics.decky("d-42", topics.DECKY_TRAFFIC) == "decky.d-42.traffic"
def test_system_builder_allows_dotted_leaf() -> None:
# system.bus.health has a dot in the leaf — that's intentional and a
# legitimate hierarchy refinement, not a segment violation.
assert topics.system(topics.SYSTEM_BUS_HEALTH) == "system.bus.health"
assert topics.system(topics.SYSTEM_LOG) == "system.log"
def test_system_builder_rejects_empty() -> None:
with pytest.raises(ValueError):
topics.system("")
@pytest.mark.parametrize("bad", ["", "has.dot", "has*wildcard", "has>wild", "with space", "with\ttab"])
def test_segment_validation(bad: str) -> None:
with pytest.raises(ValueError):
topics.topology_mutation(bad, topics.MUTATION_APPLIED)
with pytest.raises(ValueError):
topics.topology_status(bad)
with pytest.raises(ValueError):
topics.decky(bad, topics.DECKY_STATE)
with pytest.raises(ValueError):
topics.system_health(bad)
def test_attacker_builder() -> None:
assert topics.attacker(topics.ATTACKER_OBSERVED) == "attacker.observed"
assert topics.attacker(topics.ATTACKER_SCORED) == "attacker.scored"
assert topics.attacker(topics.ATTACKER_FINGERPRINTED) == "attacker.fingerprinted"
# Dotted leaf is intentional — same as system.bus.health.
assert topics.attacker(topics.ATTACKER_SESSION_STARTED) == "attacker.session.started"
assert topics.attacker(topics.ATTACKER_SESSION_ENDED) == "attacker.session.ended"
def test_attacker_builder_rejects_empty() -> None:
with pytest.raises(ValueError):
topics.attacker("")
def test_system_health_builder() -> None:
assert topics.system_health("sniffer") == "system.sniffer.health"
assert topics.system_health("mutator") == "system.mutator.health"
def test_system_control_builder() -> None:
assert topics.system_control("mutator") == "system.mutator.control"
assert topics.system_control("collector") == "system.collector.control"
@pytest.mark.parametrize("bad", ["", "has.dot", "has*wildcard", "has>wild", "with space", "with\ttab"])
def test_system_control_rejects_bad_segments(bad: str) -> None:
with pytest.raises(ValueError):
topics.system_control(bad)