mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-04-02 13:35:38 +00:00
- Added new settings for the topology engine in `config.ini.example` to support legacy, shadow, and new modes. - Updated documentation to include details on the probabilistic topology engine and its configuration options. - Enhanced `MeshCoreBot` to initialize the topology engine and log its status. - Introduced new database tables for shadow topology inference, ghost nodes, and model metrics in `RepeaterManager`. - Modified `PathCommand` to utilize the topology engine for repeater selection and comparison telemetry. - Updated web viewer templates to display topology engine mode and enable topology validation features. - Added JavaScript functions to handle data mode switching between legacy and model graphs.
311 lines
12 KiB
Python
311 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Unit tests for PathCommand graph-based selection logic
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
from datetime import datetime
|
|
from modules.commands.path_command import PathCommand
|
|
from tests.helpers import create_test_repeater, create_test_edge, populate_test_graph
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPathCommandGraphSelection:
|
|
"""Test PathCommand._select_repeater_by_graph method."""
|
|
|
|
def test_select_repeater_by_graph_no_graph(self, mock_bot):
|
|
"""Test when graph_based_validation is False."""
|
|
# Disable graph validation
|
|
mock_bot.config.set('Path_Command', 'graph_based_validation', 'false')
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
repeaters = [create_test_repeater('01', 'Test Repeater')]
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '01', ['01'])
|
|
|
|
assert result == (None, 0.0, None)
|
|
|
|
def test_select_repeater_by_graph_no_mesh_graph(self, mock_bot):
|
|
"""Test when mesh_graph is None."""
|
|
mock_bot.mesh_graph = None
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
repeaters = [create_test_repeater('01', 'Test Repeater')]
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '01', ['01'])
|
|
|
|
assert result == (None, 0.0, None)
|
|
|
|
def test_select_repeater_by_graph_no_context(self, mock_bot, mesh_graph):
|
|
"""Test when node_id not in path_context."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
repeaters = [create_test_repeater('99', 'Test Repeater')]
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '99', ['01', '7e', '86'])
|
|
|
|
assert result == (None, 0.0, None)
|
|
|
|
def test_select_repeater_by_graph_direct_edge(self, mock_bot, mesh_graph):
|
|
"""Test selection with strong direct edge."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
# Create strong edge: 01 -> 7e
|
|
mesh_graph.add_edge('01', '7e')
|
|
for _ in range(10):
|
|
mesh_graph.add_edge('01', '7e') # 11 total observations
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
# Create repeaters with matching prefix
|
|
repeaters = [
|
|
create_test_repeater('7e', 'Test Repeater 7e', public_key='7e' * 32)
|
|
]
|
|
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e', '86'])
|
|
|
|
assert result[0] is not None # Should select a repeater
|
|
assert result[1] > 0.7 # High confidence
|
|
assert result[2] == 'graph' # Direct edge method
|
|
|
|
def test_select_repeater_by_graph_stored_public_key_bonus(self, mock_bot, mesh_graph):
|
|
"""Test stored public key bonus."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
# Create edge with stored public key
|
|
public_key = '7e' * 32 # 64 hex chars
|
|
mesh_graph.add_edge('01', '7e', from_public_key='01' * 32, to_public_key=public_key)
|
|
for _ in range(5):
|
|
mesh_graph.add_edge('01', '7e')
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
# Create repeater with matching public key
|
|
repeaters = [
|
|
create_test_repeater('7e', 'Test Repeater', public_key=public_key),
|
|
create_test_repeater('7e', 'Other Repeater', public_key='aa' * 32) # Different key
|
|
]
|
|
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e', '86'])
|
|
|
|
assert result[0] is not None
|
|
# Should select the repeater with matching public key
|
|
assert result[0]['public_key'] == public_key
|
|
assert result[1] > 0.5 # Should have good confidence with stored key bonus
|
|
|
|
def test_select_repeater_by_graph_star_bias(self, mock_bot, mesh_graph):
|
|
"""Test star bias multiplier application."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
# Create edges for both repeaters
|
|
mesh_graph.add_edge('01', '7e')
|
|
mesh_graph.add_edge('01', '7a')
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
# Create one starred and one non-starred repeater
|
|
repeaters = [
|
|
create_test_repeater('7e', 'Starred Repeater', is_starred=True),
|
|
create_test_repeater('7a', 'Regular Repeater', is_starred=False)
|
|
]
|
|
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e'])
|
|
|
|
assert result[0] is not None
|
|
# Starred repeater should be selected even if both have similar graph scores
|
|
assert result[0]['is_starred'] is True
|
|
|
|
def test_select_repeater_by_graph_multi_hop(self, mock_bot, mesh_graph):
|
|
"""Test multi-hop inference when direct edge has low confidence."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
# Create 2-hop path: 01 -> 7e -> 86 (no direct 01 -> 86)
|
|
# Need at least 3 observations per edge for min_edge_observations
|
|
for _ in range(3):
|
|
mesh_graph.add_edge('01', '7e')
|
|
mesh_graph.add_edge('7e', '86')
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
# Create repeater that's the intermediate node
|
|
repeaters = [
|
|
create_test_repeater('7e', 'Intermediate Repeater', public_key='7e' * 32)
|
|
]
|
|
|
|
# Try to select 7e when path is 01 -> 7e -> 86
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e', '86'])
|
|
|
|
assert result[0] is not None
|
|
# Should use graph method (direct edge exists)
|
|
assert result[2] in ('graph', 'graph_multihop')
|
|
|
|
def test_select_repeater_by_graph_hop_position(self, mock_bot, mesh_graph):
|
|
"""Test hop position validation."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
# Create edge with avg_hop_position = 1.0
|
|
# Need at least 3 observations for min_edge_observations
|
|
for _ in range(3):
|
|
mesh_graph.add_edge('01', '7e', hop_position=1)
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
path_cmd.graph_use_hop_position = True
|
|
|
|
repeaters = [create_test_repeater('7e', 'Test Repeater')]
|
|
|
|
# Path where 7e is at position 1
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e', '86'])
|
|
|
|
assert result[0] is not None
|
|
assert result[1] > 0.0
|
|
|
|
def test_select_repeater_by_graph_multiple_candidates(self, mock_bot, mesh_graph):
|
|
"""Test selection from multiple candidates."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
# Create edges with different strengths
|
|
mesh_graph.add_edge('01', '7e')
|
|
for _ in range(10):
|
|
mesh_graph.add_edge('01', '7e') # Strong edge
|
|
|
|
mesh_graph.add_edge('01', '7a')
|
|
for _ in range(2):
|
|
mesh_graph.add_edge('01', '7a') # Weaker edge
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
repeaters = [
|
|
create_test_repeater('7e', 'Strong Edge Repeater'),
|
|
create_test_repeater('7a', 'Weak Edge Repeater')
|
|
]
|
|
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e'])
|
|
|
|
assert result[0] is not None
|
|
# Should select the one with stronger edge (7e)
|
|
assert result[0]['name'] == 'Strong Edge Repeater'
|
|
assert result[1] > 0.5
|
|
|
|
def test_select_repeater_by_graph_confidence_conversion(self, mock_bot, mesh_graph):
|
|
"""Test graph score to confidence conversion."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
# Create very strong edge
|
|
mesh_graph.add_edge('01', '7e')
|
|
for _ in range(20):
|
|
mesh_graph.add_edge('01', '7e')
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
repeaters = [create_test_repeater('7e', 'Test Repeater')]
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e'])
|
|
|
|
assert result[0] is not None
|
|
# Confidence should be capped at 1.0
|
|
assert 0.0 <= result[1] <= 1.0
|
|
|
|
def test_select_repeater_by_graph_star_bias_exceeds_one(self, mock_bot, mesh_graph):
|
|
"""Test star bias can exceed 1.0 but confidence is normalized."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
mesh_graph.add_edge('01', '7e')
|
|
for _ in range(5):
|
|
mesh_graph.add_edge('01', '7e')
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
path_cmd.star_bias_multiplier = 2.5 # High multiplier
|
|
|
|
# Create starred repeater
|
|
repeaters = [create_test_repeater('7e', 'Starred Repeater', is_starred=True)]
|
|
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e'])
|
|
|
|
assert result[0] is not None
|
|
# Confidence should still be capped appropriately
|
|
assert 0.0 <= result[1] <= 1.0
|
|
|
|
def test_select_repeater_by_graph_prefix_extraction(self, mock_bot, mesh_graph):
|
|
"""Test prefix extraction from public_key."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
# Create edge
|
|
mesh_graph.add_edge('01', '7e')
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
# Repeater with public key starting with '7e'
|
|
public_key = '7e' + '00' * 31 # 7e prefix
|
|
repeaters = [create_test_repeater('7e', 'Test Repeater', public_key=public_key)]
|
|
|
|
result = path_cmd._select_repeater_by_graph(repeaters, '7e', ['01', '7e'])
|
|
|
|
assert result[0] is not None
|
|
|
|
def test_select_repeater_by_graph_missing_public_key(self, mock_bot, mesh_graph):
|
|
"""Test handling when public_key is missing."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
|
|
mesh_graph.add_edge('01', '7e')
|
|
|
|
path_cmd = PathCommand(mock_bot)
|
|
|
|
# Repeater without public_key
|
|
repeater = create_test_repeater('7e', 'Test Repeater')
|
|
del repeater['public_key'] # Remove public key
|
|
|
|
result = path_cmd._select_repeater_by_graph([repeater], '7e', ['01', '7e'])
|
|
|
|
# Should skip this repeater (no prefix to match)
|
|
assert result == (None, 0.0, None) or result[0] is None
|
|
|
|
def test_select_repeater_by_graph_path_validation_bonus_applies_without_remainder(self, mock_bot, mesh_graph):
|
|
"""Path-validation bonus should apply for well-formed decoded paths (no length remainder)."""
|
|
mock_bot.mesh_graph = mesh_graph
|
|
path_cmd = PathCommand(mock_bot)
|
|
path_cmd.graph_multi_hop_enabled = False
|
|
|
|
mock_bot.db_manager.create_table(
|
|
"observed_paths",
|
|
"""
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
public_key TEXT,
|
|
packet_hash TEXT,
|
|
from_prefix TEXT NOT NULL,
|
|
to_prefix TEXT NOT NULL,
|
|
path_hex TEXT NOT NULL,
|
|
path_length INTEGER NOT NULL,
|
|
bytes_per_hop INTEGER,
|
|
packet_type TEXT NOT NULL,
|
|
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
observation_count INTEGER DEFAULT 1
|
|
""",
|
|
)
|
|
candidate_key = "7e" * 32
|
|
mock_bot.db_manager.execute_update(
|
|
"""
|
|
INSERT INTO observed_paths
|
|
(public_key, packet_hash, from_prefix, to_prefix, path_hex, path_length, bytes_per_hop, packet_type, first_seen, last_seen, observation_count)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""",
|
|
(
|
|
candidate_key,
|
|
"pkt-path-bonus",
|
|
"01",
|
|
"86",
|
|
"017e86",
|
|
3,
|
|
1,
|
|
"advert",
|
|
datetime.now().isoformat(),
|
|
datetime.now().isoformat(),
|
|
25,
|
|
),
|
|
)
|
|
|
|
repeaters = [create_test_repeater("7e", "PathBonusCandidate", public_key=candidate_key)]
|
|
selected, confidence, method = path_cmd._select_repeater_by_graph(repeaters, "7e", ["01", "7e", "86"])
|
|
assert selected is not None
|
|
assert selected["public_key"] == candidate_key
|
|
assert confidence > 0.0
|
|
assert method == "graph"
|