feat(logs): Implement file-based offloading for scripts and tool outputs
This commit is contained in:
110
tests/test_app_controller_offloading.py
Normal file
110
tests/test_app_controller_offloading.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import pytest
|
||||
import json
|
||||
import time
|
||||
import copy
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
from src.app_controller import AppController
|
||||
from src import session_logger, paths, ai_client, project_manager
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_session_dir(tmp_path, monkeypatch):
|
||||
"""Set up a temporary session directory for session_logger."""
|
||||
logs_dir = tmp_path / "logs"
|
||||
scripts_dir = tmp_path / "scripts"
|
||||
logs_dir.mkdir()
|
||||
scripts_dir.mkdir()
|
||||
|
||||
monkeypatch.setenv("SLOP_LOGS_DIR", str(logs_dir))
|
||||
monkeypatch.setenv("SLOP_SCRIPTS_DIR", str(scripts_dir))
|
||||
paths.reset_resolved()
|
||||
|
||||
# Ensure session_logger is clean
|
||||
with patch("src.session_logger._comms_fh", None):
|
||||
session_logger.open_session("test_offloading")
|
||||
yield logs_dir / session_logger._session_id
|
||||
session_logger.close_session()
|
||||
|
||||
@pytest.fixture
|
||||
def app_controller(tmp_session_dir):
|
||||
"""Create an AppController instance for testing."""
|
||||
with patch("src.app_controller.performance_monitor.PerformanceMonitor"):
|
||||
ctrl = AppController()
|
||||
# Minimal setup to avoid complex initialization
|
||||
ctrl.ui_auto_add_history = True
|
||||
return ctrl
|
||||
|
||||
def test_on_comms_entry_tool_result_offloading(app_controller, tmp_session_dir):
|
||||
"""
|
||||
Test that _on_comms_entry offloads tool_result output to a separate file.
|
||||
"""
|
||||
output_content = "This is a large tool output that should be offloaded."
|
||||
entry = {
|
||||
"kind": "tool_result",
|
||||
"payload": {
|
||||
"output": output_content
|
||||
},
|
||||
"ts": "12:00:00"
|
||||
}
|
||||
|
||||
# Track calls to session_logger.log_comms
|
||||
with patch("src.session_logger.log_comms") as mock_log_comms:
|
||||
app_controller._on_comms_entry(entry)
|
||||
|
||||
# 1. Verify log_comms was called with an optimized entry
|
||||
assert mock_log_comms.called
|
||||
optimized_entry = mock_log_comms.call_args[0][0]
|
||||
assert optimized_entry["kind"] == "tool_result"
|
||||
assert "output" in optimized_entry["payload"]
|
||||
# The output should be a reference like [REF:output_0001.txt]
|
||||
ref_text = optimized_entry["payload"]["output"]
|
||||
assert ref_text.startswith("[REF:output_")
|
||||
assert ref_text.endswith(".txt]")
|
||||
|
||||
# 2. Verify the original entry was NOT modified in terms of its payload content
|
||||
# Wait, the tool uses deepcopy so it should be fine.
|
||||
assert entry["payload"]["output"] == output_content
|
||||
|
||||
# 3. Verify the offloaded file exists and contains the correct content
|
||||
ref_filename = ref_text[5:-1] # Strip [REF: and ]
|
||||
offloaded_path = tmp_session_dir / "outputs" / ref_filename
|
||||
assert offloaded_path.exists()
|
||||
assert offloaded_path.read_text(encoding="utf-8") == output_content
|
||||
|
||||
# 4. Verify that effects on internal state (like history adds) use the original output
|
||||
# _on_comms_entry appends to _pending_history_adds
|
||||
with app_controller._pending_history_adds_lock:
|
||||
assert len(app_controller._pending_history_adds) > 0
|
||||
history_entry = next(e for e in app_controller._pending_history_adds if e["role"] == "Tool")
|
||||
assert output_content in history_entry["content"]
|
||||
assert "[TOOL RESULT]" in history_entry["content"]
|
||||
|
||||
def test_on_tool_log_offloading(app_controller, tmp_session_dir):
|
||||
"""
|
||||
Test that _on_tool_log calls session_logger.log_tool_call and log_tool_output.
|
||||
"""
|
||||
script = "Get-Process"
|
||||
result = "Process list..."
|
||||
|
||||
with patch("src.ai_client.get_current_tier", return_value="Tier 3"):
|
||||
app_controller._on_tool_log(script, result)
|
||||
|
||||
# Verify files were created in session directory
|
||||
scripts_dir = tmp_session_dir / "scripts"
|
||||
outputs_dir = tmp_session_dir / "outputs"
|
||||
|
||||
script_files = list(scripts_dir.glob("script_*.ps1"))
|
||||
assert len(script_files) == 1
|
||||
assert script_files[0].read_text(encoding="utf-8") == script
|
||||
|
||||
output_files = list(outputs_dir.glob("output_*.txt"))
|
||||
# We expect at least one output file for the result
|
||||
assert len(output_files) >= 1
|
||||
assert any(f.read_text(encoding="utf-8") == result for f in output_files)
|
||||
|
||||
# Verify AppController internal state
|
||||
with app_controller._pending_tool_calls_lock:
|
||||
assert len(app_controller._pending_tool_calls) == 1
|
||||
assert app_controller._pending_tool_calls[0]["script"] == script
|
||||
assert app_controller._pending_tool_calls[0]["result"] == result
|
||||
assert app_controller._pending_tool_calls[0]["source_tier"] == "Tier 3"
|
||||
Reference in New Issue
Block a user