mirror of
https://github.com/jorijn/meshcore-stats.git
synced 2026-03-28 17:42:55 +01:00
* 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
182 lines
6.3 KiB
Python
182 lines
6.3 KiB
Python
"""Tests for logging utilities."""
|
|
|
|
from datetime import datetime
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from meshmon import log
|
|
|
|
|
|
class TestTimestamp:
|
|
"""Test the _ts() timestamp function."""
|
|
|
|
def test_returns_string(self):
|
|
"""_ts() should return a string."""
|
|
result = log._ts()
|
|
assert isinstance(result, str)
|
|
|
|
def test_format_is_correct(self):
|
|
"""_ts() should return timestamp in expected format."""
|
|
result = log._ts()
|
|
# Format: YYYY-MM-DD HH:MM:SS
|
|
try:
|
|
datetime.strptime(result, "%Y-%m-%d %H:%M:%S")
|
|
except ValueError:
|
|
pytest.fail(f"Timestamp '{result}' doesn't match expected format")
|
|
|
|
@patch("meshmon.log.datetime")
|
|
def test_uses_current_time(self, mock_datetime):
|
|
"""_ts() should use current time."""
|
|
mock_now = MagicMock()
|
|
mock_now.strftime.return_value = "2024-01-15 10:30:45"
|
|
mock_datetime.now.return_value = mock_now
|
|
|
|
result = log._ts()
|
|
|
|
mock_datetime.now.assert_called_once()
|
|
mock_now.strftime.assert_called_once_with("%Y-%m-%d %H:%M:%S")
|
|
assert result == "2024-01-15 10:30:45"
|
|
|
|
|
|
@pytest.fixture
|
|
def fixed_ts(monkeypatch):
|
|
"""Freeze log timestamp for deterministic output assertions."""
|
|
timestamp = "2024-01-15 10:30:45"
|
|
monkeypatch.setattr(log, "_ts", lambda: timestamp)
|
|
return timestamp
|
|
|
|
|
|
class TestInfoLog:
|
|
"""Test the info() function."""
|
|
|
|
def test_prints_to_stdout(self, capsys, fixed_ts):
|
|
"""info() should print to stdout."""
|
|
log.info("test message")
|
|
captured = capsys.readouterr()
|
|
assert captured.out == f"[{fixed_ts}] test message\n"
|
|
assert captured.err == ""
|
|
|
|
def test_includes_timestamp(self, capsys, fixed_ts):
|
|
"""info() output should include timestamp."""
|
|
log.info("test")
|
|
captured = capsys.readouterr()
|
|
# Should have format: [YYYY-MM-DD HH:MM:SS] message
|
|
assert captured.out.startswith(f"[{fixed_ts}]")
|
|
assert captured.out.endswith("test\n")
|
|
|
|
def test_message_appears_after_timestamp(self, capsys, fixed_ts):
|
|
"""Message should appear after the timestamp."""
|
|
log.info("unique_test_message")
|
|
captured = capsys.readouterr()
|
|
assert captured.out == f"[{fixed_ts}] unique_test_message\n"
|
|
# Message should be after the closing bracket
|
|
bracket_pos = captured.out.index("]")
|
|
message_pos = captured.out.index("unique_test_message")
|
|
assert message_pos > bracket_pos
|
|
|
|
|
|
class TestDebugLog:
|
|
"""Test the debug() function."""
|
|
|
|
def test_no_output_when_debug_disabled(self, capsys, monkeypatch, fixed_ts):
|
|
"""debug() should not print when MESH_DEBUG is not set."""
|
|
# Clean env should already have MESH_DEBUG unset
|
|
import meshmon.env
|
|
meshmon.env._config = None
|
|
|
|
log.debug("debug message")
|
|
captured = capsys.readouterr()
|
|
assert captured.out == ""
|
|
assert captured.err == ""
|
|
|
|
def test_prints_when_debug_enabled(self, capsys, monkeypatch, fixed_ts):
|
|
"""debug() should print when MESH_DEBUG=1."""
|
|
monkeypatch.setenv("MESH_DEBUG", "1")
|
|
import meshmon.env
|
|
meshmon.env._config = None
|
|
|
|
log.debug("debug message")
|
|
captured = capsys.readouterr()
|
|
assert captured.out == f"[{fixed_ts}] DEBUG: debug message\n"
|
|
|
|
def test_debug_prefix(self, capsys, monkeypatch, fixed_ts):
|
|
"""debug() output should include DEBUG: prefix."""
|
|
monkeypatch.setenv("MESH_DEBUG", "1")
|
|
import meshmon.env
|
|
meshmon.env._config = None
|
|
|
|
log.debug("test")
|
|
captured = capsys.readouterr()
|
|
assert captured.out == f"[{fixed_ts}] DEBUG: test\n"
|
|
|
|
|
|
class TestErrorLog:
|
|
"""Test the error() function."""
|
|
|
|
def test_prints_to_stderr(self, capsys, fixed_ts):
|
|
"""error() should print to stderr."""
|
|
log.error("error message")
|
|
captured = capsys.readouterr()
|
|
assert captured.out == ""
|
|
assert captured.err == f"[{fixed_ts}] ERROR: error message\n"
|
|
|
|
def test_includes_error_prefix(self, capsys, fixed_ts):
|
|
"""error() output should include ERROR: prefix."""
|
|
log.error("test error")
|
|
captured = capsys.readouterr()
|
|
assert captured.err == f"[{fixed_ts}] ERROR: test error\n"
|
|
|
|
def test_includes_timestamp(self, capsys, fixed_ts):
|
|
"""error() output should include timestamp."""
|
|
log.error("test")
|
|
captured = capsys.readouterr()
|
|
assert captured.err == f"[{fixed_ts}] ERROR: test\n"
|
|
|
|
|
|
class TestWarnLog:
|
|
"""Test the warn() function."""
|
|
|
|
def test_prints_to_stderr(self, capsys, fixed_ts):
|
|
"""warn() should print to stderr."""
|
|
log.warn("warning message")
|
|
captured = capsys.readouterr()
|
|
assert captured.out == ""
|
|
assert captured.err == f"[{fixed_ts}] WARN: warning message\n"
|
|
|
|
def test_includes_warn_prefix(self, capsys, fixed_ts):
|
|
"""warn() output should include WARN: prefix."""
|
|
log.warn("test warning")
|
|
captured = capsys.readouterr()
|
|
assert captured.err == f"[{fixed_ts}] WARN: test warning\n"
|
|
|
|
def test_includes_timestamp(self, capsys, fixed_ts):
|
|
"""warn() output should include timestamp."""
|
|
log.warn("test")
|
|
captured = capsys.readouterr()
|
|
assert captured.err == f"[{fixed_ts}] WARN: test\n"
|
|
|
|
|
|
class TestLogMessageFormatting:
|
|
"""Test message formatting across all log functions."""
|
|
|
|
def test_info_handles_special_characters(self, capsys, fixed_ts):
|
|
"""info() should handle special characters in messages."""
|
|
log.info("Message with 'quotes' and \"double quotes\"")
|
|
captured = capsys.readouterr()
|
|
assert captured.out == (
|
|
f"[{fixed_ts}] Message with 'quotes' and \"double quotes\"\n"
|
|
)
|
|
|
|
def test_error_handles_newlines(self, capsys, fixed_ts):
|
|
"""error() should handle newlines in messages."""
|
|
log.error("Line1\nLine2")
|
|
captured = capsys.readouterr()
|
|
assert captured.err == f"[{fixed_ts}] ERROR: Line1\nLine2\n"
|
|
|
|
def test_warn_handles_unicode(self, capsys, fixed_ts):
|
|
"""warn() should handle unicode characters."""
|
|
log.warn("Warning: \u26a0 Alert!")
|
|
captured = capsys.readouterr()
|
|
assert captured.err == f"[{fixed_ts}] WARN: Warning: \u26a0 Alert!\n"
|