"""Unit tests for the logging module.""" import unittest import tempfile import os import logging from pathlib import Path from unittest.mock import patch, MagicMock # Add the src directory to the path import sys sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) from forensictrails.utils.logging import setup_logging class TestSetupLogging(unittest.TestCase): """Test cases for setup_logging function.""" def setUp(self): """Set up test fixtures.""" self.temp_dir = tempfile.mkdtemp() self.test_log_path = os.path.join(self.temp_dir, 'test.log') # Clear any existing handlers logger = logging.getLogger() for handler in logger.handlers[:]: logger.removeHandler(handler) def tearDown(self): """Clean up test fixtures.""" # Clear handlers after test logger = logging.getLogger() for handler in logger.handlers[:]: handler.close() logger.removeHandler(handler) if os.path.exists(self.test_log_path): os.remove(self.test_log_path) # Clean up any nested directories if os.path.exists(self.temp_dir): for root, dirs, files in os.walk(self.temp_dir, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) os.rmdir(self.temp_dir) @patch('forensictrails.utils.logging.config') def test_setup_logging_creates_log_file(self, mock_config): """Test that setup_logging creates the log file.""" mock_config.log_path = self.test_log_path mock_config.log_level = 'DEBUG' self.assertFalse(os.path.exists(self.test_log_path)) setup_logging(self.test_log_path) self.assertTrue(os.path.exists(self.test_log_path)) @patch('forensictrails.utils.logging.config') def test_setup_logging_creates_nested_directories(self, mock_config): """Test that setup_logging creates nested directories.""" nested_log_path = os.path.join(self.temp_dir, 'logs', 'nested', 'test.log') mock_config.log_path = nested_log_path mock_config.log_level = 'INFO' self.assertFalse(os.path.exists(nested_log_path)) setup_logging(nested_log_path) self.assertTrue(os.path.exists(nested_log_path)) self.assertTrue(os.path.isfile(nested_log_path)) @patch('forensictrails.utils.logging.config') def test_setup_logging_adds_handlers(self, mock_config): """Test that setup_logging adds file and stream handlers.""" mock_config.log_path = self.test_log_path mock_config.log_level = 'DEBUG' logger = logging.getLogger() initial_handler_count = len(logger.handlers) setup_logging(self.test_log_path) # Should add 2 handlers: FileHandler and StreamHandler self.assertEqual(len(logger.handlers), initial_handler_count + 2) # Check handler types handler_types = [type(h).__name__ for h in logger.handlers] self.assertIn('FileHandler', handler_types) self.assertIn('StreamHandler', handler_types) @patch('forensictrails.utils.logging.config') def test_setup_logging_sets_correct_log_level(self, mock_config): """Test that setup_logging sets the correct log level.""" mock_config.log_path = self.test_log_path mock_config.log_level = 'WARNING' setup_logging(self.test_log_path) logger = logging.getLogger() self.assertEqual(logger.level, logging.WARNING) @patch('forensictrails.utils.logging.config') def test_setup_logging_logs_messages(self, mock_config): """Test that setup_logging enables logging messages.""" mock_config.log_path = self.test_log_path mock_config.log_level = 'DEBUG' setup_logging(self.test_log_path) # Log a test message test_message = "Test logging message" logging.info(test_message) # Force flush for handler in logging.getLogger().handlers: handler.flush() # Check that message was written to file with open(self.test_log_path, 'r') as f: log_content = f.read() self.assertIn(test_message, log_content) self.assertIn('INFO', log_content) @patch('forensictrails.utils.logging.config') def test_setup_logging_formatter(self, mock_config): """Test that setup_logging uses correct formatter.""" mock_config.log_path = self.test_log_path mock_config.log_level = 'DEBUG' setup_logging(self.test_log_path) # Log a message logging.info("Formatter test") # Force flush for handler in logging.getLogger().handlers: handler.flush() # Check log format with open(self.test_log_path, 'r') as f: log_content = f.read() # Should contain timestamp, level, and message self.assertIn('INFO', log_content) self.assertIn('Formatter test', log_content) # Check for timestamp pattern (YYYY-MM-DD HH:MM:SS) import re timestamp_pattern = r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' self.assertTrue(re.search(timestamp_pattern, log_content)) @patch('forensictrails.utils.logging.config') def test_setup_logging_uses_config_when_no_path_provided(self, mock_config): """Test that setup_logging uses config.log_path when no path is provided.""" mock_config.log_path = self.test_log_path mock_config.log_level = 'INFO' setup_logging() # No path provided # Should create file at config.log_path self.assertTrue(os.path.exists(self.test_log_path)) @patch('forensictrails.utils.logging.config') def test_setup_logging_different_log_levels(self, mock_config): """Test setup_logging with different log levels.""" mock_config.log_path = self.test_log_path for level_name, level_value in [ ('DEBUG', logging.DEBUG), ('INFO', logging.INFO), ('WARNING', logging.WARNING), ('ERROR', logging.ERROR), ('CRITICAL', logging.CRITICAL) ]: with self.subTest(level=level_name): # Clear handlers logger = logging.getLogger() for handler in logger.handlers[:]: handler.close() logger.removeHandler(handler) mock_config.log_level = level_name setup_logging(self.test_log_path) self.assertEqual(logger.level, level_value) class TestLoggingIntegration(unittest.TestCase): """Integration tests for logging functionality.""" def setUp(self): """Set up test fixtures.""" self.temp_dir = tempfile.mkdtemp() self.test_log_path = os.path.join(self.temp_dir, 'integration.log') # Clear any existing handlers logger = logging.getLogger() for handler in logger.handlers[:]: logger.removeHandler(handler) def tearDown(self): """Clean up test fixtures.""" # Clear handlers after test logger = logging.getLogger() for handler in logger.handlers[:]: handler.close() logger.removeHandler(handler) if os.path.exists(self.test_log_path): os.remove(self.test_log_path) os.rmdir(self.temp_dir) @patch('forensictrails.utils.logging.config') def test_logging_multiple_messages(self, mock_config): """Test logging multiple messages of different levels.""" mock_config.log_path = self.test_log_path mock_config.log_level = 'DEBUG' setup_logging(self.test_log_path) # Log messages at different levels logging.debug("Debug message") logging.info("Info message") logging.warning("Warning message") logging.error("Error message") # Force flush for handler in logging.getLogger().handlers: handler.flush() # Verify all messages are in the log with open(self.test_log_path, 'r') as f: log_content = f.read() self.assertIn("Debug message", log_content) self.assertIn("Info message", log_content) self.assertIn("Warning message", log_content) self.assertIn("Error message", log_content) self.assertIn("DEBUG", log_content) self.assertIn("INFO", log_content) self.assertIn("WARNING", log_content) self.assertIn("ERROR", log_content) if __name__ == '__main__': unittest.main()