Files
meshcore-bot/tests/test_graph_trace_helper.py
T
agessaman 53112c5f05 Refactor: streamline imports and enhance documentation in utility and service files
- Updated the `resolve_path` function in `utils.py` to clarify behavior regarding absolute paths.
- Changed type hints in `discord_bridge_service.py` for better clarity and consistency.
- Removed unused imports and unnecessary comments in various test files to improve code cleanliness and readability.
2026-03-18 18:27:05 -07:00

320 lines
14 KiB
Python

"""Tests for modules.graph_trace_helper — update_mesh_graph_from_trace_data."""
import configparser
from unittest.mock import MagicMock, Mock
import pytest
from modules.graph_trace_helper import update_mesh_graph_from_trace_data
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_bot(bot_prefix="aa", has_mesh_graph=True, has_transmission_tracker=True):
"""Create a minimal mock bot for graph_trace_helper tests."""
bot = MagicMock()
bot.logger = Mock()
config = configparser.ConfigParser()
config.add_section("Path_Command")
config.set("Path_Command", "graph_edge_expiration_days", "7")
bot.config = config
if has_mesh_graph:
bot.mesh_graph = MagicMock()
else:
bot.mesh_graph = None
if has_transmission_tracker:
bot.transmission_tracker = MagicMock()
bot.transmission_tracker.bot_prefix = bot_prefix
bot.transmission_tracker.match_packet_hash = Mock(return_value=None)
else:
bot.transmission_tracker = None
# DB manager returns empty results by default
bot.db_manager = MagicMock()
bot.db_manager.execute_query = Mock(return_value=[])
# meshcore device (optional)
bot.meshcore = MagicMock()
bot.meshcore.device = MagicMock()
bot.meshcore.device.public_key = "aa" * 32
return bot
# ---------------------------------------------------------------------------
# Early exit / guard cases
# ---------------------------------------------------------------------------
class TestEarlyExits:
def test_empty_path_hashes_returns_immediately(self):
bot = _make_bot()
update_mesh_graph_from_trace_data(bot, [], {})
bot.mesh_graph.add_edge.assert_not_called()
def test_none_path_hashes_treated_as_empty(self):
bot = _make_bot()
# empty list is falsy, so this is covered by the guard
update_mesh_graph_from_trace_data(bot, [], {})
bot.mesh_graph.add_edge.assert_not_called()
def test_no_mesh_graph_returns_immediately(self):
bot = _make_bot(has_mesh_graph=False)
update_mesh_graph_from_trace_data(bot, ["ab"], {})
# Should log and return without crash
bot.logger.debug.assert_called()
def test_no_transmission_tracker_returns_immediately(self):
bot = _make_bot(has_transmission_tracker=False)
update_mesh_graph_from_trace_data(bot, ["ab"], {})
bot.logger.debug.assert_called()
def test_missing_mesh_graph_attribute(self):
bot = _make_bot()
del bot.mesh_graph
update_mesh_graph_from_trace_data(bot, ["ab"], {})
# No crash expected
def test_missing_transmission_tracker_attribute(self):
bot = _make_bot()
del bot.transmission_tracker
update_mesh_graph_from_trace_data(bot, ["ab"], {})
# No crash expected
def test_empty_bot_prefix_returns_immediately(self):
bot = _make_bot()
bot.transmission_tracker.bot_prefix = None
update_mesh_graph_from_trace_data(bot, ["ab"], {})
bot.mesh_graph.add_edge.assert_not_called()
def test_empty_string_bot_prefix_returns_immediately(self):
bot = _make_bot()
bot.transmission_tracker.bot_prefix = ""
update_mesh_graph_from_trace_data(bot, ["ab"], {})
bot.mesh_graph.add_edge.assert_not_called()
# ---------------------------------------------------------------------------
# is_our_trace resolution
# ---------------------------------------------------------------------------
class TestIsOurTraceResolution:
def test_is_our_trace_none_resolves_false_when_no_match(self):
"""When packet_hash doesn't match, is_our_trace stays False."""
bot = _make_bot(bot_prefix="aa")
bot.transmission_tracker.match_packet_hash = Mock(return_value=None)
update_mesh_graph_from_trace_data(bot, ["bb"], {"packet_hash": "abc123"})
# Not our trace → should add one edge (last_node → bot)
bot.mesh_graph.add_edge.assert_called_once()
def test_is_our_trace_none_resolves_true_when_match(self):
"""When packet_hash matches a transmission record, is_our_trace becomes True."""
bot = _make_bot(bot_prefix="aa")
bot.transmission_tracker.match_packet_hash = Mock(return_value={"matched": True})
# Single hop — should trigger immediate-neighbor path (bidirectional)
update_mesh_graph_from_trace_data(bot, ["bb"], {"packet_hash": "abc123"})
# Bidirectional: 2 add_edge calls
assert bot.mesh_graph.add_edge.call_count == 2
def test_is_our_trace_explicit_true(self):
"""Explicit is_our_trace=True skips transmission tracker lookup."""
bot = _make_bot(bot_prefix="aa")
bot.transmission_tracker.match_packet_hash = Mock(return_value=None)
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=True)
# Single hop + explicit True → immediate neighbor → bidirectional
assert bot.mesh_graph.add_edge.call_count == 2
def test_is_our_trace_explicit_false(self):
"""Explicit is_our_trace=False skips transmission tracker lookup."""
bot = _make_bot(bot_prefix="aa")
bot.transmission_tracker.match_packet_hash = Mock(return_value={"matched": True})
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=False)
# Not our trace even though match_packet_hash would return a record
bot.mesh_graph.add_edge.assert_called_once()
def test_is_our_trace_true_no_packet_hash(self):
"""is_our_trace None with no packet_hash defaults to False."""
bot = _make_bot(bot_prefix="aa")
update_mesh_graph_from_trace_data(bot, ["bb"], {})
# No packet_hash → is_our_trace stays False → one edge
bot.mesh_graph.add_edge.assert_called_once()
# ---------------------------------------------------------------------------
# Immediate neighbor (single hop, is_our_trace=True)
# ---------------------------------------------------------------------------
class TestImmediateNeighbor:
def test_single_hop_creates_bidirectional_edges(self):
bot = _make_bot(bot_prefix="aa")
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=True)
assert bot.mesh_graph.add_edge.call_count == 2
def test_single_hop_edge_directions(self):
bot = _make_bot(bot_prefix="aa")
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=True)
calls = bot.mesh_graph.add_edge.call_args_list
from_prefixes = {c.kwargs.get("from_prefix") or c[1].get("from_prefix") for c in calls}
to_prefixes = {c.kwargs.get("to_prefix") or c[1].get("to_prefix") for c in calls}
# Both directions: aa↔bb
assert "aa" in from_prefixes or "aa" in to_prefixes
assert "bb" in from_prefixes or "bb" in to_prefixes
def test_single_hop_lowercase_normalization(self):
"""Path hashes are lowercased before use."""
bot = _make_bot(bot_prefix="AA")
update_mesh_graph_from_trace_data(bot, ["BB"], {}, is_our_trace=True)
assert bot.mesh_graph.add_edge.call_count == 2
calls = bot.mesh_graph.add_edge.call_args_list
# Check lowercase normalization
all_prefixes = set()
for c in calls:
all_prefixes.add(c.kwargs.get("from_prefix") or (c[0][0] if c[0] else None))
all_prefixes.add(c.kwargs.get("to_prefix") or (c[0][1] if len(c[0]) > 1 else None))
# Should contain lowercase versions
assert "aa" in all_prefixes or "bb" in all_prefixes
def test_single_hop_with_unique_db_key(self):
"""When DB returns exactly one public_key for the neighbor, it's used."""
bot = _make_bot(bot_prefix="aa")
# First query returns count=1, second returns the key
bot.db_manager.execute_query = Mock(side_effect=[
[{"count": 1}],
[{"public_key": "bb" * 32}],
[], # bot location query
])
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=True)
assert bot.mesh_graph.add_edge.call_count == 2
def test_single_hop_with_ambiguous_db_results(self):
"""When DB returns count != 1, no key is resolved but edges still added."""
bot = _make_bot(bot_prefix="aa")
bot.db_manager.execute_query = Mock(return_value=[{"count": 2}])
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=True)
assert bot.mesh_graph.add_edge.call_count == 2
def test_single_hop_db_exception_handled(self):
"""DB exceptions during key lookup are swallowed; edges still added."""
bot = _make_bot(bot_prefix="aa")
bot.db_manager.execute_query = Mock(side_effect=Exception("DB error"))
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=True)
assert bot.mesh_graph.add_edge.call_count == 2
def test_single_hop_no_meshcore_device(self):
"""No meshcore device → bot_key is None, edges still added."""
bot = _make_bot(bot_prefix="aa")
bot.meshcore = None
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=True)
assert bot.mesh_graph.add_edge.call_count == 2
def test_single_hop_device_pubkey_bytes(self):
"""Device public key as bytes is hex-encoded."""
bot = _make_bot(bot_prefix="aa")
bot.meshcore.device.public_key = b"\xaa\xbb"
update_mesh_graph_from_trace_data(bot, ["bb"], {}, is_our_trace=True)
assert bot.mesh_graph.add_edge.call_count == 2
# ---------------------------------------------------------------------------
# Regular trace (bot is destination, not immediate neighbor)
# ---------------------------------------------------------------------------
class TestRegularTrace:
def test_two_hop_creates_two_edges(self):
"""[a, b] path (bot receives from b via a): two edges expected."""
bot = _make_bot(bot_prefix="cc")
update_mesh_graph_from_trace_data(bot, ["aa", "bb"], {})
# 1 edge: last_node(bb) → bot(cc)
# 1 edge: aa → bb
assert bot.mesh_graph.add_edge.call_count == 2
def test_single_node_path_creates_one_edge(self):
"""Single-node path: last_node → bot."""
bot = _make_bot(bot_prefix="cc")
update_mesh_graph_from_trace_data(bot, ["aa"], {})
assert bot.mesh_graph.add_edge.call_count == 1
def test_three_hop_creates_three_edges(self):
"""[a, b, c] path creates 3 edges."""
bot = _make_bot(bot_prefix="dd")
update_mesh_graph_from_trace_data(bot, ["aa", "bb", "cc"], {})
assert bot.mesh_graph.add_edge.call_count == 3
def test_path_hashes_lowercased(self):
"""Uppercase path hashes are normalized to lowercase."""
bot = _make_bot(bot_prefix="cc")
update_mesh_graph_from_trace_data(bot, ["AA", "BB"], {})
assert bot.mesh_graph.add_edge.call_count == 2
calls = bot.mesh_graph.add_edge.call_args_list
# All prefixes should be lowercase
for c in calls:
kw = c.kwargs
if "from_prefix" in kw:
assert kw["from_prefix"] == kw["from_prefix"].lower()
if "to_prefix" in kw:
assert kw["to_prefix"] == kw["to_prefix"].lower()
def test_last_edge_points_to_bot(self):
"""The last_node→bot edge has to_prefix equal to bot_prefix."""
bot = _make_bot(bot_prefix="cc")
update_mesh_graph_from_trace_data(bot, ["aa"], {})
call_kwargs = bot.mesh_graph.add_edge.call_args.kwargs
assert call_kwargs.get("to_prefix") == "cc"
assert call_kwargs.get("from_prefix") == "aa"
def test_db_exception_in_regular_path_handled(self):
"""DB exceptions don't prevent edges from being added."""
bot = _make_bot(bot_prefix="cc")
bot.db_manager.execute_query = Mock(side_effect=Exception("DB error"))
update_mesh_graph_from_trace_data(bot, ["aa", "bb"], {})
assert bot.mesh_graph.add_edge.call_count == 2
def test_unique_key_resolved_for_last_node(self):
"""When exactly one key matches the last_node prefix, it's used."""
bot = _make_bot(bot_prefix="cc")
bot.db_manager.execute_query = Mock(side_effect=[
[{"count": 1}],
[{"public_key": "aa" * 32}],
])
update_mesh_graph_from_trace_data(bot, ["aa"], {})
assert bot.mesh_graph.add_edge.call_count == 1
def test_mesh_graph_add_edge_exception_propagates(self):
"""Exception from add_edge is not caught (it's a programming error)."""
bot = _make_bot(bot_prefix="cc")
bot.mesh_graph.add_edge = Mock(side_effect=RuntimeError("mesh error"))
with pytest.raises(RuntimeError):
update_mesh_graph_from_trace_data(bot, ["aa"], {})
# ---------------------------------------------------------------------------
# Multi-hop intermediate edges
# ---------------------------------------------------------------------------
class TestMultiHopEdges:
def test_hop_positions_decrease_towards_bot(self):
"""Hop positions are assigned based on distance from bot."""
bot = _make_bot(bot_prefix="dd")
update_mesh_graph_from_trace_data(bot, ["aa", "bb", "cc"], {})
calls = bot.mesh_graph.add_edge.call_args_list
# Collect hop_position values from kwargs
hop_positions = [c.kwargs.get("hop_position") for c in calls]
assert all(h is not None for h in hop_positions)
def test_single_hop_position_is_1(self):
"""Single-hop path has hop_position=1."""
bot = _make_bot(bot_prefix="bb")
update_mesh_graph_from_trace_data(bot, ["aa"], {})
call_kwargs = bot.mesh_graph.add_edge.call_args.kwargs
assert call_kwargs.get("hop_position") == 1
def test_no_meshcore_in_regular_path(self):
"""No meshcore still creates edges."""
bot = _make_bot(bot_prefix="cc")
bot.meshcore = None
update_mesh_graph_from_trace_data(bot, ["aa", "bb"], {})
assert bot.mesh_graph.add_edge.call_count == 2