Files
meshcore-bot/tests/unit/test_path_command_graph.py
agessaman d699ea1cf1 Update configuration handling and validation for bot sections
- Enhanced .gitignore to allow test files in the tests/ directory and committed pytest.ini for test discovery.
- Added checks for missing sections in configuration files, specifically for Admin_ACL and Banned_Users, to prevent errors during bot startup.
- Updated generate_website.py and command_manager.py to handle cases where required sections are absent, returning empty lists instead of raising exceptions.
- Introduced optional dependencies for testing in pyproject.toml, ensuring a smoother development experience.
- Improved localization handling in core.py to default to English when the Localization section is missing, enhancing user experience.
2026-02-12 19:23:35 -08:00

259 lines
10 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