Files
meshcore-stats/tests/config/test_config_file.py
Jorijn Schrijvershof ca13e31aae test: stabilize suite and broaden integration coverage (#32)
* tests: cache integration/report fixtures to speed up tests

* fix: speed up yearly aggregation and refresh timings report

* chore: remove the report

* fix: unrecognized named-value: 'runner'. Located at position 1 within expression: runner.temp

* fix: ruff linting error

* test: strengthen assertions and stabilize tests

* test(integration): expand rendered chart metrics
2026-01-08 21:20:34 +01:00

254 lines
9.0 KiB
Python

"""Tests for meshcore.conf file parsing."""
import os
from meshmon.env import _parse_config_value
def _load_config_from_content(tmp_path, monkeypatch, content: str | None) -> None:
import meshmon.env as env
config_path = tmp_path / "meshcore.conf"
if content is not None:
config_path.write_text(content)
fake_env_path = tmp_path / "src" / "meshmon" / "env.py"
fake_env_path.parent.mkdir(parents=True, exist_ok=True)
fake_env_path.write_text("")
monkeypatch.setattr(env, "__file__", str(fake_env_path))
env._load_config_file()
class TestParseConfigValueDetailed:
"""Detailed tests for _parse_config_value."""
# ==========================================================================
# Empty/whitespace handling
# ==========================================================================
def test_empty_string(self):
assert _parse_config_value("") == ""
def test_only_spaces(self):
assert _parse_config_value(" ") == ""
def test_only_tabs(self):
assert _parse_config_value("\t\t") == ""
# ==========================================================================
# Unquoted values
# ==========================================================================
def test_simple_value(self):
assert _parse_config_value("hello") == "hello"
def test_value_with_leading_trailing_space(self):
assert _parse_config_value(" hello ") == "hello"
def test_value_with_internal_spaces(self):
assert _parse_config_value("hello world") == "hello world"
def test_numeric_value(self):
assert _parse_config_value("12345") == "12345"
def test_path_value(self):
assert _parse_config_value("/dev/ttyUSB0") == "/dev/ttyUSB0"
# ==========================================================================
# Double-quoted strings
# ==========================================================================
def test_double_quoted_simple(self):
assert _parse_config_value('"hello"') == "hello"
def test_double_quoted_with_spaces(self):
assert _parse_config_value('"hello world"') == "hello world"
def test_double_quoted_with_special_chars(self):
assert _parse_config_value('"hello #world"') == "hello #world"
def test_double_quoted_unclosed(self):
assert _parse_config_value('"hello') == "hello"
def test_double_quoted_empty(self):
assert _parse_config_value('""') == ""
def test_double_quoted_with_trailing_content(self):
# Only extracts content within first pair of quotes
assert _parse_config_value('"hello" # comment') == "hello"
# ==========================================================================
# Single-quoted strings
# ==========================================================================
def test_single_quoted_simple(self):
assert _parse_config_value("'hello'") == "hello"
def test_single_quoted_with_spaces(self):
assert _parse_config_value("'hello world'") == "hello world"
def test_single_quoted_unclosed(self):
assert _parse_config_value("'hello") == "hello"
def test_single_quoted_empty(self):
assert _parse_config_value("''") == ""
# ==========================================================================
# Inline comments
# ==========================================================================
def test_inline_comment_with_space(self):
assert _parse_config_value("hello # comment") == "hello"
def test_inline_comment_multiple_spaces(self):
assert _parse_config_value("hello # comment here") == "hello"
def test_hash_without_space_kept(self):
# Hash without preceding space is kept (not a comment)
assert _parse_config_value("color#ffffff") == "color#ffffff"
def test_hash_at_start_kept(self):
# Hash at start is kept (though unusual for a value)
assert _parse_config_value("#ffffff") == "#ffffff"
# ==========================================================================
# Mixed scenarios
# ==========================================================================
def test_quoted_preserves_hash_comment_style(self):
assert _parse_config_value('"test # not a comment"') == "test # not a comment"
def test_value_ending_with_hash(self):
# "test#" has no space before #, so kept
assert _parse_config_value("test#") == "test#"
class TestLoadConfigFileBehavior:
"""Tests for _load_config_file behavior."""
def test_nonexistent_file_no_error(self, tmp_path, monkeypatch, isolate_config_loading):
"""Missing config file doesn't raise error."""
_load_config_from_content(tmp_path, monkeypatch, content=None)
assert "MESH_TRANSPORT" not in os.environ
def test_skips_empty_lines(self, tmp_path, monkeypatch, isolate_config_loading):
"""Empty lines are skipped."""
config_content = """
MESH_TRANSPORT=tcp
MESH_DEBUG=1
"""
_load_config_from_content(tmp_path, monkeypatch, config_content)
assert os.environ["MESH_TRANSPORT"] == "tcp"
assert os.environ["MESH_DEBUG"] == "1"
def test_skips_comment_lines(self, tmp_path, monkeypatch, isolate_config_loading):
"""Lines starting with # are skipped."""
config_content = """# This is a comment
MESH_TRANSPORT=tcp
# Another comment
"""
_load_config_from_content(tmp_path, monkeypatch, config_content)
assert os.environ["MESH_TRANSPORT"] == "tcp"
def test_handles_export_prefix(self, tmp_path, monkeypatch, isolate_config_loading):
"""Lines with 'export ' prefix are handled."""
config_content = "export MESH_TRANSPORT=tcp\n"
_load_config_from_content(tmp_path, monkeypatch, config_content)
assert os.environ["MESH_TRANSPORT"] == "tcp"
def test_skips_lines_without_equals(self, tmp_path, monkeypatch, isolate_config_loading):
"""Lines without = are skipped."""
config_content = """MESH_TRANSPORT=tcp
this line has no equals
MESH_DEBUG=1
"""
_load_config_from_content(tmp_path, monkeypatch, config_content)
assert os.environ["MESH_TRANSPORT"] == "tcp"
assert os.environ["MESH_DEBUG"] == "1"
def test_env_vars_take_precedence(self, tmp_path, monkeypatch, isolate_config_loading):
"""Environment variables override config file values."""
# Set env var first
monkeypatch.setenv("MESH_TRANSPORT", "ble")
# Config file has different value
config_content = "MESH_TRANSPORT=serial\n"
_load_config_from_content(tmp_path, monkeypatch, config_content)
# After loading, env var should still be "ble"
assert os.environ.get("MESH_TRANSPORT") == "ble"
class TestConfigFileFormats:
"""Test various config file format scenarios."""
def test_standard_format(self):
"""Standard KEY=value format."""
assert _parse_config_value("value") == "value"
def test_spaces_around_equals(self):
"""Key = value with spaces (handled by partition)."""
# Note: _parse_config_value only handles the value part
# The key=value split happens in _load_config_file
assert _parse_config_value(" value ") == "value"
def test_quoted_path_with_spaces(self):
"""Path with spaces must be quoted."""
assert _parse_config_value('"/path/with spaces/file.txt"') == "/path/with spaces/file.txt"
def test_url_value(self):
"""URL values work correctly."""
assert _parse_config_value("https://example.com:8080/path") == "https://example.com:8080/path"
def test_email_value(self):
"""Email values work correctly."""
assert _parse_config_value("user@example.com") == "user@example.com"
def test_json_like_value(self):
"""JSON-like values need quoting if they have spaces."""
# Without spaces, works fine
assert _parse_config_value("{key:value}") == "{key:value}"
# With spaces, needs quotes
assert _parse_config_value('"{key: value}"') == "{key: value}"
class TestValidKeyPatterns:
"""Test key validation patterns."""
def test_valid_key_patterns(self):
"""Valid shell identifier patterns."""
# These would be tested in _load_config_file
# Valid: starts with letter or underscore, contains letters/numbers/underscores
valid_keys = [
"MESH_TRANSPORT",
"_PRIVATE",
"var123",
"MY_VAR_2",
]
# All should match: ^[A-Za-z_][A-Za-z0-9_]*$
import re
pattern = r"^[A-Za-z_][A-Za-z0-9_]*$"
for key in valid_keys:
assert re.match(pattern, key), f"{key} should be valid"
def test_invalid_key_patterns(self):
"""Invalid key patterns are rejected."""
invalid_keys = [
"123_starts_with_number",
"has-dash",
"has.dot",
"has space",
"",
]
import re
pattern = r"^[A-Za-z_][A-Za-z0-9_]*$"
for key in invalid_keys:
assert not re.match(pattern, key), f"{key} should be invalid"