Replaces LICENSE (GPLv3 -> AGPLv3) and prepends `SPDX-License-Identifier: AGPL-3.0-or-later` to every source file across decnet/, decnet_web/, tests/, scripts/, and tools/. Rationale: closes the GPLv3 ASP loophole so any party operating a modified DECNET as a network service must offer their modified source. Personal copyright (Samuel Paschuan) + inbound=outbound contributions make a future unilateral relicense infeasible. - LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt) - COPYRIGHT: project copyright notice - tools/add_spdx_headers.py: idempotent header injector (shebang- and PEP 263-aware) Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh). No behavior change; comments only.
107 lines
3.8 KiB
Python
107 lines
3.8 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""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)
|
|
|
|
|
|
# ─── Identity resolution topics (commit 4 of IDENTITY_RESOLUTION.md) ─────────
|
|
|
|
|
|
def test_identity_builder() -> None:
|
|
assert topics.identity(topics.IDENTITY_FORMED) == "identity.formed"
|
|
assert topics.identity(topics.IDENTITY_OBSERVATION_LINKED) == "identity.observation.linked"
|
|
assert topics.identity(topics.IDENTITY_MERGED) == "identity.merged"
|
|
assert topics.identity(topics.IDENTITY_UNMERGED) == "identity.unmerged"
|
|
|
|
|
|
def test_identity_builder_rejects_empty() -> None:
|
|
with pytest.raises(ValueError):
|
|
topics.identity("")
|
|
|
|
|
|
def test_attribution_builder() -> None:
|
|
assert (
|
|
topics.attribution(topics.ATTRIBUTION_PROFILE_STATE_CHANGED)
|
|
== "attribution.profile.state_changed"
|
|
)
|
|
assert (
|
|
topics.attribution(topics.ATTRIBUTION_PROFILE_MULTI_ACTOR_SUSPECTED)
|
|
== "attribution.profile.multi_actor_suspected"
|
|
)
|
|
|
|
|
|
def test_attribution_builder_rejects_empty() -> None:
|
|
with pytest.raises(ValueError):
|
|
topics.attribution("")
|