EU-Utility/tests/unit/test_log_reader.py

393 lines
11 KiB
Python

"""
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