import logging from unittest.mock import mock_open, patch import pytest from control_backend.logging.setup_logging import add_logging_level, setup_logging def test_add_logging_level(): # Add a unique level to avoid conflicts with other tests/libraries level_name = "TESTLEVEL" level_num = 35 add_logging_level(level_name, level_num) assert logging.getLevelName(level_num) == level_name assert hasattr(logging, level_name) assert hasattr(logging.getLoggerClass(), level_name.lower()) # Test functionality logger = logging.getLogger("test_custom_level") with patch.object(logger, "_log") as mock_log: getattr(logger, level_name.lower())("message") mock_log.assert_called_with(level_num, "message", ()) # Test duplicates with pytest.raises(AttributeError): add_logging_level(level_name, level_num) with pytest.raises(AttributeError): add_logging_level("INFO", 20) # Existing level def test_setup_logging_no_file(caplog): with patch("os.path.exists", return_value=False): setup_logging("dummy.yaml") assert "Logging config file not found" in caplog.text def test_setup_logging_yaml_error(caplog): with patch("os.path.exists", return_value=True): with patch("builtins.open", mock_open(read_data="invalid: [yaml")): with patch("logging.config.dictConfig") as mock_dict_config: setup_logging("config.yaml") # Verify we logged the warning assert "Could not load logging configuration" in caplog.text # Verify dictConfig was called with empty dict (which would crash real dictConfig) mock_dict_config.assert_called_with({}) assert "Could not load logging configuration" in caplog.text def test_setup_logging_success(): config_data = """ version: 1 handlers: console: class: logging.StreamHandler root: handlers: [console] level: INFO custom_levels: MYLEVEL: 15 """ with patch("os.path.exists", return_value=True): with patch("builtins.open", mock_open(read_data=config_data)): with patch("logging.config.dictConfig") as mock_dict_config: setup_logging("config.yaml") mock_dict_config.assert_called() assert hasattr(logging, "MYLEVEL") def test_setup_logging_zmq_handler(mock_zmq_context): config_data = """ version: 1 handlers: ui: class: logging.NullHandler # In real config this would be a zmq handler, but for unit test logic # we just want to see if the socket injection happens """ with patch("os.path.exists", return_value=True): with patch("builtins.open", mock_open(read_data=config_data)): with patch("logging.config.dictConfig") as mock_dict_config: setup_logging("config.yaml") args = mock_dict_config.call_args[0][0] assert "interface_or_socket" in args["handlers"]["ui"] def test_add_logging_level_method_name_exists_in_logging(): # method_name explicitly set to an existing logging method → triggers first hasattr branch with pytest.raises(AttributeError) as exc: add_logging_level("NEWDUPLEVEL", 37, method_name="info") assert "info already defined in logging module" in str(exc.value) def test_add_logging_level_method_name_exists_in_logger_class(): # 'makeRecord' exists on Logger class but not on the logging module with pytest.raises(AttributeError) as exc: add_logging_level("ANOTHERLEVEL", 38, method_name="makeRecord") assert "makeRecord already defined in logger class" in str(exc.value) def test_add_logging_level_log_to_root_path_executes_without_error(): # Verify log_to_root is installed and callable — without asserting logging output level_name = "ROOTTEST" level_num = 36 add_logging_level(level_name, level_num) # Simply call the injected root logger method # The line is executed even if we don't validate output root_logging_method = getattr(logging, level_name.lower(), None) assert callable(root_logging_method) # Execute the method to hit log_to_root in coverage. # No need to verify log output. root_logging_method("some message")