# SPDX-License-Identifier: Apache-2.0 """Where a flood's scope is shown: the packet viewer, the live feed, message paths, monitor. The scope itself — the maths, the store, the history columns — is pinned in ``test_regions.py``; these tests are about the surfaces that state it, and the one rule they all share: a flood says which region it was sent into (or that it was unscoped, or scoped to a region nobody here has named), and a direct frame says nothing at all. """ from __future__ import annotations import io import json import re from datetime import timedelta from pathlib import Path from rich.cells import cell_len from meshterm.core.models import ChatMessage, Observation, utcnow from meshterm.core.region_store import RegionStore from meshterm.core.regions import ( UNSCOPED, Scope, frame_scope, region_key, scope_body, transport_code, ) from meshterm.persistence.repository import Repository from meshterm.services.message_paths import ( Arrival, channel_arrivals, collapse, message_scope, ) from meshterm.tools.monitor import _live_packets from meshterm.ui import script from meshterm.ui.livefeed_screen import LiveFeedScreen from meshterm.ui.message_paths_screen import MessagePathsScreen from meshterm.ui.packet_viewer import PacketEntry, PacketViewer from meshterm.ui.renderers import JsonRenderer, PlainRenderer from tests.conftest import plain as _plain from tests.test_message_paths import SECRET, _grp_txt_raw #: An arbitrary channel-text payload to scope (the code is over it, not over its meaning). _PAYLOAD = bytes.fromhex("a71c2d00112233445566778899aabbccddeeff") def _scoped(region: str, payload: bytes = _PAYLOAD, **extra) -> dict: """An RX-log frame's raw payload for a channel text flooded under ``region``.""" code = transport_code(region_key(region), scope_body(5, payload)) return { "route_typename": "TC_FLOOD", "payload_type": 5, "payload_typename": "GRP_TXT", "pkt_payload": payload, "transport_code": code.to_bytes(2, "little").hex() + "0000", **extra, } def _code(region: str) -> str: """The 4-hex code a :func:`_scoped` frame carries.""" return f"{transport_code(region_key(region), scope_body(5, _PAYLOAD)):04x}" _FLOOD = {"route_typename": "FLOOD", "payload_typename": "GRP_TXT", "chan_hash": "a7"} _DIRECT = {"route_typename": "DIRECT", "payload_typename": "TEXT_MSG", "dest_hash": "a1"} def _knows(*names: str): # noqa: ANN202 - a scope reader over a fixed set of names """A ``scope_of`` that knows exactly ``names`` — the region store's answer, in small.""" return lambda raw: frame_scope(raw, names) if isinstance(raw, dict) else None def _stripped(lines: list[str]) -> list[str]: return [re.sub(r"\x1b\[[0-9;]*m", "", line) for line in lines] def _col(line: str, needle: str) -> int: """The display column (cells, not characters — the class icon is two) of ``needle``.""" return cell_len(line[: line.index(needle)]) # -- the packet viewer ------------------------------------------------------------------- def _card_row(raw: dict, label: str, scope_of=None) -> str | None: # noqa: ANN001 """The value of the viewer's ``label`` row for one frame, as plain text (``None``: absent).""" entry = PacketEntry(when=utcnow(), kind="packet", path="3d", raw=raw) body = _stripped( PacketViewer([entry], 0, resolve=lambda h: "", scope_of=scope_of).render_body(80) ) row = next((line for line in body if line.split(None, 1)[:1] == [label]), None) return None if row is None else row.split(None, 1)[1].rstrip() def _has_scope_row(text: str, value: str) -> bool: """Whether a rendered card carries a ``scope`` row reading ``value``.""" return re.search(rf"^scope\s+{re.escape(value)}\s*$", text, re.M) is not None def test_the_viewer_names_the_region_a_flood_was_scoped_to() -> None: """``tc flood`` said *how* and never *where*: route says flood, its own row the region.""" assert _card_row(_scoped("harbour"), "route", _knows("harbour")) == "flood" assert _card_row(_scoped("harbour"), "scope", _knows("harbour")) == "harbour" def test_the_viewer_shows_an_unnamed_scopes_code_and_a_plain_flood_as_unscoped() -> None: """A scope no known name reproduces keeps its code; a plain flood reads unscoped.""" assert _card_row(_scoped("elsewhere"), "scope", _knows("harbour")) == ( f"unknown region · code {_code('elsewhere')}" ) assert _card_row(_FLOOD, "scope", _knows("harbour")) == "unscoped" def test_the_viewer_without_a_store_still_tells_scoped_from_unscoped() -> None: """No names at hand is less of the truth, never a wrong one: the code stands in.""" assert _card_row(_scoped("harbour"), "scope") == f"unknown region · code {_code('harbour')}" assert _card_row(_FLOOD, "scope") == "unscoped" def test_the_viewer_never_gives_a_direct_frame_a_scope() -> None: """A repeater never region-filters a direct packet: its route word alone, no scope row.""" assert _card_row(_DIRECT, "route", _knows("harbour")) == "direct" assert _card_row(_DIRECT, "scope", _knows("harbour")) is None tc_direct = {**_DIRECT, "route_typename": "TC_DIRECT"} assert _card_row(tc_direct, "route") == "tc direct" assert _card_row(tc_direct, "scope") is None def test_the_viewer_keeps_the_scope_body_out_of_the_raw_dump(tmp_path: Path) -> None: """The restored HMAC input is plumbing the route row already spoke for.""" raw = _scoped("harbour") restored = {k: v for k, v in raw.items() if k not in ("payload_type", "pkt_payload")} restored["scope_body"] = scope_body(5, _PAYLOAD).hex() body = _plain( PacketViewer( [PacketEntry(when=utcnow(), kind="packet", path="", raw=restored)], 0, resolve=lambda h: "", scope_of=_knows("harbour"), ).render_body(80) ) assert _has_scope_row(body, "harbour") # a replayed frame resolves exactly as a live one assert "scope_body" not in body def test_the_viewer_renames_a_scope_the_moment_its_region_is_learned(tmp_path: Path) -> None: """The card is memoized, but not past a name arriving: the scope is in the cache key.""" store = RegionStore(tmp_path / "regions.json") entry = PacketEntry(when=utcnow(), kind="packet", path="", raw=_scoped("harbour")) viewer = PacketViewer([entry], 0, resolve=lambda h: "", scope_of=store.scope_of) assert not _has_scope_row(_plain(viewer.render_body(80)), "harbour") store.learn("harbour", "typed") assert _has_scope_row(_plain(viewer.render_body(80)), "harbour") # -- the live feed ----------------------------------------------------------------------- class _Session: def invalidate(self) -> None: pass def _feed(*raws: dict) -> LiveFeedScreen: """A live feed over one packet row per frame, oldest first, knowing ``harbour``.""" screen = LiveFeedScreen( session=_Session(), resolve=lambda h: "", seed=[ Observation(node="", kind="packet", snr=5.0, rssi=-90.0, observed_at=utcnow(), raw=raw) for raw in raws ], scope_of=_knows("harbour"), ) screen.note_viewport(40) return screen def test_the_feed_scope_lane_sits_between_subject_and_readings() -> None: """A plain flood is a muted dash; a direct frame, which has no scope, leaves it blank.""" screen = _feed(_DIRECT, _FLOOD, _scoped("elsewhere"), _scoped("harbour")) header, *rows = _stripped(screen.render_body(100)) lanes = header.split() assert lanes.index("SUBJECT") + 1 == lanes.index("SCOPE") == lanes.index("SNR") - 1 col = _col(header, "SCOPE") assert _col(rows[0], "harbour") == col assert _col(rows[1], f"? {_code('elsewhere')}") == col assert _col(rows[2], " - ") + 1 == col assert "-" not in rows[3].replace("-9", "") # the direct frame's lane is blank assert all(row.rstrip().endswith("dBm") for row in rows) def test_the_feed_never_collapses_a_lane() -> None: """The class name and the scope are drawn at every width; a narrow row scrolls instead. Nothing gives way: at 72 columns (a 68-cell body) the readings run past the edge, and the highlighted row's ←→ is what reaches them. """ screen = _feed(_scoped("harbour")) for width in (68, 72, 80, 100): header, row = _stripped(screen.render_body(width))[:2] assert "CLASS" in header and "chan text" in row, width assert _col(header, "SCOPE") == _col(row, "harbour"), width screen._selected = 0 # noqa: SLF001 screen.render_body(68) assert screen._hmax > 0 # noqa: SLF001 - the row is wider than the body: ←→ reaches it def test_the_picocalc_feed_names_the_class_without_an_icon() -> None: """On the console the class icon is a one-glyph stand-in; the name alone says it.""" from meshterm.platforms import PICOCALC, REGULAR, set_platform set_platform(PICOCALC) try: screen = _feed(_scoped("harbour")) header, row = _stripped(screen.render_body(53))[:2] assert header.split()[:2] == ["TIME", "CLASS"] assert _col(header, "CLASS") == _col(row, "chan text") finally: set_platform(REGULAR) def test_the_feed_hands_its_scope_reader_to_the_viewer() -> None: """Enter opens a card that names the same region the row did.""" screen = _feed(_scoped("harbour")) opened: list = [] screen._session.run_screen = lambda viewer: opened.append(viewer) # noqa: SLF001 screen._session.run_detached = lambda coro: None # noqa: SLF001 screen.handle("enter") assert _has_scope_row(_plain(opened[0].render_body(80)), "harbour") # -- message paths ----------------------------------------------------------------------- def test_a_messages_scope_is_read_once_off_its_arrivals() -> None: """Every copy carries the sender's code, so the first flooded copy answers. A named reading beats an unnamed one, any scope beats unscoped, and direct copies have nothing to say. """ scope_of = _knows("harbour") when = utcnow() def arrival(raw): # noqa: ANN001, ANN202 return Arrival(when=when, hops=(), snr=None, frame=raw) direct, flood = arrival(_DIRECT), arrival(_FLOOD) named, unnamed = arrival(_scoped("harbour")), arrival(_scoped("elsewhere")) assert message_scope([direct, unnamed, named], scope_of) == Scope( "scoped", "harbour", _code("harbour") ) assert message_scope([flood, unnamed], scope_of).state == "unknown" assert message_scope([direct, flood], scope_of) is UNSCOPED assert message_scope([direct], scope_of) is None assert message_scope([arrival(None)], scope_of) is None def test_a_stored_scoped_message_resolves_through_its_arrivals(tmp_path: Path) -> None: """History keeps what resolution needs, the matcher keeps the frame, collapse keeps it.""" repo = Repository(tmp_path / "test.db") run = repo.start_run("monitor", {}, None) now = utcnow() wire = "Alice: hi harbour" for i, path in enumerate(("3d63", "3d63", "3d63,a1b2")): raw = {**_grp_txt_raw(SECRET, wire), **_scoped("harbour")} repo.record_observation( run, Observation( node=None, kind="packet", path=path, snr=2.0, observed_at=now - timedelta(seconds=5 - i), raw=raw, ), ) message = ChatMessage(text=wire, is_channel=True, created_at=now) arrivals = collapse(channel_arrivals(repo, message, channel_name="#general", secret=SECRET)) assert len(arrivals) == 2 store = RegionStore(tmp_path / "regions.json") assert message_scope(arrivals, store.scope_of).state == "unknown" store.learn("harbour", "channel") assert message_scope(arrivals, store.scope_of).region == "harbour" repo.close() def _paths_screen(scope: Scope | None) -> MessagePathsScreen: message = ChatMessage(text="hi", is_channel=True, created_at=utcnow()) return MessagePathsScreen( message, [Arrival(when=utcnow(), hops=("3d",), snr=1.0)], matched=True, resolve=lambda h: h, prefix_bytes=1, self_name="Homestead", summary="heard once", source="Alice", scope=scope, ) def test_the_paths_dialog_states_the_scope_once_in_its_title() -> None: """A status atom on the title, in the viewer's words — and none for a direct message.""" assert _paths_screen(Scope("scoped", "harbour", "3fa1")).title == ( "Message paths · scope harbour" ) assert ( _paths_screen(Scope("unknown", None, "3fa1")).title == "Message paths · unknown scope 3fa1" ) assert _paths_screen(UNSCOPED).title == "Message paths · unscoped" assert _paths_screen(None).title == "Message paths" # …and never per arrival row: the body does not repeat it. body = _plain(_paths_screen(Scope("scoped", "harbour", "3fa1")).render_body(72)) assert "harbour" not in body # -- monitor ------------------------------------------------------------------------------- def _capture(renderer_cls, rows: list[dict]) -> str: # noqa: ANN001 buffer = io.StringIO() with renderer_cls(script.console(buffer)).stream(_live_packets()) as emit: for row in rows: emit(row) return buffer.getvalue() def _record(scope: Scope | None, name: str) -> dict: from meshterm.ui.fields import NodeRef return { "observed_at": utcnow(), "node": NodeRef(name=name, hash="d4e5f6a7"), "kind": "packet", "snr_db": 1.0, "rssi_dbm": -90.0, "position": None, "scope": scope, "path": [], } def test_monitor_streams_the_scope_before_the_name() -> None: """A pinned SCOPE lane on the plain face, and NAME still last. A long region overruns its lane and pushes the row right, so the one field built to be pushed is the only one it can push. """ text = _capture( PlainRenderer, [ _record(Scope("scoped", "harbour", "3fa1"), "Alice"), _record(Scope("unknown", None, "3fa1"), "Bob"), _record(UNSCOPED, "Carol"), _record(None, "Dave"), ], ) header, *rows = text.splitlines() assert header.split()[-2:] == ["SCOPE", "NAME"] assert [row.split()[-2:] for row in rows] == [ ["harbour", "Alice"], ["unknown", "Bob"], ["unscoped", "Carol"], ["-", "Dave"], ] assert ( len( { row.index(name) for row, name in zip(rows, "Alice Bob Carol Dave".split(), strict=True) } ) == 1 ) def test_monitor_json_carries_the_scope_object_or_null() -> None: """The document speaks the shared scope shape; a frame with none is ``null``.""" text = _capture( JsonRenderer, [_record(Scope("scoped", "harbour", "3fa1"), "Alice"), _record(None, "Bob")] ) first, second = (json.loads(line) for line in text.splitlines()) assert first["scope"] == {"state": "scoped", "region": "harbour", "code": "3fa1"} assert second["scope"] is None assert list(first)[-2:] == ["scope", "path"] # -- the channel list -------------------------------------------------------------------- def _channel_header(scopes: dict[int, str]) -> str: """The Channels list's column header, with the given slots scoped.""" from meshterm.core.channel_probe import ChannelSlot from meshterm.ui.channels import _LiveStats, _menu_items from meshterm.ui.tui.select import SelectScreen from tests.test_gallery import DEFAULT_PUBLIC_SECRET, _ChannelsCtx slots = [ ChannelSlot(idx=0, name="Public", secret=DEFAULT_PUBLIC_SECRET), ChannelSlot(idx=1, name="Ops", secret=bytes(range(16))), ] ctx = _ChannelsCtx(muted=set(), scopes={slots[i].identity: n for i, n in scopes.items()}) title, items = _menu_items(ctx, slots, 8, _LiveStats(ctx)) return _stripped(SelectScreen(title, items).render_body(68))[0] def test_the_channel_list_draws_scope_only_when_a_channel_has_one() -> None: """A column of blanks says nothing; one scoped channel brings the lane back.""" assert _channel_header({}).split() == ["CHANNEL", "UNREAD", "LAST", "MSGS", "ACTIVITY"] assert _channel_header({1: "harbour"}).split() == [ "CHANNEL", "SCOPE", "UNREAD", "LAST", "MSGS", "ACTIVITY", ] # -- a decoded message's card -------------------------------------------------------------- def test_a_decoded_messages_card_reads_its_scope_off_its_logged_copies(tmp_path: Path) -> None: """A 💬 row carries no frame; its card finds the flooded copy in the log and names it.""" from types import SimpleNamespace from meshterm.persistence.repository import Repository from meshterm.ui.livefeed_screen import message_scope_reader text = "Alice: road closed" raw = _grp_txt_raw(SECRET, text) payload = bytes.fromhex(raw["chan_hash"] + raw["cipher_mac"] + raw["crypted"]) code = transport_code(region_key("harbour"), scope_body(5, payload)) raw.update( route_typename="TC_FLOOD", payload_type=5, pkt_payload=payload, transport_code=code.to_bytes(2, "little").hex() + "0000", ) repo = Repository(tmp_path / "log.db") when = utcnow() repo.record_observation( repo.start_run("monitor", {}), Observation(node=None, kind="packet", path="3d", observed_at=when, raw=raw), ) store = RegionStore(tmp_path / "regions.json") ctx = SimpleNamespace(repo=repo, region_store=store) read = message_scope_reader(ctx, {1: ("#general", SECRET)}) entry = PacketEntry(when=when, kind="message", channel=1, text=text) assert read(entry).state == "unknown" # the copy is found; its region not yet named store.learn("harbour", "typed") assert read(entry).region == "harbour" # named the moment the region is known viewer = PacketViewer([entry], 0, resolve=lambda h: "", message_scope=read) assert _has_scope_row(_plain(viewer.render_body(80)), "harbour") other = PacketEntry(when=when, kind="message", channel=1, text="Bob: something else") assert read(other) is None # no copy of it in the log: no scope row, no guess assert read(PacketEntry(when=when, kind="message", channel=9, text=text)) is None repo.close()