"""Unit tests for logging configuration.""" from __future__ import annotations import logging from collections.abc import Generator from pathlib import Path from unittest.mock import patch import pytest from gitea_runner_manager.logging_config import _level_from_env, get_logger @pytest.fixture(autouse=True) def _cleanup_loggers() -> Generator[None, None, None]: """Remove test loggers after each test to prevent state leakage.""" yield for name in list(logging.Logger.manager.loggerDict.keys()): if name.startswith("test_"): logger = logging.getLogger(name) logger.handlers.clear() del logging.Logger.manager.loggerDict[name] class TestLevelFromEnv: def test_none_defaults_to_info(self) -> None: assert _level_from_env(None) == logging.INFO def test_empty_string_defaults_to_info(self) -> None: assert _level_from_env("") == logging.INFO def test_valid_levels(self) -> None: assert _level_from_env("debug") == logging.DEBUG assert _level_from_env("DEBUG") == logging.DEBUG assert _level_from_env("warning") == logging.WARNING assert _level_from_env("error") == logging.ERROR assert _level_from_env("critical") == logging.CRITICAL def test_invalid_fallback_to_info(self) -> None: assert _level_from_env("foo") == logging.INFO assert _level_from_env("VERBOSE") == logging.INFO class TestGetLogger: def test_returns_logger(self, tmp_path: Path) -> None: with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path): logger = get_logger("test_returns_logger") assert isinstance(logger, logging.Logger) assert logger.name == "test_returns_logger" assert len(logger.handlers) == 2 # console + file def test_caches_same_instance(self, tmp_path: Path) -> None: with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path): logger1 = get_logger("test_caches_same") logger2 = get_logger("test_caches_same") assert logger1 is logger2 def test_file_handler_writes(self, tmp_path: Path) -> None: with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path): logger = get_logger("test_file_writes") logger.info("hello from test") log_file = tmp_path / ".local" / "state" / "grm" / "logs" / "grm.log" assert log_file.exists() content = log_file.read_text() assert "hello from test" in content assert "INFO" in content def test_respects_grm_log_level(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: monkeypatch.setenv("GRM_LOG_LEVEL", "DEBUG") with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path): logger = get_logger("test_env_level") console = [h for h in logger.handlers if type(h) is logging.StreamHandler] assert len(console) == 1 assert console[0].level == logging.DEBUG def test_grm_log_level_warning(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: monkeypatch.setenv("GRM_LOG_LEVEL", "WARNING") with patch("gitea_runner_manager.logging_config.Path.home", return_value=tmp_path): logger = get_logger("test_env_warning") console = [h for h in logger.handlers if type(h) is logging.StreamHandler] assert len(console) == 1 assert console[0].level == logging.WARNING