Files
meshcore-stats/tests/config/test_env.py
Jorijn Schrijvershof 137bbe3c66 feat: add telemetry chart discovery and unit display (#109)
* add telemetry chart discovery and unit display

* fix: tests were failing
2026-02-09 13:12:44 +01:00

229 lines
8.6 KiB
Python

"""Tests for environment variable parsing and Config class."""
import pytest
from meshmon.env import (
Config,
get_bool,
get_config,
get_int,
get_str,
)
class TestGetStrEdgeCases:
"""Additional edge case tests for get_str."""
def test_whitespace_value_preserved(self, monkeypatch):
"""Whitespace-only value is preserved."""
monkeypatch.setenv("TEST_VAR", " ")
assert get_str("TEST_VAR") == " "
def test_special_characters(self, monkeypatch):
"""Special characters are preserved."""
monkeypatch.setenv("TEST_VAR", "hello@world#123!")
assert get_str("TEST_VAR") == "hello@world#123!"
class TestGetIntEdgeCases:
"""Additional edge case tests for get_int."""
def test_leading_zeros(self, monkeypatch):
"""Leading zeros work (not octal)."""
monkeypatch.setenv("TEST_INT", "042")
assert get_int("TEST_INT", 0) == 42
def test_whitespace_around_number(self, monkeypatch):
"""Whitespace around number is tolerated by int()."""
monkeypatch.setenv("TEST_INT", " 42 ")
# Python's int() handles whitespace
assert get_int("TEST_INT", 0) == 42
class TestGetBoolEdgeCases:
"""Additional edge case tests for get_bool."""
def test_mixed_case(self, monkeypatch):
"""Mixed case variants work."""
monkeypatch.setenv("TEST_BOOL", "TrUe")
assert get_bool("TEST_BOOL") is True
def test_with_spaces(self, monkeypatch):
"""Whitespace causes a non-match since get_bool does not strip."""
monkeypatch.setenv("TEST_BOOL", " yes ")
# .lower() doesn't strip, so " yes " != "yes"
# This will return False
assert get_bool("TEST_BOOL") is False
class TestConfigComplete:
"""Complete Config class tests."""
def test_all_connection_settings(self, clean_env, monkeypatch):
"""All connection settings are loaded."""
monkeypatch.setenv("MESH_TRANSPORT", "tcp")
monkeypatch.setenv("MESH_SERIAL_PORT", "/dev/ttyUSB0")
monkeypatch.setenv("MESH_SERIAL_BAUD", "9600")
monkeypatch.setenv("MESH_TCP_HOST", "192.168.1.1")
monkeypatch.setenv("MESH_TCP_PORT", "8080")
monkeypatch.setenv("MESH_BLE_ADDR", "AA:BB:CC:DD:EE:FF")
monkeypatch.setenv("MESH_BLE_PIN", "1234")
monkeypatch.setenv("MESH_DEBUG", "true")
config = Config()
assert config.mesh_transport == "tcp"
assert config.mesh_serial_port == "/dev/ttyUSB0"
assert config.mesh_serial_baud == 9600
assert config.mesh_tcp_host == "192.168.1.1"
assert config.mesh_tcp_port == 8080
assert config.mesh_ble_addr == "AA:BB:CC:DD:EE:FF"
assert config.mesh_ble_pin == "1234"
assert config.mesh_debug is True
def test_all_repeater_settings(self, clean_env, monkeypatch):
"""All repeater identity settings are loaded."""
monkeypatch.setenv("REPEATER_NAME", "HilltopRepeater")
monkeypatch.setenv("REPEATER_KEY_PREFIX", "abc123")
monkeypatch.setenv("REPEATER_PASSWORD", "secret")
monkeypatch.setenv("REPEATER_DISPLAY_NAME", "Hilltop Relay")
monkeypatch.setenv("REPEATER_PUBKEY_PREFIX", "!abc123")
monkeypatch.setenv("REPEATER_HARDWARE", "RAK4631 with Solar")
config = Config()
assert config.repeater_name == "HilltopRepeater"
assert config.repeater_key_prefix == "abc123"
assert config.repeater_password == "secret"
assert config.repeater_display_name == "Hilltop Relay"
assert config.repeater_pubkey_prefix == "!abc123"
assert config.repeater_hardware == "RAK4631 with Solar"
def test_all_timeout_settings(self, clean_env, monkeypatch):
"""All timeout and retry settings are loaded."""
monkeypatch.setenv("REMOTE_TIMEOUT_S", "30")
monkeypatch.setenv("REMOTE_RETRY_ATTEMPTS", "5")
monkeypatch.setenv("REMOTE_RETRY_BACKOFF_S", "10")
monkeypatch.setenv("REMOTE_CB_FAILS", "10")
monkeypatch.setenv("REMOTE_CB_COOLDOWN_S", "7200")
config = Config()
assert config.remote_timeout_s == 30
assert config.remote_retry_attempts == 5
assert config.remote_retry_backoff_s == 10
assert config.remote_cb_fails == 10
assert config.remote_cb_cooldown_s == 7200
def test_all_telemetry_settings(self, clean_env, monkeypatch):
"""All telemetry settings are loaded."""
monkeypatch.setenv("TELEMETRY_ENABLED", "yes")
monkeypatch.setenv("TELEMETRY_TIMEOUT_S", "20")
monkeypatch.setenv("TELEMETRY_RETRY_ATTEMPTS", "3")
monkeypatch.setenv("TELEMETRY_RETRY_BACKOFF_S", "5")
config = Config()
assert config.telemetry_enabled is True
assert config.telemetry_timeout_s == 20
assert config.telemetry_retry_attempts == 3
assert config.telemetry_retry_backoff_s == 5
def test_display_unit_system_defaults_to_metric(self, clean_env):
"""DISPLAY_UNIT_SYSTEM defaults to metric."""
config = Config()
assert config.display_unit_system == "metric"
def test_display_unit_system_accepts_imperial(self, clean_env, monkeypatch):
"""DISPLAY_UNIT_SYSTEM=imperial is honored."""
monkeypatch.setenv("DISPLAY_UNIT_SYSTEM", "imperial")
config = Config()
assert config.display_unit_system == "imperial"
def test_display_unit_system_invalid_falls_back_to_metric(self, clean_env, monkeypatch):
"""Invalid DISPLAY_UNIT_SYSTEM falls back to metric."""
monkeypatch.setenv("DISPLAY_UNIT_SYSTEM", "kelvin")
config = Config()
assert config.display_unit_system == "metric"
def test_all_location_settings(self, clean_env, monkeypatch):
"""All location/report settings are loaded."""
monkeypatch.setenv("REPORT_LOCATION_NAME", "Mountain Peak Observatory")
monkeypatch.setenv("REPORT_LOCATION_SHORT", "Mountain Peak")
monkeypatch.setenv("REPORT_LAT", "46.8523")
monkeypatch.setenv("REPORT_LON", "9.5369")
monkeypatch.setenv("REPORT_ELEV", "2500")
monkeypatch.setenv("REPORT_ELEV_UNIT", "ft")
config = Config()
assert config.report_location_name == "Mountain Peak Observatory"
assert config.report_location_short == "Mountain Peak"
assert config.report_lat == pytest.approx(46.8523)
assert config.report_lon == pytest.approx(9.5369)
assert config.report_elev == pytest.approx(2500)
assert config.report_elev_unit == "ft"
def test_all_radio_settings(self, clean_env, monkeypatch):
"""All radio configuration settings are loaded."""
monkeypatch.setenv("RADIO_FREQUENCY", "915.000 MHz")
monkeypatch.setenv("RADIO_BANDWIDTH", "125 kHz")
monkeypatch.setenv("RADIO_SPREAD_FACTOR", "SF12")
monkeypatch.setenv("RADIO_CODING_RATE", "CR5")
config = Config()
assert config.radio_frequency == "915.000 MHz"
assert config.radio_bandwidth == "125 kHz"
assert config.radio_spread_factor == "SF12"
assert config.radio_coding_rate == "CR5"
def test_companion_settings(self, clean_env, monkeypatch):
"""Companion display settings are loaded."""
monkeypatch.setenv("COMPANION_DISPLAY_NAME", "Base Station")
monkeypatch.setenv("COMPANION_PUBKEY_PREFIX", "!def456")
monkeypatch.setenv("COMPANION_HARDWARE", "T-Beam Supreme")
config = Config()
assert config.companion_display_name == "Base Station"
assert config.companion_pubkey_prefix == "!def456"
assert config.companion_hardware == "T-Beam Supreme"
class TestGetConfigSingleton:
"""Tests for get_config singleton behavior."""
def test_config_persists_across_calls(self, clean_env, monkeypatch):
"""Config values persist across multiple get_config calls."""
monkeypatch.setenv("MESH_TRANSPORT", "tcp")
config1 = get_config()
assert config1.mesh_transport == "tcp"
# Change env var - should NOT affect cached config
monkeypatch.setenv("MESH_TRANSPORT", "ble")
config2 = get_config()
assert config2.mesh_transport == "tcp" # Still tcp, cached
assert config1 is config2
def test_reset_allows_new_config(self, clean_env, monkeypatch):
"""Resetting singleton allows new config."""
monkeypatch.setenv("MESH_TRANSPORT", "tcp")
config1 = get_config()
assert config1.mesh_transport == "tcp"
# Reset singleton
import meshmon.env
meshmon.env._config = None
# Change env var
monkeypatch.setenv("MESH_TRANSPORT", "ble")
config2 = get_config()
assert config2.mesh_transport == "ble"
assert config1 is not config2