""" Unit tests for Log Reader service. Tests cover: - Log file discovery - Log event parsing - Event subscription - Pattern matching - Statistics tracking """ import pytest from pathlib import Path from unittest.mock import MagicMock, patch, mock_open from datetime import datetime from core.log_reader import LogReader, LogEvent, get_log_reader @pytest.mark.unit class TestLogReaderInitialization: """Test LogReader initialization.""" def test_default_log_path(self): """Test default log path detection.""" with patch.object(Path, 'exists', return_value=True): reader = LogReader() # Should find one of the default paths assert reader.log_path is not None def test_custom_log_path(self, tmp_path): """Test custom log path.""" log_file = tmp_path / "custom.log" log_file.write_text("test log content") reader = LogReader(log_path=log_file) assert reader.log_path == log_file def test_find_log_file(self, tmp_path): """Test log file discovery.""" log_file = tmp_path / "chat.log" log_file.write_text("test") reader = LogReader() found_path = reader._find_log_file() # May or may not find depending on system # Just verify method doesn't raise assert found_path is None or isinstance(found_path, Path) @pytest.mark.unit class TestLogEvent: """Test LogEvent dataclass.""" def test_log_event_creation(self): """Test LogEvent creation.""" event = LogEvent( timestamp=datetime.now(), raw_line="Test line", event_type="skill_gain", data={"skill": "Rifle", "gain": 0.01} ) assert event.raw_line == "Test line" assert event.event_type == "skill_gain" assert event.data["skill"] == "Rifle" @pytest.mark.unit class TestPatternMatching: """Test log pattern matching.""" def test_skill_gain_pattern(self): """Test skill gain pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 Rifle has improved by 0.01 points" event = reader._parse_event(line) assert event is not None assert event.event_type == "skill_gain" assert "Rifle" in event.data['groups'] assert "0.01" in event.data['groups'] def test_loot_pattern(self): """Test loot pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 You received Animal Oil x 1" event = reader._parse_event(line) assert event is not None assert event.event_type == "loot" def test_global_pattern(self): """Test global pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 Player received something from Mob worth 1000 PED" event = reader._parse_event(line) assert event is not None assert event.event_type == "global" def test_damage_pattern(self): """Test damage pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 You hit for 150 damage" event = reader._parse_event(line) assert event is not None assert event.event_type == "damage" def test_damage_taken_pattern(self): """Test damage taken pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 You were hit for 50 damage" event = reader._parse_event(line) assert event is not None assert event.event_type == "damage_taken" def test_heal_pattern(self): """Test heal pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 You healed 100 health" event = reader._parse_event(line) assert event is not None assert event.event_type == "heal" def test_mission_complete_pattern(self): """Test mission complete pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 Mission completed: Test Mission" event = reader._parse_event(line) assert event is not None assert event.event_type == "mission_complete" def test_tier_increase_pattern(self): """Test tier increase pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 Your Weapon has reached tier 5" event = reader._parse_event(line) assert event is not None assert event.event_type == "tier_increase" def test_enhancer_break_pattern(self): """Test enhancer break pattern matching.""" reader = LogReader() line = "2024-01-01 12:00:00 Your Enhancer broke" event = reader._parse_event(line) assert event is not None assert event.event_type == "enhancer_break" def test_no_match(self): """Test line that doesn't match any pattern.""" reader = LogReader() line = "2024-01-01 12:00:00 Some random log line" event = reader._parse_event(line) assert event is None @pytest.mark.unit class TestSubscription: """Test event subscription.""" def test_subscribe_to_event_type(self): """Test subscribing to specific event type.""" reader = LogReader() received = [] def callback(event): received.append(event) reader.subscribe("skill_gain", callback) # Simulate event event = LogEvent( timestamp=datetime.now(), raw_line="Test", event_type="skill_gain", data={} ) reader._notify_subscribers(event) assert len(received) == 1 def test_subscribe_all(self): """Test subscribing to all events.""" reader = LogReader() received = [] def callback(event): received.append(event) reader.subscribe_all(callback) # Simulate events for event_type in ["skill_gain", "loot", "damage"]: event = LogEvent( timestamp=datetime.now(), raw_line="Test", event_type=event_type, data={} ) reader._notify_subscribers(event) assert len(received) == 3 def test_unsubscribe(self): """Test unsubscribing from events.""" reader = LogReader() received = [] def callback(event): received.append(event) reader.subscribe("skill_gain", callback) # First event event = LogEvent( timestamp=datetime.now(), raw_line="Test", event_type="skill_gain", data={} ) reader._notify_subscribers(event) assert len(received) == 1 # Unsubscribe reader.unsubscribe("skill_gain", callback) # Second event reader._notify_subscribers(event) assert len(received) == 1 # No new events @pytest.mark.unit class TestReadLines: """Test reading log lines.""" def test_read_lines(self): """Test reading recent lines.""" reader = LogReader() reader._recent_lines = ["line1", "line2", "line3", "line4", "line5"] lines = reader.read_lines(count=3) assert len(lines) == 3 assert lines == ["line3", "line4", "line5"] def test_read_lines_with_filter(self): """Test reading lines with filter.""" reader = LogReader() reader._recent_lines = [ "skill gain line", "combat line", "another skill gain", "loot line" ] lines = reader.read_lines(count=10, filter_text="skill") assert len(lines) == 2 assert all("skill" in line.lower() for line in lines) def test_read_lines_more_than_available(self): """Test reading more lines than available.""" reader = LogReader() reader._recent_lines = ["line1", "line2"] lines = reader.read_lines(count=10) assert len(lines) == 2 @pytest.mark.unit class TestStatistics: """Test statistics tracking.""" def test_stats_initialization(self): """Test stats initialization.""" reader = LogReader() assert reader.stats['lines_read'] == 0 assert reader.stats['events_parsed'] == 0 assert reader.stats['start_time'] is None def test_stats_tracking(self): """Test stats tracking during operation.""" reader = LogReader() # Simulate processing lines reader._process_line("Line 1") reader._process_line("Line 2") reader._process_line("Rifle has improved by 0.01 points") # Matches pattern assert reader.stats['lines_read'] == 3 assert reader.stats['events_parsed'] == 1 def test_get_stats(self): """Test getting stats copy.""" reader = LogReader() reader.stats['lines_read'] = 100 stats = reader.get_stats() assert stats['lines_read'] == 100 # Modifying returned stats shouldn't affect original stats['lines_read'] = 200 assert reader.stats['lines_read'] == 100 @pytest.mark.unit class TestAvailability: """Test availability checking.""" def test_is_available_true(self, tmp_path): """Test availability when log exists.""" log_file = tmp_path / "chat.log" log_file.write_text("test") reader = LogReader(log_path=log_file) assert reader.is_available() is True def test_is_available_false(self): """Test availability when log doesn't exist.""" reader = LogReader(log_path=Path("/nonexistent/path/chat.log")) assert reader.is_available() is False def test_is_available_no_path(self): """Test availability when no path set.""" reader = LogReader() reader.log_path = None assert reader.is_available() is False @pytest.mark.unit class TestStartStop: """Test starting and stopping the reader.""" def test_start_with_valid_log(self, tmp_path): """Test starting with valid log file.""" log_file = tmp_path / "chat.log" log_file.write_text("test content here") reader = LogReader(log_path=log_file) result = reader.start() assert result is True assert reader.running is True assert reader.thread is not None reader.stop() def test_start_with_invalid_log(self): """Test starting with invalid log file.""" reader = LogReader(log_path=Path("/nonexistent/chat.log")) result = reader.start() assert result is False assert reader.running is False def test_stop(self, tmp_path): """Test stopping the reader.""" log_file = tmp_path / "chat.log" log_file.write_text("test") reader = LogReader(log_path=log_file) reader.start() reader.stop() assert reader.running is False