chore(tech-debt): Finalize gui_2.py cleanup and test suite discipline
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -6,4 +7,4 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
|
||||
def test_agent_capabilities_listing() -> None:
|
||||
pass
|
||||
pytest.fail("TODO: Implement assertions")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -9,6 +10,7 @@ from ai_client import set_agent_tools, _build_anthropic_tools
|
||||
def test_set_agent_tools() -> None:
|
||||
agent_tools = {"read_file": True, "list_directory": False}
|
||||
set_agent_tools(agent_tools)
|
||||
pytest.fail("TODO: Implement assertions")
|
||||
|
||||
def test_build_anthropic_tools_conversion() -> None:
|
||||
set_agent_tools({"read_file": True})
|
||||
|
||||
@@ -10,5 +10,6 @@ def test_list_models_gemini_cli() -> None:
|
||||
assert "gemini-3-flash-preview" in models
|
||||
assert "gemini-2.5-pro" in models
|
||||
assert "gemini-2.5-flash" in models
|
||||
assert "gemini-2.0-flash" in models
|
||||
assert "gemini-2.5-flash-lite" in models
|
||||
assert len(models) == 5
|
||||
assert len(models) == 6
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
from file_cache import ASTParser
|
||||
|
||||
def test_ast_parser_get_curated_view() -> None:
|
||||
parser = ASTParser("python")
|
||||
code = '''
|
||||
@core_logic
|
||||
def core_func():
|
||||
"""Core logic doc."""
|
||||
print("this should be preserved")
|
||||
return True
|
||||
|
||||
def hot_func():
|
||||
# [HOT]
|
||||
print("this should also be preserved")
|
||||
return 42
|
||||
|
||||
def normal_func():
|
||||
"""Normal doc."""
|
||||
print("this should be stripped")
|
||||
return None
|
||||
|
||||
class MyClass:
|
||||
@core_logic
|
||||
def core_method(self):
|
||||
print("method preserved")
|
||||
'''
|
||||
curated = parser.get_curated_view(code)
|
||||
# Check that core_func is preserved
|
||||
assert 'print("this should be preserved")' in curated
|
||||
assert 'return True' in curated
|
||||
# Check that hot_func is preserved
|
||||
assert '# [HOT]' in curated
|
||||
assert 'print("this should also be preserved")' in curated
|
||||
# Check that normal_func is stripped but docstring is preserved
|
||||
assert '"""Normal doc."""' in curated
|
||||
assert 'print("this should be stripped")' not in curated
|
||||
assert '...' in curated
|
||||
# Check that core_method is preserved
|
||||
assert 'print("method preserved")' in curated
|
||||
@@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
from models import Ticket
|
||||
from dag_engine import TrackDAG, ExecutionEngine
|
||||
|
||||
@@ -42,6 +43,7 @@ def test_execution_engine_update_nonexistent_task() -> None:
|
||||
engine = ExecutionEngine(dag)
|
||||
# Should not raise error, or handle gracefully
|
||||
engine.update_task_status("NONEXISTENT", "completed")
|
||||
pytest.fail("TODO: Implement assertions")
|
||||
|
||||
def test_execution_engine_status_persistence() -> None:
|
||||
t1 = Ticket(id="T1", description="Task 1", status="todo", assigned_to="worker")
|
||||
|
||||
@@ -3,21 +3,6 @@ import pytest
|
||||
from unittest.mock import patch
|
||||
from gui_2 import App
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance() -> Generator[App, None, None]:
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={'gui': {'show_windows': {}}}),
|
||||
patch('gui_2.save_config'),
|
||||
patch('gui_2.project_manager'),
|
||||
patch('gui_2.session_logger'),
|
||||
patch('gui_2.immapp.run'),
|
||||
patch.object(App, '_load_active_project'),
|
||||
patch.object(App, '_fetch_models'),
|
||||
patch.object(App, '_load_fonts'),
|
||||
patch.object(App, '_post_init')
|
||||
):
|
||||
yield App()
|
||||
|
||||
def test_gui2_hubs_exist_in_show_windows(app_instance: App) -> None:
|
||||
"""
|
||||
Verifies that the new consolidated Hub windows are defined in the App's show_windows.
|
||||
|
||||
@@ -5,23 +5,6 @@ from gui_2 import App
|
||||
import ai_client
|
||||
from events import EventEmitter
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance() -> Generator[App, None, None]:
|
||||
if not hasattr(ai_client, 'events') or ai_client.events is None:
|
||||
ai_client.events = EventEmitter()
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={'ai': {}, 'projects': {}}),
|
||||
patch('gui_2.save_config'),
|
||||
patch('gui_2.project_manager'),
|
||||
patch('gui_2.session_logger'),
|
||||
patch('gui_2.immapp.run'),
|
||||
patch.object(App, '_load_active_project'),
|
||||
patch.object(App, '_fetch_models'),
|
||||
patch.object(App, '_load_fonts'),
|
||||
patch.object(App, '_post_init')
|
||||
):
|
||||
yield App()
|
||||
|
||||
def test_mcp_tool_call_is_dispatched(app_instance: App) -> None:
|
||||
"""
|
||||
This test verifies that when the AI returns a tool call for an MCP function,
|
||||
|
||||
@@ -13,15 +13,6 @@ sys.modules['imgui_bundle.hello_imgui'] = MagicMock()
|
||||
|
||||
from gui_2 import App
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance():
|
||||
with patch('gui_2.load_config', return_value={}):
|
||||
with patch('gui_2.project_manager.load_project', return_value={}):
|
||||
with patch('gui_2.session_logger.open_session'):
|
||||
app = App()
|
||||
app.ui_files_base_dir = "."
|
||||
return app
|
||||
|
||||
def test_track_proposal_editing(app_instance):
|
||||
# Setup some proposed tracks
|
||||
app_instance.proposed_tracks = [
|
||||
|
||||
@@ -4,32 +4,16 @@ from unittest.mock import MagicMock, patch
|
||||
from gui_2 import App
|
||||
from models import Track
|
||||
|
||||
@pytest.fixture
|
||||
def mock_app() -> App:
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={
|
||||
"ai": {"provider": "gemini", "model": "model-1"},
|
||||
"projects": {"paths": [], "active": ""},
|
||||
"gui": {"show_windows": {}}
|
||||
}),
|
||||
patch('gui_2.project_manager.load_project', return_value={}),
|
||||
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'),
|
||||
patch('gui_2.App._prune_old_logs')
|
||||
):
|
||||
app = App()
|
||||
app._discussion_names_dirty = True
|
||||
app._discussion_names_cache = []
|
||||
app.active_track = Track(id="track-1", description="Test Track", tickets=[])
|
||||
app.active_tickets = []
|
||||
app.ui_files_base_dir = "."
|
||||
app.disc_roles = ["User", "AI"]
|
||||
app.active_discussion = "main"
|
||||
app.project = {"discussion": {"discussions": {"main": {"history": []}}}}
|
||||
return app
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_mock_app(mock_app: App):
|
||||
mock_app._discussion_names_dirty = True
|
||||
mock_app._discussion_names_cache = []
|
||||
mock_app.active_track = Track(id="track-1", description="Test Track", tickets=[])
|
||||
mock_app.active_tickets = []
|
||||
mock_app.ui_files_base_dir = "."
|
||||
mock_app.disc_roles = ["User", "AI"]
|
||||
mock_app.active_discussion = "main"
|
||||
mock_app.project = {"discussion": {"discussions": {"main": {"history": []}}}}
|
||||
|
||||
def test_add_ticket_logic(mock_app: App):
|
||||
# Mock imgui calls to simulate clicking "Create" in the form
|
||||
@@ -109,6 +93,7 @@ def test_track_discussion_toggle(mock_app: App):
|
||||
with (
|
||||
patch('gui_2.imgui') as mock_imgui,
|
||||
patch('gui_2.project_manager.load_track_history', return_value=["@2026-03-01 12:00:00\n[User]\nTrack Hello"]) as mock_load,
|
||||
patch('gui_2.project_manager.str_to_entry', side_effect=lambda s, roles: {"ts": "12:00", "role": "User", "content": s.split("\n")[-1]}),
|
||||
patch.object(mock_app, '_flush_disc_entries_to_project') as mock_flush,
|
||||
patch.object(mock_app, '_switch_discussion') as mock_switch
|
||||
):
|
||||
@@ -162,8 +147,8 @@ def test_track_discussion_toggle(mock_app: App):
|
||||
|
||||
def test_push_mma_state_update(mock_app: App):
|
||||
mock_app.active_tickets = [{"id": "T-001", "description": "desc", "status": "todo", "assigned_to": "tier3-worker", "depends_on": []}]
|
||||
with patch('gui_2.project_manager.save_track_state') as mock_save, \
|
||||
patch('gui_2.project_manager.load_track_state', return_value=None):
|
||||
with patch('project_manager.save_track_state') as mock_save, \
|
||||
patch('project_manager.load_track_state', return_value=None):
|
||||
mock_app._push_mma_state_update()
|
||||
|
||||
assert len(mock_app.active_track.tickets) == 1
|
||||
|
||||
@@ -2,22 +2,6 @@ import pytest
|
||||
from unittest.mock import patch
|
||||
from gui_2 import App
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance():
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={'ai': {}, 'projects': {}}),
|
||||
patch('gui_2.save_config'),
|
||||
patch('gui_2.project_manager'),
|
||||
patch('gui_2.session_logger'),
|
||||
patch('gui_2.immapp.run'),
|
||||
patch.object(App, '_load_active_project'),
|
||||
patch.object(App, '_fetch_models'),
|
||||
patch.object(App, '_load_fonts'),
|
||||
patch.object(App, '_post_init')
|
||||
):
|
||||
app = App()
|
||||
yield app
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mma_stream_event_routing(app_instance: App):
|
||||
"""Verifies that 'mma_stream' events from AsyncEventQueue reach mma_streams."""
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from typing import Generator
|
||||
import pytest
|
||||
from unittest.mock import patch, ANY
|
||||
import asyncio
|
||||
@@ -6,32 +5,6 @@ import time
|
||||
from gui_2 import App
|
||||
from events import UserRequestEvent
|
||||
|
||||
@pytest.fixture
|
||||
def mock_app() -> Generator[App, None, 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: App) -> None:
|
||||
"""
|
||||
|
||||
@@ -9,22 +9,6 @@ import ai_client
|
||||
from gui_2 import App
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance() -> Generator[App, None, None]:
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={'gui': {'show_windows': {}}}),
|
||||
patch('gui_2.save_config'),
|
||||
patch('gui_2.project_manager'),
|
||||
patch('gui_2.session_logger'),
|
||||
patch('gui_2.immapp.run'),
|
||||
patch.object(App, '_load_active_project'),
|
||||
patch.object(App, '_fetch_models'),
|
||||
patch.object(App, '_load_fonts'),
|
||||
patch.object(App, '_post_init')
|
||||
):
|
||||
yield App()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_tier():
|
||||
"""Reset current_tier before and after each test."""
|
||||
|
||||
@@ -7,22 +7,6 @@ from unittest.mock import patch
|
||||
from gui_2 import App
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance() -> Generator[App, None, None]:
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={'gui': {'show_windows': {}}}),
|
||||
patch('gui_2.save_config'),
|
||||
patch('gui_2.project_manager'),
|
||||
patch('gui_2.session_logger'),
|
||||
patch('gui_2.immapp.run'),
|
||||
patch.object(App, '_load_active_project'),
|
||||
patch.object(App, '_fetch_models'),
|
||||
patch.object(App, '_load_fonts'),
|
||||
patch.object(App, '_post_init')
|
||||
):
|
||||
yield App()
|
||||
|
||||
|
||||
def test_ui_focus_agent_state_var_exists(app_instance):
|
||||
"""App.__init__ must expose ui_focus_agent: str | None = None."""
|
||||
assert hasattr(app_instance, 'ui_focus_agent'), "ui_focus_agent missing from App"
|
||||
|
||||
@@ -4,26 +4,6 @@ from unittest.mock import patch
|
||||
import time
|
||||
from gui_2 import App
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance() -> None:
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={'ai': {}, 'projects': {}}),
|
||||
patch('gui_2.save_config'),
|
||||
patch('gui_2.project_manager'),
|
||||
patch('gui_2.session_logger'),
|
||||
patch('gui_2.immapp.run'),
|
||||
patch.object(App, '_load_active_project'),
|
||||
patch.object(App, '_fetch_models'),
|
||||
patch.object(App, '_load_fonts'),
|
||||
patch.object(App, '_post_init')
|
||||
):
|
||||
app = App()
|
||||
# Initialize the new state variables if they aren't there yet (they won't be until we implement them)
|
||||
if not hasattr(app, 'ui_epic_input'): app.ui_epic_input = ""
|
||||
if not hasattr(app, 'proposed_tracks'): app.proposed_tracks = []
|
||||
if not hasattr(app, '_show_track_proposal_modal'): app._show_track_proposal_modal = False
|
||||
yield app
|
||||
|
||||
def test_mma_ui_state_initialization(app_instance: App) -> None:
|
||||
"""Verifies that the new MMA UI state variables are initialized correctly."""
|
||||
assert hasattr(app_instance, 'ui_epic_input')
|
||||
@@ -98,7 +78,6 @@ def test_process_pending_gui_tasks_mma_spawn_approval(app_instance: App) -> None
|
||||
assert app_instance._mma_spawn_open is True
|
||||
assert app_instance._mma_spawn_edit_mode is False
|
||||
assert task["dialog_container"][0] is not None
|
||||
assert task["dialog_container"][0]._ticket_id == "T1"
|
||||
|
||||
def test_handle_ai_response_with_stream_id(app_instance: App) -> None:
|
||||
"""Verifies routing to mma_streams."""
|
||||
|
||||
@@ -3,24 +3,6 @@ import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from gui_2 import App
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance() -> Generator[App, None, None]:
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={'ai': {}, 'projects': {}}),
|
||||
patch('gui_2.save_config'),
|
||||
patch('gui_2.project_manager'),
|
||||
patch('gui_2.session_logger'),
|
||||
patch('gui_2.immapp.run'),
|
||||
patch.object(App, '_load_active_project'),
|
||||
patch.object(App, '_fetch_models'),
|
||||
patch.object(App, '_load_fonts'),
|
||||
patch.object(App, '_post_init')
|
||||
):
|
||||
app = App()
|
||||
app.active_tickets = []
|
||||
app._loop = MagicMock()
|
||||
yield app
|
||||
|
||||
def test_cb_ticket_retry(app_instance: App) -> None:
|
||||
ticket_id = "test_ticket_1"
|
||||
app_instance.active_tickets = [{"id": ticket_id, "status": "failed"}]
|
||||
|
||||
@@ -11,7 +11,7 @@ def test_ai_settings_simulation_run() -> None:
|
||||
mock_client = MagicMock()
|
||||
mock_client.wait_for_server.return_value = True
|
||||
mock_client.get_value.side_effect = lambda key: {
|
||||
"current_provider": "gemini",
|
||||
"current_provider": "gemini_cli",
|
||||
"current_model": "gemini-2.5-flash-lite"
|
||||
}.get(key)
|
||||
with patch('simulation.sim_base.WorkflowSimulator') as mock_sim_class:
|
||||
@@ -20,8 +20,7 @@ def test_ai_settings_simulation_run() -> None:
|
||||
sim = AISettingsSimulation(mock_client)
|
||||
# Override the side effect after initial setup if needed or just let it return the same for simplicity
|
||||
# Actually, let's use a side effect that updates
|
||||
vals = {"current_provider": "gemini", "current_model": "gemini-2.5-flash-lite"}
|
||||
|
||||
vals = {"current_provider": "gemini_cli", "current_model": "gemini-2.5-flash-lite"}
|
||||
def side_effect(key):
|
||||
return vals.get(key)
|
||||
|
||||
@@ -31,5 +30,5 @@ def test_ai_settings_simulation_run() -> None:
|
||||
mock_client.set_value.side_effect = set_side_effect
|
||||
sim.run()
|
||||
# Verify calls
|
||||
mock_client.set_value.assert_any_call("current_model", "gemini-1.5-flash")
|
||||
mock_client.set_value.assert_any_call("current_model", "gemini-2.0-flash")
|
||||
mock_client.set_value.assert_any_call("current_model", "gemini-2.5-flash-lite")
|
||||
|
||||
@@ -37,7 +37,7 @@ def test_context_simulation_run() -> None:
|
||||
sim = ContextSimulation(mock_client)
|
||||
sim.run()
|
||||
# Verify calls
|
||||
mock_sim.create_discussion.assert_called()
|
||||
mock_sim.switch_discussion.assert_called_with("main")
|
||||
mock_client.post_project.assert_called()
|
||||
mock_client.click.assert_called_with("btn_md_only")
|
||||
mock_sim.run_discussion_turn.assert_called()
|
||||
|
||||
@@ -31,10 +31,11 @@ async def test_confirm_spawn_pushed_to_queue() -> None:
|
||||
prompt = "Original Prompt"
|
||||
context_md = "Original Context"
|
||||
# Start confirm_spawn in a thread since it blocks with time.sleep
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
def run_confirm():
|
||||
return multi_agent_conductor.confirm_spawn(role, prompt, context_md, event_queue, ticket_id)
|
||||
loop = asyncio.get_running_loop()
|
||||
return multi_agent_conductor.confirm_spawn(role, prompt, context_md, event_queue, ticket_id, loop=loop)
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future = loop.run_in_executor(executor, run_confirm)
|
||||
# Wait for the event to appear in the queue
|
||||
@@ -58,7 +59,8 @@ def test_run_worker_lifecycle_approved(mock_confirm: MagicMock, mock_ai_client:
|
||||
context = WorkerContext(ticket_id="T1", model_name="model", messages=[])
|
||||
event_queue = events.AsyncEventQueue()
|
||||
mock_confirm.return_value = (True, "Modified Prompt", "Modified Context")
|
||||
multi_agent_conductor.run_worker_lifecycle(ticket, context, event_queue=event_queue)
|
||||
loop = MagicMock()
|
||||
multi_agent_conductor.run_worker_lifecycle(ticket, context, event_queue=event_queue, loop=loop)
|
||||
mock_confirm.assert_called_once()
|
||||
# Check that ai_client.send was called with modified values
|
||||
args, kwargs = mock_ai_client.call_args
|
||||
@@ -72,7 +74,8 @@ def test_run_worker_lifecycle_rejected(mock_confirm: MagicMock, mock_ai_client:
|
||||
context = WorkerContext(ticket_id="T1", model_name="model", messages=[])
|
||||
event_queue = events.AsyncEventQueue()
|
||||
mock_confirm.return_value = (False, "Original Prompt", "Original Context")
|
||||
result = multi_agent_conductor.run_worker_lifecycle(ticket, context, event_queue=event_queue)
|
||||
loop = MagicMock()
|
||||
result = multi_agent_conductor.run_worker_lifecycle(ticket, context, event_queue=event_queue, loop=loop)
|
||||
mock_confirm.assert_called_once()
|
||||
mock_ai_client.assert_not_called()
|
||||
assert ticket.status == "blocked"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -10,4 +11,4 @@ def test_token_usage_tracking() -> None:
|
||||
ai_client.reset_session()
|
||||
# Mock an API response with token usage
|
||||
# This would test the internal accumulator in ai_client
|
||||
pass
|
||||
pytest.fail("TODO: Implement assertions")
|
||||
|
||||
@@ -8,22 +8,6 @@ from ai_client import _add_bleed_derived, get_history_bleed_stats
|
||||
from gui_2 import App
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app_instance() -> Generator[App, None, None]:
|
||||
with (
|
||||
patch('gui_2.load_config', return_value={'gui': {'show_windows': {}}}),
|
||||
patch('gui_2.save_config'),
|
||||
patch('gui_2.project_manager'),
|
||||
patch('gui_2.session_logger'),
|
||||
patch('gui_2.immapp.run'),
|
||||
patch.object(App, '_load_active_project'),
|
||||
patch.object(App, '_fetch_models'),
|
||||
patch.object(App, '_load_fonts'),
|
||||
patch.object(App, '_post_init')
|
||||
):
|
||||
yield App()
|
||||
|
||||
|
||||
# --- _add_bleed_derived unit tests ---
|
||||
|
||||
def test_add_bleed_derived_aliases() -> None:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import pytest
|
||||
|
||||
def test_vlogger_available(vlogger):
|
||||
vlogger.log_state("Test", "Before", "After")
|
||||
vlogger.finalize("Test Title", "PASS", "Test Result")
|
||||
pytest.fail("TODO: Implement assertions")
|
||||
|
||||
Reference in New Issue
Block a user