Files
microdao-daarion/tests/test_matrix_bridge_mixed_routing.py
Apple a85a11984b feat(matrix-bridge-dagi): add mixed-room routing by slash/mention (M2.1)
- mixed_routing.py: parse BRIDGE_MIXED_ROOM_MAP, route by /slash > @mention > name: > default
- ingress.py: _try_enqueue_mixed for mixed rooms, session isolation {room}:{agent}, reply tagging
- config.py: bridge_mixed_room_map + bridge_mixed_defaults fields
- main.py: parse mixed config, pass to MatrixIngressLoop, expose in /health + /bridge/mappings
- docker-compose: BRIDGE_MIXED_ROOM_MAP / BRIDGE_MIXED_DEFAULTS env vars, BRIDGE_ALLOWED_AGENTS multi-value
- tests: 25 routing unit tests + 10 ingress integration tests (94 total pass)

Made-with: Cursor
2026-03-05 01:29:18 -08:00

228 lines
8.1 KiB
Python

"""
Tests for services/matrix-bridge-dagi/app/mixed_routing.py
Covers:
- parse_mixed_room_map: valid, errors, defaults
- route_message: slash, @mention, colon-mention, fallback, unknown agent
- reply_prefix: mixed vs single-agent rooms
"""
import sys
from pathlib import Path
import pytest
_BRIDGE = Path(__file__).parent.parent / "services" / "matrix-bridge-dagi"
if str(_BRIDGE) not in sys.path:
sys.path.insert(0, str(_BRIDGE))
from app.mixed_routing import ( # noqa: E402
MixedRoomConfig,
MixedRoom,
parse_mixed_room_map,
route_message,
reply_prefix,
REASON_SLASH,
REASON_AT_MENTION,
REASON_COLON_MENTION,
REASON_DEFAULT,
)
ROOM_X = "!roomX:daarion.space"
ROOM_Y = "!roomY:daarion.space"
ALLOWED = frozenset({"sofiia", "helion", "druid", "nutra"})
# ── Parsing ────────────────────────────────────────────────────────────────────
def test_parse_single_mixed_room():
raw = f"{ROOM_X}=sofiia,helion"
cfg = parse_mixed_room_map(raw, "", ALLOWED)
assert cfg.total_rooms == 1
assert cfg.agents_for_room(ROOM_X) == ["sofiia", "helion"]
assert cfg.default_agent(ROOM_X) == "sofiia" # first in list
def test_parse_two_mixed_rooms():
raw = f"{ROOM_X}=sofiia,helion;{ROOM_Y}=druid,nutra"
cfg = parse_mixed_room_map(raw, "", ALLOWED)
assert cfg.total_rooms == 2
assert cfg.agents_for_room(ROOM_Y) == ["druid", "nutra"]
assert cfg.default_agent(ROOM_Y) == "druid"
def test_parse_explicit_default():
raw = f"{ROOM_X}=sofiia,helion"
defaults = f"{ROOM_X}=helion"
cfg = parse_mixed_room_map(raw, defaults, ALLOWED)
assert cfg.default_agent(ROOM_X) == "helion"
def test_parse_explicit_default_not_in_agents_raises():
raw = f"{ROOM_X}=sofiia,helion"
defaults = f"{ROOM_X}=druid" # druid not in agents for ROOM_X
with pytest.raises(ValueError, match="Default agent"):
parse_mixed_room_map(raw, defaults, ALLOWED)
def test_parse_duplicate_room_raises():
raw = f"{ROOM_X}=sofiia;{ROOM_X}=helion"
with pytest.raises(ValueError, match="Duplicate room_id"):
parse_mixed_room_map(raw, "", ALLOWED)
def test_parse_unknown_agent_raises():
raw = f"{ROOM_X}=sofiia,unknown_bot"
with pytest.raises(ValueError, match="not in allowed_agents"):
parse_mixed_room_map(raw, "", ALLOWED)
def test_parse_bad_room_id_raises():
raw = "not-a-room-id=sofiia"
with pytest.raises(ValueError, match="Invalid room_id"):
parse_mixed_room_map(raw, "", ALLOWED)
def test_parse_empty_map_returns_empty():
cfg = parse_mixed_room_map("", "", ALLOWED)
assert cfg.total_rooms == 0
def test_parse_semicolons_with_spaces():
raw = f" {ROOM_X}=sofiia,helion ; {ROOM_Y}=druid "
cfg = parse_mixed_room_map(raw, "", ALLOWED)
assert cfg.total_rooms == 2
def test_is_mixed_true_false():
raw = f"{ROOM_X}=sofiia,helion"
cfg = parse_mixed_room_map(raw, "", ALLOWED)
assert cfg.is_mixed(ROOM_X) is True
assert cfg.is_mixed(ROOM_Y) is False
def test_as_summary_shape():
raw = f"{ROOM_X}=sofiia,helion;{ROOM_Y}=druid"
cfg = parse_mixed_room_map(raw, "", ALLOWED)
summary = cfg.as_summary()
assert len(summary) == 2
for entry in summary:
assert "room_id" in entry
assert "agents" in entry
assert "default_agent" in entry
# ── Routing — slash command ────────────────────────────────────────────────────
def _make_cfg(room_id: str = ROOM_X, agents=("sofiia", "helion")) -> MixedRoomConfig:
raw = f"{room_id}={','.join(agents)}"
return parse_mixed_room_map(raw, "", frozenset(agents))
def test_slash_routes_to_correct_agent():
cfg = _make_cfg()
agent, reason, body = route_message("/helion tell me the weather", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent == "helion"
assert reason == REASON_SLASH
assert body == "tell me the weather"
def test_slash_case_insensitive():
cfg = _make_cfg()
agent, reason, _ = route_message("/Sofiia hello", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent == "sofiia"
assert reason == REASON_SLASH
def test_slash_empty_body_keeps_original():
cfg = _make_cfg()
agent, reason, body = route_message("/helion", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent == "helion"
# body fallback: original text
assert "/helion" in body
def test_slash_unknown_agent_returns_none():
cfg = _make_cfg()
agent, reason, _ = route_message("/druid hello", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent is None
assert "unknown_slash_druid" in reason
# ── Routing — @mention ────────────────────────────────────────────────────────
def test_at_mention_routes_correctly():
cfg = _make_cfg()
agent, reason, body = route_message("@sofiia what is the status?", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent == "sofiia"
assert reason == REASON_AT_MENTION
assert body == "what is the status?"
def test_at_mention_unknown_falls_through_to_default():
cfg = _make_cfg()
# @unknown_bot — not in agents → falls through to colon check, then default
agent, reason, _ = route_message("@unknown_bot hello", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent == "sofiia" # default
assert reason == REASON_DEFAULT
# ── Routing — colon mention ───────────────────────────────────────────────────
def test_colon_mention_routes_correctly():
cfg = _make_cfg()
agent, reason, body = route_message("sofiia: can you help?", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent == "sofiia"
assert reason == REASON_COLON_MENTION
assert body == "can you help?"
def test_colon_mention_unknown_falls_to_default():
cfg = _make_cfg()
agent, reason, _ = route_message("druid: hello", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent == "sofiia"
assert reason == REASON_DEFAULT
# ── Routing — priority order ──────────────────────────────────────────────────
def test_slash_beats_at_mention():
"""If text starts with slash, it should be slash-routed even if it also mentions @."""
cfg = _make_cfg()
agent, reason, _ = route_message("/helion @sofiia hello", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert reason == REASON_SLASH
assert agent == "helion"
# ── Routing — default fallback ────────────────────────────────────────────────
def test_plain_message_routes_to_default():
cfg = _make_cfg()
agent, reason, body = route_message("plain message no routing token", ROOM_X, cfg, frozenset({"sofiia", "helion"}))
assert agent == "sofiia"
assert reason == REASON_DEFAULT
assert body == "plain message no routing token"
def test_no_mapping_for_room_returns_none():
cfg = _make_cfg(room_id=ROOM_X)
agent, reason, _ = route_message("hello", ROOM_Y, cfg, ALLOWED) # ROOM_Y not in config
assert agent is None
assert reason == "no_mapping"
# ── Reply prefix ──────────────────────────────────────────────────────────────
def test_reply_prefix_mixed_room():
assert reply_prefix("sofiia", is_mixed=True) == "Sofiia: "
assert reply_prefix("helion", is_mixed=True) == "Helion: "
def test_reply_prefix_single_room_empty():
assert reply_prefix("sofiia", is_mixed=False) == ""
def test_reply_prefix_capitalises_first_letter():
assert reply_prefix("druid", is_mixed=True) == "Druid: "
assert reply_prefix("NUTRA", is_mixed=True) == "Nutra: " # capitalize() normalises case