""" Tests for the Log Viewer UI component (Story 7.15). """ import pytest from pathlib import Path import tempfile from datetime import datetime, timedelta from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import Qt from src.ui.log_viewer import ( LogLevel, LogEntry, InMemoryLogHandler, LogListModel, LogTableModel, LogViewer, get_log_handler, log_info, log_warning, log_error, ) @pytest.fixture def app(qtbot): """Create QApplication for tests.""" return QApplication.instance() or QApplication([]) @pytest.fixture def clean_handler(): """Create a fresh log handler for testing.""" import src.ui.log_viewer as lv lv._global_log_handler = None handler = InMemoryLogHandler() return handler @pytest.fixture def sample_entries(): """Create sample log entries.""" now = datetime.now() return [ LogEntry( level=LogLevel.INFO, timestamp=now - timedelta(minutes=3), message="Application started", module="main" ), LogEntry( level=LogLevel.DEBUG, timestamp=now - timedelta(minutes=2), message="Loading configuration", module="config" ), LogEntry( level=LogLevel.WARNING, timestamp=now - timedelta(minutes=1), message="Configuration file not found, using defaults", module="config" ), LogEntry( level=LogLevel.ERROR, timestamp=now - timedelta(seconds=30), message="Failed to connect to server", module="network" ), LogEntry( level=LogLevel.INFO, timestamp=now, message="Translation started", module="translator" ), ] class TestLogLevel: """Tests for LogLevel enum.""" def test_level_values(self): """Test LogLevel enum values.""" assert LogLevel.DEBUG.value == "DEBUG" assert LogLevel.INFO.value == "INFO" assert LogLevel.WARNING.value == "WARNING" assert LogLevel.ERROR.value == "ERROR" assert LogLevel.CRITICAL.value == "CRITICAL" def test_display_names(self): """Test Chinese display names.""" assert LogLevel.DEBUG.display_name == "调试" assert LogLevel.INFO.display_name == "信息" assert LogLevel.WARNING.display_name == "警告" assert LogLevel.ERROR.display_name == "错误" assert LogLevel.CRITICAL.display_name == "严重" def test_colors(self): """Test level colors.""" assert LogLevel.DEBUG.color == "#888888" assert LogLevel.INFO.color == "#3498db" assert LogLevel.WARNING.color == "#f39c12" assert LogLevel.ERROR.color == "#e74c3c" assert LogLevel.CRITICAL.color == "#8e44ad" class TestLogEntry: """Tests for LogEntry dataclass.""" def test_entry_creation(self): """Test creating a log entry.""" entry = LogEntry( level=LogLevel.INFO, timestamp=datetime.now(), message="Test message" ) assert entry.level == LogLevel.INFO assert entry.message == "Test message" def test_str_representation(self): """Test string representation of entry.""" now = datetime.now() entry = LogEntry( level=LogLevel.INFO, timestamp=now, message="Test message" ) str_repr = str(entry) assert "[INFO]" in str_repr assert "Test message" in str_repr def test_timestamp_display(self): """Test timestamp display formatting.""" now = datetime(2024, 3, 15, 14, 30, 45) entry = LogEntry( level=LogLevel.INFO, timestamp=now, message="Test" ) assert entry.timestamp_display == "14:30:45" assert entry.date_display == "2024-03-15" def test_level_properties(self): """Test level-related properties.""" entry = LogEntry( level=LogLevel.ERROR, timestamp=datetime.now(), message="Error message" ) assert entry.level_display == "错误" assert entry.level_color == "#e74c3c" class TestInMemoryLogHandler: """Tests for InMemoryLogHandler.""" def test_initialization(self, clean_handler): """Test handler initialization.""" assert clean_handler.entry_count == 0 def test_emit_entry(self, clean_handler): """Test emitting a log entry.""" clean_handler.emit(LogLevel.INFO, "Test message") assert clean_handler.entry_count == 1 def test_emit_with_metadata(self, clean_handler): """Test emitting with metadata.""" clean_handler.emit( LogLevel.ERROR, "Error occurred", module="test_module", function="test_func", line=42 ) assert clean_handler.entry_count == 1 def test_get_all_entries(self, clean_handler): """Test getting all entries.""" clean_handler.emit(LogLevel.INFO, "Message 1") clean_handler.emit(LogLevel.WARNING, "Message 2") entries = clean_handler.get_entries() assert len(entries) == 2 def test_filter_by_level(self, clean_handler): """Test filtering by log level.""" clean_handler.emit(LogLevel.DEBUG, "Debug message") clean_handler.emit(LogLevel.INFO, "Info message") clean_handler.emit(LogLevel.WARNING, "Warning message") clean_handler.emit(LogLevel.ERROR, "Error message") # Get only INFO and above entries = clean_handler.get_entries(min_level=LogLevel.INFO) assert len(entries) == 3 # Get only ERROR and above entries = clean_handler.get_entries(min_level=LogLevel.ERROR) assert len(entries) == 1 def test_filter_by_search_text(self, clean_handler): """Test filtering by search text.""" clean_handler.emit(LogLevel.INFO, "Application started") clean_handler.emit(LogLevel.INFO, "Configuration loaded") clean_handler.emit(LogLevel.ERROR, "Failed to connect") entries = clean_handler.get_entries(search_text="failed") assert len(entries) == 1 entries = clean_handler.get_entries(search_text="app") assert len(entries) == 1 def test_limit_entries(self, clean_handler): """Test limiting number of entries.""" for i in range(10): clean_handler.emit(LogLevel.INFO, f"Message {i}") entries = clean_handler.get_entries(limit=5) assert len(entries) == 5 def test_max_entries_trimming(self): """Test that handler trims old entries when over max.""" handler = InMemoryLogHandler(max_entries=5) for i in range(10): handler.emit(LogLevel.INFO, f"Message {i}") # Should only keep last 5 assert handler.entry_count == 5 entries = handler.get_entries() assert entries[0].message == "Message 5" assert entries[-1].message == "Message 9" def test_clear(self, clean_handler): """Test clearing all entries.""" clean_handler.emit(LogLevel.INFO, "Message 1") clean_handler.emit(LogLevel.INFO, "Message 2") assert clean_handler.entry_count == 2 clean_handler.clear() assert clean_handler.entry_count == 0 def test_listeners(self, clean_handler): """Test entry listeners.""" received = [] def listener(entry): received.append(entry) clean_handler.add_listener(listener) clean_handler.emit(LogLevel.INFO, "Test message") assert len(received) == 1 assert received[0].message == "Test message" # Remove listener clean_handler.remove_listener(listener) clean_handler.emit(LogLevel.INFO, "Another message") assert len(received) == 1 # Should not have received the second message class TestLogTableModel: """Tests for LogTableModel.""" def test_initialization(self, app, sample_entries): """Test model initialization.""" model = LogTableModel(sample_entries) assert model.rowCount() == 5 assert model.columnCount() == 4 def test_data_retrieval(self, app, sample_entries): """Test data retrieval from model.""" model = LogTableModel(sample_entries) # Check first row index = model.index(0, 1) # Level column assert model.data(index) == "信息" index = model.index(0, 2) # Message column assert "Application started" in model.data(index) def test_header_data(self, app, sample_entries): """Test header data.""" model = LogTableModel(sample_entries) assert model.headerData(0, Qt.Orientation.Horizontal) == "时间" assert model.headerData(1, Qt.Orientation.Horizontal) == "级别" assert model.headerData(2, Qt.Orientation.Horizontal) == "消息" assert model.headerData(3, Qt.Orientation.Horizontal) == "模块" def test_get_entry_at_row(self, app, sample_entries): """Test getting entry at specific row.""" model = LogTableModel(sample_entries) entry = model.get_entry_at_row(0) assert entry is not None assert entry.message == "Application started" entry = model.get_entry_at_row(999) assert entry is None class TestLogFunctions: """Tests for module-level logging functions.""" def test_log_info(self): """Test log_info function.""" import src.ui.log_viewer as lv lv._global_log_handler = None log_info("Info message") handler = get_log_handler() assert handler.entry_count == 1 def test_log_warning(self): """Test log_warning function.""" import src.ui.log_viewer as lv lv._global_log_handler = None log_warning("Warning message") handler = get_log_handler() assert handler.entry_count == 1 def test_log_error(self): """Test log_error function.""" import src.ui.log_viewer as lv lv._global_log_handler = None log_error("Error message") handler = get_log_handler() assert handler.entry_count == 1 class TestLogHandlerSingleton: """Tests for the log handler singleton.""" def test_get_singleton(self): """Test getting singleton instance.""" import src.ui.log_viewer as lv lv._global_log_handler = None handler = get_log_handler() assert handler is not None assert isinstance(handler, InMemoryLogHandler) def test_singleton_persistence(self): """Test that singleton returns same instance.""" import src.ui.log_viewer as lv lv._global_log_handler = None handler1 = get_log_handler() handler2 = get_log_handler() assert handler1 is handler2