Files
MeshTerm/tests/test_demo.py
T
Jean-Pierre MartineauandClaude Opus 5.5 507e3bd999 In the demo, a node that we mention answers, and our node uses three-byte paths
A channel message of our node that mentions Dave gets his answer a few seconds later, and the answer mentions our node. The answers are a table in the demo script. Our node uses three bytes for each hop, as most of the mesh does, so a trace uses two.

The video's tour now selects the two paths of a message in turn, types a question to Dave so that the mention becomes a chip, and shows his answer with our star. The trace shows its new records, and the map zooms from the city to the streets around our node.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 09:51:56 -04:00

142 lines
5.5 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""The demo world: the same history at each run, and a device that agrees with it."""
from __future__ import annotations
import asyncio
from datetime import timedelta
from pathlib import Path
from meshterm.core.channels import channel_identity
from meshterm.core.models import utcnow
from meshterm.demo import history, world
from meshterm.demo.device import DemoDevice
from meshterm.demo.home import prepare
from meshterm.persistence.repository import Repository
def _seed(path: Path, now) -> Repository: # noqa: ANN001 - a datetime
repo = Repository(path)
history.seed(repo, now)
return repo
def test_each_node_has_its_own_hash() -> None:
"""No two nodes of the world share a path hash, so each hop resolves to one node."""
hashes = [node.hash for node in (world.SELF, *world.NODES)]
assert len(hashes) == len(set(hashes))
def test_the_history_is_the_same_at_each_run(tmp_path: Path) -> None:
"""Two runs give the same rows, with the times moved by the gap between the two starts."""
now = utcnow()
first = _seed(tmp_path / "a.db", now)
second = _seed(tmp_path / "b.db", now + timedelta(hours=5))
assert first.table_counts() == second.table_counts()
assert first.kind_counts() == second.kind_counts()
public = world.CHANNELS[0]
identity = channel_identity(public.name, public.secret)
def lines(repo: Repository, start) -> list[tuple[str, int]]: # noqa: ANN001 - a datetime
rows = repo.recent_chat_messages(is_channel=True, channel_id=identity, limit=500)
return [(m.text, round((start - m.created_at).total_seconds())) for m in rows]
assert lines(first, now) == lines(second, now + timedelta(hours=5))
def test_a_channel_line_has_its_heard_copies(tmp_path: Path) -> None:
"""The packets of a channel line decrypt to the line, so its paths can be found."""
from meshterm.services.message_paths import channel_arrivals
repo = _seed(tmp_path / "h.db", utcnow())
public = world.CHANNELS[0]
identity = channel_identity(public.name, public.secret)
last = repo.recent_chat_messages(is_channel=True, channel_id=identity, limit=1)[-1]
arrivals = channel_arrivals(repo, last, channel_name=public.name, secret=public.secret)
assert arrivals
def test_the_demo_device_traces_a_path() -> None:
"""A forced path through linked repeaters comes back with a reading for each link."""
async def run(): # noqa: ANN202
device = DemoDevice()
await device.connect()
spec = ",".join(world.node(n).hash for n in ("Rosemont", "Mont-Royal", "Rosemont"))
for _ in range(10):
result = await device.run_trace("(path)", path=spec)
if result.success:
return result
return result
result = asyncio.run(run())
assert result.success
assert [h.node for h in result.hops] == ["d8", "2a", "d8", None]
def test_prepare_writes_a_home(tmp_path: Path) -> None:
"""The demo home gets a database and the stores of the joined room and the alerts."""
home = tmp_path / "demo"
prepare(home, utcnow())
prepare(home, utcnow()) # a second start writes it again over the first
assert (home / "meshterm.db").exists()
assert (home / "rooms.json").exists()
assert (home / "watchtower.json").exists()
def test_each_line_is_sent_while_its_sender_transmits() -> None:
"""No node speaks before our node first heard it, or after it went quiet."""
from meshterm.demo import script
groups = (script.CHANNEL_LINES, script.DIRECT_LINES, script.ROOM_LINES)
for group in groups:
for lines in group.values():
for line in lines:
node = world.BY_NAME.get(line.sender)
if node is None or node is world.SELF:
continue # an author that no contact names
age_h = line.age_min / 60.0
assert node.quiet_hours is not None, line
assert node.quiet_hours <= age_h <= node.first_heard_days * 24.0, line
def test_our_node_uses_the_path_width_of_the_mesh() -> None:
"""Our node uses three bytes for each hop, so a trace uses two (the widest it encodes)."""
from meshterm.ui.trace_screen import collapse_trace_width
mode = asyncio.run(DemoDevice().get_path_hash_mode())
assert mode + 1 == world.path_width(world.SELF) == 3
assert collapse_trace_width(mode) == 2
def test_a_mentioned_node_answers(monkeypatch) -> None: # noqa: ANN001 - the pytest fixture
"""A channel message that mentions Dave gets his answer, which mentions our node."""
from meshterm.demo import script
monkeypatch.setattr(script, "REPLY_DELAY_S", 0.0)
async def run(): # noqa: ANN202
device = DemoDevice()
await device.connect()
messages = []
def on_event(event) -> None: # noqa: ANN001 - a MeshEvent
if event.message is not None:
messages.append(event.message)
unsubscribe = await device.subscribe_events(on_event)
await device.send_channel_message(0, "Hey, is @[Dave-Lachine] coming?")
await device.send_channel_message(0, "Hello, @[Félix]") # no answer in the script
for _ in range(100):
if messages:
break
await asyncio.sleep(0.05)
await asyncio.sleep(0.5)
unsubscribe()
return messages
messages = asyncio.run(run())
assert [m.text for m in messages] == [f"Dave-Lachine: {script.REPLIES['Dave-Lachine']}"]
assert messages[0].is_channel and messages[0].channel == 0
assert f"@[{world.SELF.name}]" in messages[0].text