Applied 236 return type annotations to functions with no return values across 100+ files (core modules, tests, scripts, simulations). Added Phase 4 to python_style_refactor track for remaining 597 items (untyped params, vars, and functions with return values). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
4.0 KiB
Python
118 lines
4.0 KiB
Python
import pytest
|
|
from unittest.mock import MagicMock, patch, AsyncMock
|
|
import asyncio
|
|
import time
|
|
from gui_2 import App
|
|
from events import UserRequestEvent
|
|
import ai_client
|
|
|
|
@pytest.fixture
|
|
def mock_app() -> None:
|
|
with (
|
|
patch('gui_2.load_config', return_value={
|
|
"ai": {"provider": "gemini", "model": "model-1", "temperature": 0.0, "max_tokens": 100, "history_trunc_limit": 1000},
|
|
"projects": {"paths": [], "active": ""},
|
|
"gui": {"show_windows": {}}
|
|
}),
|
|
patch('gui_2.project_manager.load_project', return_value={
|
|
"project": {"name": "test_proj"},
|
|
"discussion": {"active": "main", "discussions": {"main": {"history": []}}},
|
|
"files": {"paths": [], "base_dir": "."},
|
|
"screenshots": {"paths": [], "base_dir": "."},
|
|
"agent": {"tools": {}}
|
|
}),
|
|
patch('gui_2.project_manager.migrate_from_legacy_config', return_value={}),
|
|
patch('gui_2.project_manager.save_project'),
|
|
patch('gui_2.session_logger.open_session'),
|
|
patch('gui_2.App._init_ai_and_hooks'),
|
|
patch('gui_2.App._fetch_models')
|
|
):
|
|
app = App()
|
|
yield app
|
|
# We don't have a clean way to stop the loop thread in gui_2.py App
|
|
# so we just let it daemon-exit.
|
|
|
|
@pytest.mark.timeout(10)
|
|
def test_user_request_integration_flow(mock_app):
|
|
"""
|
|
Verifies that pushing a UserRequestEvent to the event_queue:
|
|
1. Triggers ai_client.send
|
|
2. Results in a 'response' event back to the queue
|
|
3. Eventually updates the UI state (ai_response, ai_status) after processing GUI tasks.
|
|
"""
|
|
app = mock_app
|
|
# Mock all ai_client methods called during _handle_request_event
|
|
mock_response = "This is a test AI response"
|
|
with (
|
|
patch('ai_client.send', return_value=mock_response) as mock_send,
|
|
patch('ai_client.set_custom_system_prompt'),
|
|
patch('ai_client.set_model_params'),
|
|
patch('ai_client.set_agent_tools')
|
|
):
|
|
# 1. Create and push a UserRequestEvent
|
|
event = UserRequestEvent(
|
|
prompt="Hello AI",
|
|
stable_md="Context",
|
|
file_items=[],
|
|
disc_text="History",
|
|
base_dir="."
|
|
)
|
|
# 2. Push event to the app's internal loop
|
|
asyncio.run_coroutine_threadsafe(
|
|
app.event_queue.put("user_request", event),
|
|
app._loop
|
|
)
|
|
# 3. Wait for ai_client.send to be called (polling background thread)
|
|
start_time = time.time()
|
|
while not mock_send.called and time.time() - start_time < 5:
|
|
time.sleep(0.1)
|
|
assert mock_send.called, "ai_client.send was not called within timeout"
|
|
mock_send.assert_called_once_with("Context", "Hello AI", ".", [], "History")
|
|
# 4. Wait for the response to propagate to _pending_gui_tasks and update UI
|
|
# We call _process_pending_gui_tasks manually to simulate a GUI frame update.
|
|
start_time = time.time()
|
|
success = False
|
|
while time.time() - start_time < 3:
|
|
app._process_pending_gui_tasks()
|
|
if app.ai_response == mock_response and app.ai_status == "done":
|
|
success = True
|
|
break
|
|
time.sleep(0.1)
|
|
assert success, f"UI state was not updated. ai_response: '{app.ai_response}', status: '{app.ai_status}'"
|
|
assert app.ai_response == mock_response
|
|
assert app.ai_status == "done"
|
|
|
|
@pytest.mark.timeout(10)
|
|
def test_user_request_error_handling(mock_app):
|
|
"""
|
|
Verifies that if ai_client.send raises an exception, the UI is updated with the error state.
|
|
"""
|
|
app = mock_app
|
|
with (
|
|
patch('ai_client.send', side_effect=Exception("API Failure")) as mock_send,
|
|
patch('ai_client.set_custom_system_prompt'),
|
|
patch('ai_client.set_model_params'),
|
|
patch('ai_client.set_agent_tools')
|
|
):
|
|
event = UserRequestEvent(
|
|
prompt="Trigger Error",
|
|
stable_md="",
|
|
file_items=[],
|
|
disc_text="",
|
|
base_dir="."
|
|
)
|
|
asyncio.run_coroutine_threadsafe(
|
|
app.event_queue.put("user_request", event),
|
|
app._loop
|
|
)
|
|
# Poll for error state by processing GUI tasks
|
|
start_time = time.time()
|
|
success = False
|
|
while time.time() - start_time < 5:
|
|
app._process_pending_gui_tasks()
|
|
if app.ai_status == "error" and "ERROR: API Failure" in app.ai_response:
|
|
success = True
|
|
break
|
|
time.sleep(0.1)
|
|
assert success, f"Error state was not reflected in UI. status: {app.ai_status}, response: {app.ai_response}"
|