Files
meshcore-bot/tests/test_utils.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

188 lines
6.2 KiB
Python

"""Tests for modules.utils."""
import pytest
from unittest.mock import MagicMock
from modules.utils import (
abbreviate_location,
truncate_string,
decode_escape_sequences,
parse_location_string,
calculate_distance,
format_elapsed_display,
parse_path_string,
)
class TestAbbreviateLocation:
"""Tests for abbreviate_location()."""
def test_empty_returns_empty(self):
assert abbreviate_location("") == ""
assert abbreviate_location(None) is None
def test_under_max_length_unchanged(self):
assert abbreviate_location("Seattle", max_length=20) == "Seattle"
assert abbreviate_location("Portland, OR", max_length=20) == "Portland, OR"
def test_united_states_abbreviated(self):
assert "USA" in abbreviate_location("United States of America", max_length=50)
assert abbreviate_location("United States", max_length=50) == "USA"
def test_british_columbia_abbreviated(self):
assert abbreviate_location("Vancouver, British Columbia", max_length=50) == "Vancouver, BC"
def test_over_max_truncates_with_ellipsis(self):
result = abbreviate_location("Very Long City Name That Exceeds Limit", max_length=20)
assert len(result) <= 20
assert result.endswith("...")
def test_comma_separated_keeps_first_part_when_truncating(self):
result = abbreviate_location("Seattle, Washington, USA", max_length=10)
assert "Seattle" in result or result.startswith("Seattle")
class TestTruncateString:
"""Tests for truncate_string()."""
def test_empty_returns_empty(self):
assert truncate_string("", 10) == ""
assert truncate_string(None, 10) is None
def test_under_max_unchanged(self):
assert truncate_string("hello", 10) == "hello"
def test_over_max_truncates_with_ellipsis(self):
assert truncate_string("hello world", 8) == "hello..."
assert truncate_string("hello world", 11) == "hello world"
def test_custom_ellipsis(self):
# max_length=8 with ellipsis=".." (2 chars) -> 6 chars + ".."
assert truncate_string("hello world", 8, ellipsis="..") == "hello .."
class TestDecodeEscapeSequences:
"""Tests for decode_escape_sequences()."""
def test_empty_returns_empty(self):
assert decode_escape_sequences("") == ""
assert decode_escape_sequences(None) is None
def test_newline(self):
assert decode_escape_sequences(r"Line 1\nLine 2") == "Line 1\nLine 2"
def test_tab(self):
assert decode_escape_sequences(r"Col1\tCol2") == "Col1\tCol2"
def test_literal_backslash_n(self):
assert decode_escape_sequences(r"Literal \\n here") == "Literal \\n here"
def test_mixed(self):
result = decode_escape_sequences(r"Line 1\nLine 2\tTab")
assert "Line 1" in result
assert "\n" in result
assert "\t" in result
def test_carriage_return(self):
assert decode_escape_sequences(r"Line1\r\nLine2") == "Line1\r\nLine2"
class TestParseLocationString:
"""Tests for parse_location_string()."""
def test_no_comma_returns_city_only(self):
city, second, kind = parse_location_string("Seattle")
assert city == "Seattle"
assert second is None
assert kind is None
def test_zipcode_only(self):
city, second, kind = parse_location_string("98101")
assert city == "98101"
assert second is None
assert kind is None
def test_city_state_format(self):
city, second, kind = parse_location_string("Seattle, WA")
assert city == "Seattle"
assert second is not None
assert kind in ("state", None)
def test_city_country_format(self):
city, second, kind = parse_location_string("Stockholm, Sweden")
assert city == "Stockholm"
assert second is not None
class TestCalculateDistance:
"""Tests for calculate_distance() (Haversine)."""
def test_same_point_zero_distance(self):
assert calculate_distance(47.6062, -122.3321, 47.6062, -122.3321) == 0.0
def test_known_distance_seattle_portland(self):
# Seattle to Portland ~233 km
dist = calculate_distance(47.6062, -122.3321, 45.5152, -122.6784)
assert 220 < dist < 250
def test_known_distance_short(self):
# ~1 degree lat at equator ~111 km
dist = calculate_distance(0, 0, 1, 0)
assert 110 < dist < 112
class TestFormatElapsedDisplay:
"""Tests for format_elapsed_display()."""
def test_none_returns_sync_message(self):
assert "Sync" in format_elapsed_display(None)
assert "Clock" in format_elapsed_display(None)
def test_unknown_returns_sync_message(self):
assert "Sync" in format_elapsed_display("unknown")
def test_invalid_type_returns_sync_message(self):
assert "Sync" in format_elapsed_display("not_a_number")
def test_valid_recent_timestamp_returns_ms(self):
import time
ts = time.time() - 1.5 # 1.5 seconds ago
result = format_elapsed_display(ts)
assert "ms" in result
assert "Sync" not in result
def test_future_timestamp_returns_sync_message(self):
import time
ts = time.time() + 3600 # 1 hour in future
assert "Sync" in format_elapsed_display(ts)
def test_translator_used_when_provided(self):
translator = MagicMock()
translator.translate = MagicMock(return_value="Custom Sync Message")
result = format_elapsed_display(None, translator=translator)
assert result == "Custom Sync Message"
class TestParsePathString:
"""Tests for parse_path_string()."""
def test_empty_returns_empty_list(self):
assert parse_path_string("") == []
assert parse_path_string(None) == []
def test_comma_separated(self):
assert parse_path_string("01,5f,ab") == ["01", "5F", "AB"]
def test_space_separated(self):
assert parse_path_string("01 5f ab") == ["01", "5F", "AB"]
def test_continuous_hex(self):
assert parse_path_string("015fab") == ["01", "5F", "AB"]
def test_with_hop_count_suffix(self):
result = parse_path_string("01,5f (2 hops)")
assert result == ["01", "5F"]
def test_mixed_case_normalized_uppercase(self):
assert parse_path_string("01,5f,aB") == ["01", "5F", "AB"]