"""Unit tests for structured logging configuration.""" from __future__ import annotations import json import logging from pathlib import Path from prometheus.cli.logging_setup import configure_logging, get_logger class TestConfigureLogging: def _count_handlers(self, name: str = "prometheus") -> int: return len(logging.getLogger(name).handlers) def test_default_creates_console_handler(self) -> None: configure_logging(level=logging.INFO) prom = logging.getLogger("prometheus") assert len(prom.handlers) == 1 assert isinstance(prom.handlers[0], logging.StreamHandler) prom.handlers.clear() def test_json_format_produces_valid_json(self, capsys) -> None: configure_logging(level=logging.INFO, log_format="json") logger = get_logger("test_json") logger.info("hello", extra={"structured": {"key": "value"}}) captured = capsys.readouterr() # Output goes to stderr line = captured.err.strip().split("\n")[-1] data = json.loads(line) assert data["message"] == "hello" assert data["structured"]["key"] == "value" assert data["level"] == "INFO" assert "timestamp" in data logging.getLogger("prometheus").handlers.clear() def test_text_format_includes_structured_extras(self, capsys) -> None: configure_logging(level=logging.INFO, log_format="text") logger = get_logger("test_text") logger.info("msg", extra={"structured": {"foo": "bar"}}) captured = capsys.readouterr() assert "foo=bar" in captured.err logging.getLogger("prometheus").handlers.clear() def test_debug_level_shows_debug_messages(self, capsys) -> None: configure_logging(level=logging.DEBUG) logger = get_logger("test_debug") logger.debug("debug msg") captured = capsys.readouterr() assert "debug msg" in captured.err logging.getLogger("prometheus").handlers.clear() def test_warning_level_hides_debug_messages(self, capsys) -> None: configure_logging(level=logging.WARNING) logger = get_logger("test_warn") logger.debug("should not appear") logger.info("also hidden") captured = capsys.readouterr() assert "should not appear" not in captured.err assert "also hidden" not in captured.err logging.getLogger("prometheus").handlers.clear() def test_file_handler_writes_to_file(self, tmp_path: Path) -> None: log_file = tmp_path / "test.log" configure_logging(level=logging.INFO, log_file=str(log_file)) logger = get_logger("test_file") logger.info("file message") prom = logging.getLogger("prometheus") # Flush handlers for h in prom.handlers: h.flush() prom.handlers.clear() content = log_file.read_text() assert "file message" in content def test_json_file_output(self, tmp_path: Path) -> None: log_file = tmp_path / "test.json.log" configure_logging(level=logging.INFO, log_format="json", log_file=str(log_file)) logger = get_logger("test_json_file") logger.info("json file msg", extra={"structured": {"x": 1}}) prom = logging.getLogger("prometheus") for h in prom.handlers: h.flush() prom.handlers.clear() content = log_file.read_text().strip() data = json.loads(content) assert data["message"] == "json file msg" assert data["structured"]["x"] == 1 def test_reconfigure_clears_old_handlers(self) -> None: configure_logging(level=logging.INFO) configure_logging(level=logging.DEBUG) prom = logging.getLogger("prometheus") assert len(prom.handlers) == 1 prom.handlers.clear() def test_propagate_false_prevents_duplicate_output(self, capsys) -> None: configure_logging(level=logging.INFO) prom = logging.getLogger("prometheus") assert prom.propagate is False prom.handlers.clear() class TestGetLogger: def test_returns_child_of_prometheus(self) -> None: logger = get_logger("mymodule") assert logger.name == "prometheus.mymodule" def test_inherits_level_from_parent(self) -> None: configure_logging(level=logging.DEBUG) logger = get_logger("child") assert logger.getEffectiveLevel() <= logging.DEBUG logging.getLogger("prometheus").handlers.clear() class TestJsonFormatter: def test_exception_included(self, capsys) -> None: configure_logging(level=logging.ERROR, log_format="json") logger = get_logger("test_exc") try: raise ValueError("boom") except ValueError: logger.error("failed", exc_info=True) captured = capsys.readouterr() line = captured.err.strip().split("\n")[-1] data = json.loads(line) assert "ValueError: boom" in data["exception"] logging.getLogger("prometheus").handlers.clear() class TestLoggingCLIIntegration: """Tests for CLI flags that configure logging.""" def test_verbose_flag_enables_info(self, tmp_path: Path) -> None: """Simulate what -v does — configure_logging at INFO level.""" configure_logging(level=logging.INFO) logger = get_logger("evolution") logger.info("test message") prom = logging.getLogger("prometheus") assert len(prom.handlers) == 1 prom.handlers.clear() def test_debug_flag_enables_debug(self) -> None: """Simulate what --debug does — configure_logging at DEBUG level.""" configure_logging(level=logging.DEBUG) logger = get_logger("evolution") logger.debug("debug message") prom = logging.getLogger("prometheus") assert prom.level == logging.DEBUG prom.handlers.clear() def test_log_format_invalid_rejected(self) -> None: """Invalid log_format should be caught by OptimizationConfig validator.""" from pydantic import ValidationError from prometheus.application.dto import OptimizationConfig import pytest with pytest.raises(ValidationError, match="log_format must be one of"): OptimizationConfig( seed_prompt="a", task_description="b", log_format="xml", ) def test_log_format_text_and_json_accepted(self) -> None: """Both text and json log_format values should be valid.""" from prometheus.application.dto import OptimizationConfig for fmt in ("text", "json"): config = OptimizationConfig( seed_prompt="a", task_description="b", log_format=fmt, ) assert config.log_format == fmt