Compare commits

...

2 Commits

19 changed files with 32 additions and 35 deletions

View File

@@ -2,7 +2,7 @@ import subprocess
import sys import sys
import os import os
def run_diag(role, prompt): def run_diag(role: str, prompt: str) -> str:
print(f"--- Running Diag for {role} ---") print(f"--- Running Diag for {role} ---")
cmd = [sys.executable, "scripts/mma_exec.py", "--role", role, prompt] cmd = [sys.executable, "scripts/mma_exec.py", "--role", role, prompt]
try: try:

View File

@@ -2,7 +2,7 @@ import subprocess
import pytest import pytest
import os import os
def run_ps_script(role, prompt): def run_ps_script(role: str, prompt: str) -> subprocess.CompletedProcess:
"""Helper to run the run_subagent.ps1 script.""" """Helper to run the run_subagent.ps1 script."""
# Using -File is safer and handles arguments better # Using -File is safer and handles arguments better
cmd = [ cmd = [

View File

@@ -5,6 +5,7 @@ import requests
import os import os
import signal import signal
import sys import sys
from typing import Generator
import os import os
# Ensure project root is in path # Ensure project root is in path
@@ -14,14 +15,14 @@ from api_hook_client import ApiHookClient
import ai_client import ai_client
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def reset_ai_client() -> None: def reset_ai_client() -> Generator[None, None, None]:
"""Reset ai_client global state between every test to prevent state pollution.""" """Reset ai_client global state between every test to prevent state pollution."""
ai_client.reset_session() ai_client.reset_session()
# Default to a safe model # Default to a safe model
ai_client.set_provider("gemini", "gemini-2.5-flash-lite") ai_client.set_provider("gemini", "gemini-2.5-flash-lite")
yield yield
def kill_process_tree(pid): def kill_process_tree(pid: int | None) -> None:
"""Robustly kills a process and all its children.""" """Robustly kills a process and all its children."""
if pid is None: if pid is None:
return return
@@ -41,7 +42,7 @@ def kill_process_tree(pid):
print(f"[Fixture] Error killing process tree {pid}: {e}") print(f"[Fixture] Error killing process tree {pid}: {e}")
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def live_gui() -> None: def live_gui() -> Generator[tuple[subprocess.Popen, str], None, None]:
""" """
Session-scoped fixture that starts gui_2.py with --enable-test-hooks. Session-scoped fixture that starts gui_2.py with --enable-test-hooks.
""" """

View File

@@ -3,8 +3,7 @@ import json
import subprocess import subprocess
import os import os
def main(): def main() -> None:
# Debug log to stderr
sys.stderr.write(f"DEBUG: mock_gemini_cli called with args: {sys.argv}\n") sys.stderr.write(f"DEBUG: mock_gemini_cli called with args: {sys.argv}\n")
sys.stderr.write(f"DEBUG: GEMINI_CLI_HOOK_CONTEXT: {os.environ.get('GEMINI_CLI_HOOK_CONTEXT')}\n") sys.stderr.write(f"DEBUG: GEMINI_CLI_HOOK_CONTEXT: {os.environ.get('GEMINI_CLI_HOOK_CONTEXT')}\n")
# Read prompt from stdin # Read prompt from stdin

View File

@@ -8,14 +8,11 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from ai_client import set_agent_tools, _build_anthropic_tools from ai_client import set_agent_tools, _build_anthropic_tools
def test_set_agent_tools(): def test_set_agent_tools() -> None:
# Correct usage: pass a dict
agent_tools = {"read_file": True, "list_directory": False} agent_tools = {"read_file": True, "list_directory": False}
set_agent_tools(agent_tools) set_agent_tools(agent_tools)
def test_build_anthropic_tools_conversion(): def test_build_anthropic_tools_conversion() -> None:
# _build_anthropic_tools takes no arguments and uses the global _agent_tools
# We set a tool to True and check if it appears in the output
set_agent_tools({"read_file": True}) set_agent_tools({"read_file": True})
anthropic_tools = _build_anthropic_tools() anthropic_tools = _build_anthropic_tools()
tool_names = [t["name"] for t in anthropic_tools] tool_names = [t["name"] for t in anthropic_tools]

View File

@@ -1,11 +1,12 @@
import pytest import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from typing import Generator
from gui_2 import App from gui_2 import App
import ai_client import ai_client
from events import EventEmitter from events import EventEmitter
@pytest.fixture @pytest.fixture
def app_instance() -> None: def app_instance() -> Generator[type[App], None, None]:
""" """
Fixture to create an instance of the gui_2.App class for testing. Fixture to create an instance of the gui_2.App class for testing.
It mocks functions that would render a window or block execution. It mocks functions that would render a window or block execution.
@@ -25,7 +26,7 @@ def app_instance() -> None:
): ):
yield App yield App
def test_app_subscribes_to_events(app_instance): def test_app_subscribes_to_events(app_instance: type[App]) -> None:
""" """
This test checks that the App's __init__ method subscribes the necessary This test checks that the App's __init__ method subscribes the necessary
event handlers to the ai_client.events emitter. event handlers to the ai_client.events emitter.

View File

@@ -1,11 +1,12 @@
import pytest import pytest
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from typing import Generator
from gui_2 import App from gui_2 import App
import ai_client import ai_client
from events import EventEmitter from events import EventEmitter
@pytest.fixture @pytest.fixture
def app_instance() -> None: def app_instance() -> Generator[App, None, None]:
if not hasattr(ai_client, 'events') or ai_client.events is None: if not hasattr(ai_client, 'events') or ai_client.events is None:
ai_client.events = EventEmitter() ai_client.events = EventEmitter()
with ( with (
@@ -21,7 +22,7 @@ def app_instance() -> None:
): ):
yield App() yield App()
def test_mcp_tool_call_is_dispatched(app_instance): 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, This test verifies that when the AI returns a tool call for an MCP function,
the ai_client correctly dispatches it to mcp_client. the ai_client correctly dispatches it to mcp_client.

View File

@@ -11,7 +11,7 @@ from api_hook_client import ApiHookClient
# Session-wide storage for comparing metrics across parameterized fixture runs # Session-wide storage for comparing metrics across parameterized fixture runs
_shared_metrics = {} _shared_metrics = {}
def test_performance_benchmarking(live_gui): def test_performance_benchmarking(live_gui: tuple) -> None:
""" """
Collects performance metrics for the current GUI script (parameterized as gui.py and gui_2.py). Collects performance metrics for the current GUI script (parameterized as gui.py and gui_2.py).
""" """

View File

@@ -5,7 +5,7 @@ from gui_2 import App
from events import UserRequestEvent from events import UserRequestEvent
@pytest.fixture @pytest.fixture
def mock_gui(): def mock_gui() -> App:
with ( with (
patch('gui_2.load_config', return_value={ patch('gui_2.load_config', return_value={
"ai": {"provider": "gemini", "model": "model-1"}, "ai": {"provider": "gemini", "model": "model-1"},
@@ -22,8 +22,7 @@ def mock_gui():
gui = App() gui = App()
return gui return gui
def test_handle_generate_send_pushes_event(mock_gui): def test_handle_generate_send_pushes_event(mock_gui: App) -> None:
# Mock _do_generate to return sample data
mock_gui._do_generate = MagicMock(return_value=( mock_gui._do_generate = MagicMock(return_value=(
"full_md", "path", [], "stable_md", "disc_text" "full_md", "path", [], "stable_md", "disc_text"
)) ))

View File

@@ -1,13 +1,14 @@
import pytest import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from typing import Generator
import dearpygui.dearpygui as dpg import dearpygui.dearpygui as dpg
import gui_legacy import gui_legacy
from gui_legacy import App from gui_legacy import App
import ai_client import ai_client
@pytest.fixture @pytest.fixture
def app_instance() -> None: def app_instance() -> Generator[App, None, None]:
""" """
Fixture to create an instance of the App class for testing. Fixture to create an instance of the App class for testing.
It creates a real DPG context but mocks functions that would It creates a real DPG context but mocks functions that would
@@ -33,8 +34,7 @@ def app_instance() -> None:
yield app yield app
dpg.destroy_context() dpg.destroy_context()
def test_gui_updates_on_event(app_instance): def test_gui_updates_on_event(app_instance: App) -> None:
# Patch dependencies for the test
with patch('dearpygui.dearpygui.set_value') as mock_set_value, \ with patch('dearpygui.dearpygui.set_value') as mock_set_value, \
patch('dearpygui.dearpygui.does_item_exist', return_value=True), \ patch('dearpygui.dearpygui.does_item_exist', return_value=True), \
patch('dearpygui.dearpygui.configure_item'), \ patch('dearpygui.dearpygui.configure_item'), \

View File

@@ -8,7 +8,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from api_hook_client import ApiHookClient from api_hook_client import ApiHookClient
def test_idle_performance_requirements(live_gui): def test_idle_performance_requirements(live_gui) -> None:
""" """
Requirement: GUI must maintain stable performance on idle. Requirement: GUI must maintain stable performance on idle.
""" """

View File

@@ -8,7 +8,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from api_hook_client import ApiHookClient from api_hook_client import ApiHookClient
def test_comms_volume_stress_performance(live_gui): def test_comms_volume_stress_performance(live_gui) -> None:
""" """
Stress test: Inject many session entries and verify performance doesn't degrade. Stress test: Inject many session entries and verify performance doesn't degrade.
""" """

View File

@@ -23,7 +23,7 @@ def test_hooks_disabled_by_default() -> None:
app = gui_legacy.App() app = gui_legacy.App()
assert getattr(app, 'test_hooks_enabled', False) is False assert getattr(app, 'test_hooks_enabled', False) is False
def test_live_hook_server_responses(live_gui): def test_live_hook_server_responses(live_gui) -> None:
""" """
Verifies the live hook server (started via fixture) responds correctly to all major endpoints. Verifies the live hook server (started via fixture) responds correctly to all major endpoints.
""" """

View File

@@ -9,7 +9,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from api_hook_client import ApiHookClient from api_hook_client import ApiHookClient
@pytest.mark.integration @pytest.mark.integration
def test_full_live_workflow(live_gui): def test_full_live_workflow(live_gui) -> None:
""" """
Integration test that drives the GUI through a full workflow. Integration test that drives the GUI through a full workflow.
""" """

View File

@@ -4,7 +4,7 @@ import requests
import pytest import pytest
from api_hook_client import ApiHookClient from api_hook_client import ApiHookClient
def test_api_ask_client_method(live_gui): def test_api_ask_client_method(live_gui) -> None:
""" """
Tests the request_confirmation method in ApiHookClient. Tests the request_confirmation method in ApiHookClient.
""" """
@@ -13,7 +13,7 @@ def test_api_ask_client_method(live_gui):
client.get_events() client.get_events()
results = {"response": None, "error": None} results = {"response": None, "error": None}
def make_blocking_request(): def make_blocking_request() -> None:
try: try:
# This call should block until we respond # This call should block until we respond
results["response"] = client.request_confirmation( results["response"] = client.request_confirmation(

View File

@@ -8,7 +8,7 @@ from models import TrackState, Metadata, Ticket
# Import the persistence functions from project_manager # Import the persistence functions from project_manager
from project_manager import save_track_state, load_track_state from project_manager import save_track_state, load_track_state
def test_track_state_persistence(tmp_path): def test_track_state_persistence(tmp_path) -> None:
""" """
Tests saving and loading a TrackState object to/from a TOML file. Tests saving and loading a TrackState object to/from a TOML file.
1. Create a TrackState object with sample metadata, discussion, and tasks. 1. Create a TrackState object with sample metadata, discussion, and tasks.

View File

@@ -1,4 +1,3 @@
import subprocess import subprocess
import time import time
import sys import sys
@@ -14,7 +13,7 @@ from api_hook_client import ApiHookClient
class TestMMAGUIRobust(unittest.TestCase): class TestMMAGUIRobust(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls) -> None:
# 1. Launch gui_2.py with --enable-test-hooks # 1. Launch gui_2.py with --enable-test-hooks
cls.gui_command = [sys.executable, "gui_2.py", "--enable-test-hooks"] cls.gui_command = [sys.executable, "gui_2.py", "--enable-test-hooks"]
print(f"Launching GUI: {' '.join(cls.gui_command)}") print(f"Launching GUI: {' '.join(cls.gui_command)}")

View File

@@ -10,7 +10,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from api_hook_client import ApiHookClient from api_hook_client import ApiHookClient
@pytest.mark.integration @pytest.mark.integration
def test_mma_epic_lifecycle(live_gui): def test_mma_epic_lifecycle(live_gui) -> None:
""" """
Integration test for the full MMA Epic lifecycle. Integration test for the full MMA Epic lifecycle.
1. Start App. 1. Start App.

View File

@@ -9,7 +9,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from api_hook_client import ApiHookClient from api_hook_client import ApiHookClient
@pytest.mark.integration @pytest.mark.integration
def test_mma_epic_simulation(live_gui): def test_mma_epic_simulation(live_gui) -> None:
""" """
Integration test for MMA epic simulation. Integration test for MMA epic simulation.
Red Phase: asserts False. Red Phase: asserts False.