"""Tests for uniform mesh-packet sizing. Verifies that every size-sensitive code path uses the single universal mesh_max_chars constant (140 = MeshCore LCD). All transports — Meshtastic, MeshCore, and the composite — report the same fixed value. """ import sys import types from unittest.mock import MagicMock import pytest # --------------------------------------------------------------------------- # Fake meshcore module (must precede production imports that lazy-import it) # --------------------------------------------------------------------------- def _build_fake_meshcore(): mod = types.ModuleType("meshcore") class EventType: CONTACT_MSG_RECV = "CONTACT_MSG_RECV" CHANNEL_MSG_RECV = "CHANNEL_MSG_RECV" DISCONNECTED = "DISCONNECTED" CONNECTED = "CONNECTED" mod.EventType = EventType class _FakeMeshCore: self_info = {"public_key": "aabbccdd1122", "name": "FakeNode"} contacts = {} async def start_auto_message_fetching(self): pass async def stop_auto_message_fetching(self): pass async def disconnect(self): pass def subscribe(self, event_type, callback): pass def get_contact_by_key_prefix(self, prefix): return None @classmethod async def create_tcp(cls, host, port, auto_reconnect=True, max_reconnect_attempts=5): return cls() class commands: @staticmethod async def send_chan_msg(chan_idx, text): result = MagicMock() result.is_error.return_value = False return result @staticmethod async def send_msg(dst, text): result = MagicMock() result.is_error.return_value = False return result mod.MeshCore = _FakeMeshCore return mod sys.modules.setdefault("meshcore", _build_fake_meshcore()) # --------------------------------------------------------------------------- # Production imports # --------------------------------------------------------------------------- from meshai.config import ConnectionConfig # noqa: E402 from meshai.connector import MeshtasticTransport # noqa: E402 from meshai.transport.meshcore_transport import MeshCoreTransport # noqa: E402 from meshai.transport.factory import build_transport # noqa: E402 from meshai.notifications.channels import ( # noqa: E402 MeshBroadcastChannel, MeshDMChannel, ) from meshai.notifications.pipeline import build_pipeline # noqa: E402 # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _mt_config(**overrides): """Return a ConnectionConfig with no MeshCore host (Meshtastic-only).""" cfg = ConnectionConfig() # meshcore_host defaults to "" → Meshtastic only for k, v in overrides.items(): setattr(cfg, k, v) return cfg def _mc_config(**overrides): """Return a ConnectionConfig with meshcore_host set (activates MeshCore).""" cfg = ConnectionConfig( meshcore_host="127.0.0.1", meshcore_port=5050, ) for k, v in overrides.items(): setattr(cfg, k, v) return cfg def _fake_connector(max_chars_val: int): """Minimal fake connector exposing only max_chars.""" conn = MagicMock() conn.max_chars = max_chars_val return conn def _minimal_config(): """Minimal full Config for build_pipeline calls.""" from meshai.config import Config return Config() # --------------------------------------------------------------------------- # 1. Transport max_chars property # --------------------------------------------------------------------------- class TestTransportMaxChars: def test_meshtastic_default_is_140(self): """Meshtastic now uses the universal mesh_max_chars constant (140).""" cfg = _mt_config() t = MeshtasticTransport(cfg) assert t.max_chars == 140 def test_meshcore_default_is_140(self): cfg = _mc_config() t = MeshCoreTransport(cfg) assert t.max_chars == 140 def test_build_transport_meshtastic_max_chars(self): """build_transport(meshtastic) → 140 (universal constant).""" t = build_transport(_mt_config()) assert t.max_chars == 140 def test_build_transport_meshcore_max_chars(self): t = build_transport(_mc_config()) assert t.max_chars == 140 def test_mesh_max_chars_config_field_governs_all(self): """A non-default mesh_max_chars propagates to both transport types.""" cfg_mt = _mt_config(mesh_max_chars=120) assert MeshtasticTransport(cfg_mt).max_chars == 120 cfg_mc = _mc_config(mesh_max_chars=120) assert MeshCoreTransport(cfg_mc).max_chars == 120 def test_all_three_transports_and_composite_report_140(self): """Sanity: all transports + CompositeTransport report mesh_max_chars=140. This is the canonical single-budget guarantee: Meshtastic, MeshCore, and the composite (meshcore_host set) all resolve to the same constant. """ from meshai.config import ConnectionConfig from meshai.transport.composite_transport import CompositeTransport # Meshtastic assert MeshtasticTransport(_mt_config()).max_chars == 140 # MeshCore assert MeshCoreTransport(_mc_config()).max_chars == 140 # Composite — derived from meshcore_host being non-empty cfg_both = ConnectionConfig( type="tcp", tcp_host="127.0.0.1", tcp_port=4403, meshcore_host="127.0.0.1", meshcore_port=5050, ) comp = build_transport(cfg_both) assert isinstance(comp, CompositeTransport) assert comp.max_chars == 140 # --------------------------------------------------------------------------- # 2. MeshRenderer char_limit propagation via channels # --------------------------------------------------------------------------- class TestChannelRendererBudget: def test_broadcast_channel_propagates_connector_max_chars(self): """Channel renderer inherits max_chars from whatever the connector reports.""" conn = _fake_connector(140) ch = MeshBroadcastChannel(connector=conn, channel_index=0) assert ch._renderer._limit == 140 def test_broadcast_channel_with_meshtastic_connector_uses_140(self): """After the universal budget refactor, a meshtastic connector reports 140.""" conn = _fake_connector(140) ch = MeshBroadcastChannel(connector=conn, channel_index=0) assert ch._renderer._limit == 140 def test_dm_channel_propagates_connector_max_chars(self): conn = _fake_connector(140) ch = MeshDMChannel(connector=conn, node_ids=["!aabbccdd"]) assert ch._renderer._limit == 140 def test_dm_channel_with_meshtastic_connector_uses_140(self): conn = _fake_connector(140) ch = MeshDMChannel(connector=conn, node_ids=["!aabbccdd"]) assert ch._renderer._limit == 140 # --------------------------------------------------------------------------- # 3. DigestAccumulator mesh_char_limit propagation via build_pipeline # --------------------------------------------------------------------------- class TestBuildPipelineMeshCharLimit: def test_no_connector_uses_140(self): """Without a connector the pipeline falls back to the universal constant (140).""" cfg = _minimal_config() bus = build_pipeline(cfg, llm_backend=None, connector=None) acc = bus._pipeline_components["accumulator"] assert acc._mesh_char_limit == 140 def test_meshcore_connector_uses_140(self): cfg = _minimal_config() conn = _fake_connector(140) bus = build_pipeline(cfg, llm_backend=None, connector=conn) acc = bus._pipeline_components["accumulator"] assert acc._mesh_char_limit == 140 def test_meshtastic_connector_uses_140(self): """After universal budget, a meshtastic connector also reports 140.""" cfg = _minimal_config() conn = _fake_connector(140) bus = build_pipeline(cfg, llm_backend=None, connector=conn) acc = bus._pipeline_components["accumulator"] assert acc._mesh_char_limit == 140 # --------------------------------------------------------------------------- # 4. Runtime-override durability: survives adapter_config cache invalidation # --------------------------------------------------------------------------- class TestRuntimeOverrideDurability: """Verify set_runtime_override writes are not cleared by invalidate_cache(). We test at the _resolve/_overrides seam directly because the full adapter_config.nws.single_packet_max_chars read path hits the DB, which is not available in this hermetic test environment. Testing via _resolve is equivalent: it is the exact call site used by _AdapterSection.__getattr__. """ def test_override_survives_cache_invalidation(self): from meshai.adapter_config._accessor import ( _overrides, _resolve, set_runtime_override, invalidate_cache, _SENTINEL, ) key = ("nws", "single_packet_max_chars") try: set_runtime_override("nws", "single_packet_max_chars", 140) # Override is visible through the resolution path. assert _resolve("nws", "single_packet_max_chars") == 140 # Cache invalidation must NOT wipe the override. invalidate_cache() assert _resolve("nws", "single_packet_max_chars") == 140 finally: # Clean up so this test cannot pollute subsequent tests. _overrides.pop(key, None)