Files
meshcore-bot/tests/unit/test_command_localized_distance.py
T
CoderNemesis b5fc971148 feat(test): localize test-command distance units (#275)
[Test_Command] distance_unit (auto/km/mi) controls {path_distance} and {firstlast_distance}. Auto follows the reply language (miles for en/en-US, kilometres otherwise, including en-GB). Elapsed times of 1s and above render as seconds to keep the ack short.
2026-09-13 12:49:21 -07:00

180 lines
7.0 KiB
Python

#!/usr/bin/env python3
"""
Unit tests for TestCommand distance units.
[Test_Command] distance_unit decides the unit for the {path_distance} and
{firstlast_distance} placeholders. Under the default 'auto' it follows the reply
language: miles for US English, kilometres everywhere else — en-GB shares the
"en" catalog but not the units, so it stays metric.
"""
import pytest
from modules.commands.test_command import TestCommand as MeshTestCommand
from tests.conftest import mock_message
@pytest.fixture
def build_command(mock_bot):
"""Build a TestCommand whose repeater lookups resolve to fixed coordinates.
distance_unit is read once at construction, so it is a build-time argument.
"""
for section in ('Localization', 'Test_Command'):
if not mock_bot.config.has_section(section):
mock_bot.config.add_section(section)
def _build(distance_unit=None):
if distance_unit is None:
mock_bot.config.remove_option('Test_Command', 'distance_unit')
else:
mock_bot.config.set('Test_Command', 'distance_unit', distance_unit)
cmd = MeshTestCommand(mock_bot)
# Two hops one degree of longitude apart at 47N (~75.8 km/deg), so the
# distance is large enough that km and mi cannot be confused.
coords = {'AA': (47.0, -122.0), 'BB': (47.0, -121.0)}
cmd._lookup_repeater_location = lambda node_id, path_context=None: coords.get(node_id)
return cmd
return _build
@pytest.fixture
def test_command(build_command):
"""TestCommand with the default distance_unit ('auto')."""
return build_command()
def _two_hop_message():
return mock_message(
content="test",
routing_info={'path_length': 2, 'path_nodes': ['AA', 'BB']},
)
def _set_language(cmd, language):
cmd.bot.config.set('Localization', 'language', language)
@pytest.mark.unit
class TestDistanceUnitConfig:
"""distance_unit overrides the language-derived unit."""
def test_defaults_to_auto(self, test_command):
assert test_command.distance_unit == 'auto'
def test_km_forces_metric_for_english(self, build_command):
cmd = build_command('km')
_set_language(cmd, 'en')
assert cmd._format_distance(100.0) == "100.0km"
def test_mi_forces_miles_for_german(self, build_command):
cmd = build_command('mi')
_set_language(cmd, 'de')
assert cmd._format_distance(100.0) == "62.1mi"
def test_value_is_normalized(self, build_command):
cmd = build_command(' MI ')
_set_language(cmd, 'de')
assert cmd.distance_unit == 'mi'
assert cmd._format_distance(100.0) == "62.1mi"
def test_invalid_value_falls_back_to_auto(self, build_command):
cmd = build_command('furlongs')
assert cmd.distance_unit == 'auto'
_set_language(cmd, 'en')
assert cmd._format_distance(100.0) == "62.1mi"
_set_language(cmd, 'de')
assert cmd._format_distance(100.0) == "100.0km"
def test_invalid_value_warns_once_at_startup(self, build_command):
cmd = build_command('furlongs')
warnings = [str(call) for call in cmd.logger.warning.call_args_list]
assert any('distance_unit' in w and 'furlongs' in w for w in warnings)
# Rendering must not re-warn on every reply.
cmd.logger.warning.reset_mock()
cmd._format_distance(100.0)
cmd.logger.warning.assert_not_called()
def test_explicit_unit_reaches_the_placeholders(self, build_command):
cmd = build_command('km')
_set_language(cmd, 'en')
assert cmd._calculate_firstlast_distance(_two_hop_message()) == "75.8km"
@pytest.mark.unit
class TestAutoUnitFollowsLanguage:
"""Under 'auto', _format_distance picks its unit from the reply language."""
def test_english_converts_to_miles(self, test_command):
_set_language(test_command, 'en')
assert test_command._format_distance(100.0) == "62.1mi"
def test_non_english_stays_metric(self, test_command):
_set_language(test_command, 'de')
assert test_command._format_distance(100.0) == "100.0km"
def test_en_us_converts_to_miles(self, test_command):
_set_language(test_command, 'en-US')
assert test_command._format_distance(100.0) == "62.1mi"
def test_en_gb_stays_metric(self, test_command):
# en-GB reads the English catalog but not US units — the same split the
# !gwx unit fix settled on.
_set_language(test_command, 'en-GB')
assert test_command._format_distance(100.0) == "100.0km"
def test_underscore_locale_is_normalized(self, test_command):
_set_language(test_command, 'en_US')
assert test_command._format_distance(100.0) == "62.1mi"
def test_missing_language_defaults_to_miles(self, test_command):
# [Localization] language defaults to 'en' throughout the bot.
assert test_command._format_distance(100.0) == "62.1mi"
def test_response_translator_language_wins(self, test_command):
# An auto-detected sender language must carry the units with it.
_set_language(test_command, 'en')
test_command.bot.translator.language = 'fr'
assert test_command._format_distance(100.0) == "100.0km"
@pytest.mark.unit
class TestPathDistancePlaceholders:
"""The rendered {path_distance} / {firstlast_distance} strings."""
def test_path_distance_in_miles_for_english(self, test_command):
_set_language(test_command, 'en')
rendered = test_command._calculate_path_distance(_two_hop_message())
assert rendered.endswith(" (1 segs)")
assert "mi " in rendered
assert "km" not in rendered
def test_path_distance_in_km_for_spanish(self, test_command):
_set_language(test_command, 'es')
rendered = test_command._calculate_path_distance(_two_hop_message())
assert rendered.endswith(" (1 segs)")
assert "km " in rendered
assert "mi" not in rendered
def test_firstlast_distance_in_miles_for_english(self, test_command):
_set_language(test_command, 'en')
assert test_command._calculate_firstlast_distance(_two_hop_message()) == "47.1mi"
def test_firstlast_distance_in_km_for_spanish(self, test_command):
_set_language(test_command, 'es')
assert test_command._calculate_firstlast_distance(_two_hop_message()) == "75.8km"
def test_miles_value_is_the_converted_km_value(self, test_command):
_set_language(test_command, 'es')
km = float(test_command._calculate_firstlast_distance(_two_hop_message())[:-2])
_set_language(test_command, 'en')
mi = float(test_command._calculate_firstlast_distance(_two_hop_message())[:-2])
assert mi == pytest.approx(km * 0.621371, abs=0.05)
def test_direct_connection_is_unit_agnostic(self, test_command):
_set_language(test_command, 'en')
direct = mock_message(content="test", routing_info={'path_length': 0})
assert test_command._calculate_path_distance(direct) == "N/A"
assert test_command._calculate_firstlast_distance(direct) == "N/A"