mirror of
https://github.com/openhop-dev/openhop_repeater.git
synced 2026-09-03 19:33:44 +00:00
214 lines
6.9 KiB
Python
214 lines
6.9 KiB
Python
import copy
|
|
import logging
|
|
|
|
import yaml
|
|
|
|
from repeater.modem_config import normalize_modem_config, redact_modem_tokens_in_place
|
|
|
|
|
|
def test_canonical_modem_config_is_unchanged_and_input_is_not_mutated():
|
|
original = {
|
|
"radio_type": "modem_tcp",
|
|
"modem_tcp": {"host": "modem.local", "token": "secret"},
|
|
"modem_usb": {"port": "/dev/ttyACM0"},
|
|
}
|
|
before = copy.deepcopy(original)
|
|
|
|
normalized = normalize_modem_config(original)
|
|
|
|
assert normalized == before
|
|
assert original == before
|
|
assert normalized is not original
|
|
|
|
|
|
def test_legacy_top_level_type_and_section_are_canonicalized():
|
|
normalized = normalize_modem_config(
|
|
{"radio_type": "pymc_tcp", "pymc_tcp": {"host": "modem.local"}}
|
|
)
|
|
|
|
assert normalized == {
|
|
"radio_type": "modem_tcp",
|
|
"modem_tcp": {"host": "modem.local"},
|
|
}
|
|
|
|
|
|
def test_canonical_section_wins_conflict_without_logging_values(caplog):
|
|
config = {
|
|
"radio_type": "pymc_tcp",
|
|
"pymc_tcp": {"host": "legacy.example", "token": "legacy-secret"},
|
|
"modem_tcp": {"host": "canonical.example", "token": "canonical-secret"},
|
|
}
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
normalized = normalize_modem_config(config)
|
|
|
|
assert normalized["radio_type"] == "modem_tcp"
|
|
assert normalized["modem_tcp"]["host"] == "canonical.example"
|
|
assert "pymc_tcp" not in normalized
|
|
assert "pymc_tcp" in caplog.text and "modem_tcp" in caplog.text
|
|
assert "legacy.example" not in caplog.text
|
|
assert "canonical.example" not in caplog.text
|
|
assert "legacy-secret" not in caplog.text
|
|
assert "canonical-secret" not in caplog.text
|
|
|
|
|
|
def test_each_multi_radio_entry_is_normalized_independently():
|
|
normalized = normalize_modem_config(
|
|
{
|
|
"radios": [
|
|
{"id": "tcp", "radio_type": "pymc_tcp", "pymc_tcp": {"host": "x"}},
|
|
{
|
|
"id": "usb",
|
|
"radio_type": "modem_usb",
|
|
"pymc_usb": {"port": "old"},
|
|
"modem_usb": {"port": "new"},
|
|
},
|
|
"malformed",
|
|
]
|
|
}
|
|
)
|
|
|
|
assert normalized["radios"][0] == {
|
|
"id": "tcp",
|
|
"radio_type": "modem_tcp",
|
|
"modem_tcp": {"host": "x"},
|
|
}
|
|
assert normalized["radios"][1]["radio_type"] == "modem_usb"
|
|
assert normalized["radios"][1]["modem_usb"] == {"port": "new"}
|
|
assert "pymc_usb" not in normalized["radios"][1]
|
|
assert normalized["radios"][2] == "malformed"
|
|
|
|
|
|
def test_unrelated_and_malformed_values_are_preserved():
|
|
config = {
|
|
"radio_type": "kiss",
|
|
"pymc_tcp": "not-a-mapping",
|
|
"modem_tcp": ["also", "not", "a", "mapping"],
|
|
"radios": None,
|
|
}
|
|
|
|
normalized = normalize_modem_config(config)
|
|
|
|
assert normalized["radio_type"] == "kiss"
|
|
assert normalized["modem_tcp"] == ["also", "not", "a", "mapping"]
|
|
assert "pymc_tcp" not in normalized
|
|
assert normalized["radios"] is None
|
|
|
|
|
|
def test_load_config_normalizes_in_memory_without_rewriting_file(tmp_path):
|
|
from repeater.config import load_config
|
|
|
|
path = tmp_path / "config.yaml"
|
|
persisted = {
|
|
"radio_type": "pymc_usb",
|
|
"pymc_usb": {"port": "/dev/ttyACM0"},
|
|
"storage": {"storage_dir": str(tmp_path / "data")},
|
|
"repeater": {"security": {}},
|
|
}
|
|
path.write_text(yaml.safe_dump(persisted), encoding="utf-8")
|
|
before = path.read_text(encoding="utf-8")
|
|
|
|
loaded = load_config(str(path))
|
|
|
|
assert loaded["radio_type"] == "modem_usb"
|
|
assert loaded["modem_usb"]["port"] == "/dev/ttyACM0"
|
|
assert "pymc_usb" not in loaded
|
|
assert path.read_text(encoding="utf-8") == before
|
|
|
|
|
|
def test_config_manager_save_lazily_canonicalizes_in_memory_and_yaml(tmp_path):
|
|
from repeater.config_manager import ConfigManager
|
|
|
|
path = tmp_path / "config.yaml"
|
|
config = {
|
|
"radio_type": "pymc_tcp",
|
|
"pymc_tcp": {"host": "legacy.local", "token": "secret"},
|
|
"radios": [{"id": "usb", "radio_type": "pymc_usb", "pymc_usb": {"port": "/dev/x"}}],
|
|
}
|
|
|
|
assert ConfigManager(str(path), config).save_to_file() is True
|
|
|
|
written = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
assert config == written
|
|
assert written["radio_type"] == "modem_tcp"
|
|
assert "pymc_tcp" not in written
|
|
assert written["radios"][0]["radio_type"] == "modem_usb"
|
|
assert "pymc_usb" not in written["radios"][0]
|
|
|
|
|
|
def test_save_config_serializes_only_canonical_modem_keys(tmp_path):
|
|
from repeater.config import save_config
|
|
|
|
path = tmp_path / "config.yaml"
|
|
config = {
|
|
"radio_type": "pymc_usb",
|
|
"pymc_usb": {"port": "/dev/ttyACM0"},
|
|
}
|
|
|
|
assert save_config(config, str(path)) is True
|
|
|
|
written = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
assert written == {
|
|
"radio_type": "modem_usb",
|
|
"modem_usb": {"port": "/dev/ttyACM0"},
|
|
}
|
|
|
|
|
|
def test_sensor_and_gps_aliases_migrate_to_canonical_names():
|
|
normalized = normalize_modem_config(
|
|
{
|
|
"sensors": {
|
|
"definitions": [
|
|
{"name": "modem", "type": "pymc_modem", "settings": {"host": "x"}},
|
|
{"name": "system", "type": "hardware_stats"},
|
|
]
|
|
},
|
|
"gps": {"source": "pymc_modem"},
|
|
}
|
|
)
|
|
|
|
assert normalized["sensors"]["definitions"][0]["type"] == "openhop_modem"
|
|
assert normalized["sensors"]["definitions"][1]["type"] == "hardware_stats"
|
|
assert normalized["gps"]["source"] == "modem_http"
|
|
|
|
|
|
def test_unrelated_sensor_and_gps_names_keep_original_spelling():
|
|
normalized = normalize_modem_config(
|
|
{
|
|
"sensors": {"definitions": [{"name": "custom", "type": "Vendor_Custom"}]},
|
|
"gps": {"source": "Vendor_Source"},
|
|
}
|
|
)
|
|
|
|
assert normalized["sensors"]["definitions"][0]["type"] == "Vendor_Custom"
|
|
assert normalized["gps"]["source"] == "Vendor_Source"
|
|
|
|
|
|
def test_redact_modem_tokens_handles_top_level_and_multi_radio_without_mutating_others():
|
|
config = {
|
|
"modem_tcp": {"host": "root.local", "token": "root-secret"},
|
|
"radios": [
|
|
{"id": "tcp", "modem_tcp": {"host": "nested.local", "token": "nested-secret"}},
|
|
{"id": "usb", "modem_usb": {"port": "/dev/ttyACM0"}},
|
|
"malformed",
|
|
],
|
|
}
|
|
|
|
redact_modem_tokens_in_place(config)
|
|
|
|
assert config["modem_tcp"] == {"host": "root.local"}
|
|
assert config["radios"][0]["modem_tcp"] == {"host": "nested.local"}
|
|
assert config["radios"][1]["modem_usb"] == {"port": "/dev/ttyACM0"}
|
|
|
|
|
|
def test_redact_modem_tokens_can_replace_tokens_with_export_sentinel():
|
|
config = {
|
|
"modem_tcp": {"token": "root-secret"},
|
|
"radios": [{"modem_tcp": {"token": "nested-secret"}}],
|
|
}
|
|
|
|
redact_modem_tokens_in_place(config, replacement="*** REDACTED ***")
|
|
|
|
assert config["modem_tcp"]["token"] == "*** REDACTED ***"
|
|
assert config["radios"][0]["modem_tcp"]["token"] == "*** REDACTED ***"
|