mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-03-30 12:05:38 +00:00
- 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.
259 lines
10 KiB
Python
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
|