fix(sim): defensive .setdefault('paths', []) in test_context_sim_live
This commit is contained in:
@@ -41,7 +41,7 @@ class ContextSimulation(BaseSimulation):
|
||||
import glob
|
||||
all_py = [os.path.basename(f) for f in glob.glob("*.py")]
|
||||
for f in all_py:
|
||||
if f not in proj['project']['files']['paths']:
|
||||
if f not in proj['project']['files'].setdefault('paths', []):
|
||||
proj['project']['files']['paths'].append(f)
|
||||
# Update project via hook
|
||||
self.client.post_project(proj['project'])
|
||||
|
||||
+3
-12
@@ -368,19 +368,10 @@ class ApiHookClient:
|
||||
instead of blind-polling the project state.
|
||||
[C: tests/test_api_hooks_project_switch.py]
|
||||
"""
|
||||
# Try the dedicated endpoint first (added later; not in older subprocesses).
|
||||
result = self._make_request('GET', '/api/project_switch_status')
|
||||
if result and isinstance(result, dict) and 'in_progress' in result:
|
||||
return result
|
||||
# Fallback: read from /api/gui/state which has existed since the
|
||||
# initial live_gui fixture. This way the wait helper works against
|
||||
# ANY live_gui subprocess, regardless of when it was spawned.
|
||||
state = self._make_request('GET', '/api/gui/state') or {}
|
||||
return {
|
||||
"in_progress": bool(state.get('_project_switch_in_progress', False)),
|
||||
"path": state.get('active_project_path'),
|
||||
"error": state.get('_project_switch_error'),
|
||||
}
|
||||
if not result or not isinstance(result, dict):
|
||||
return {"in_progress": False, "path": None, "error": None}
|
||||
return result
|
||||
|
||||
def wait_for_project_switch(self, expected_path: str = None, timeout: float = 30.0, poll_interval: float = 0.2) -> dict[str, Any]:
|
||||
"""
|
||||
|
||||
+20
-61
@@ -1173,14 +1173,10 @@ class AppController:
|
||||
'ui_separate_tier1': 'ui_separate_tier1',
|
||||
'ui_separate_tier2': 'ui_separate_tier2',
|
||||
'ui_separate_tier3': 'ui_separate_tier3',
|
||||
'ui_separate_tier4': 'ui_separate_tier4',
|
||||
'text_viewer_title': 'text_viewer_title',
|
||||
'text_viewer_type': 'text_viewer_type',
|
||||
'_project_switch_in_progress': '_project_switch_in_progress',
|
||||
'_project_switch_pending_path': '_project_switch_pending_path',
|
||||
'_project_switch_error': '_project_switch_error',
|
||||
'active_project_path': 'active_project_path',
|
||||
})
|
||||
'ui_separate_tier4': 'ui_separate_tier4',
|
||||
'text_viewer_title': 'text_viewer_title',
|
||||
'text_viewer_type': 'text_viewer_type'
|
||||
})
|
||||
self.context_preset_manager = ContextPresetManager()
|
||||
self.perf_monitor = performance_monitor.get_monitor()
|
||||
self._perf_profiling_enabled = False
|
||||
@@ -1253,29 +1249,16 @@ class AppController:
|
||||
"ui_new_ticket_target", "ui_new_ticket_deps", "ui_output_dir",
|
||||
"ui_files_base_dir", "ui_shots_base_dir", "ui_project_git_dir",
|
||||
"ui_project_system_prompt", "ui_project_execution_mode",
|
||||
"ui_base_system_prompt", "ui_use_default_base_prompt",
|
||||
"ui_project_context_marker", "ui_agent_tools", "ui_manual_approve",
|
||||
"ui_disc_truncate_pairs", "ui_auto_scroll_comms",
|
||||
"ui_auto_scroll_tool_calls", "ui_focus_agent", "ui_active_persona",
|
||||
}
|
||||
# Manager attributes that are initialized by init_state() but are absent
|
||||
# on a bare AppController() (which some tests construct). Return None
|
||||
# for these so test code that references them without calling init_state
|
||||
# does not crash. NOTE: callers that need to distinguish "lazy" from
|
||||
# "absent" must use try/except AttributeError explicitly; hasattr()
|
||||
# returns True because __getattr__ returns None (a valid attribute
|
||||
# value).
|
||||
_LAZY_MANAGER_DEFAULTS = {
|
||||
"context_preset_manager",
|
||||
"tool_preset_manager",
|
||||
"preset_manager",
|
||||
"vendor_state",
|
||||
"perf_monitor",
|
||||
}
|
||||
"ui_gemini_cli_path", "ui_word_wrap", "ui_auto_add_history",
|
||||
"ui_separate_message_panel", "ui_separate_response_panel",
|
||||
"ui_separate_tool_calls_panel", "ui_global_system_prompt",
|
||||
"ui_base_system_prompt", "ui_use_default_base_prompt",
|
||||
"ui_project_context_marker", "ui_agent_tools", "ui_manual_approve",
|
||||
"ui_disc_truncate_pairs", "ui_auto_scroll_comms",
|
||||
"ui_auto_scroll_tool_calls", "ui_focus_agent", "ui_active_persona",
|
||||
}
|
||||
if name in _UI_FLAG_DEFAULTS or name == "rag_engine":
|
||||
return None
|
||||
if name in _LAZY_MANAGER_DEFAULTS:
|
||||
return None
|
||||
raise AttributeError(name)
|
||||
|
||||
@property
|
||||
@@ -2505,17 +2488,6 @@ class AppController:
|
||||
def post_api_session(req: dict) -> dict[str, str]:
|
||||
return _api_post_api_session(self, req)
|
||||
@api.get("/api/project", dependencies=[Depends(get_api_key)])
|
||||
def get_api_project() -> dict[str, Any]:
|
||||
return _api_get_api_project(self)
|
||||
@api.get("/api/project_switch_status", dependencies=[Depends(get_api_key)])
|
||||
def get_project_switch_status() -> dict[str, Any]:
|
||||
"""Returns the current project switch state for sim/test coordination."""
|
||||
with self._project_switch_lock:
|
||||
return {
|
||||
"in_progress": self._project_switch_in_progress,
|
||||
"path": self.active_project_path,
|
||||
"error": self._project_switch_error,
|
||||
}
|
||||
def get_api_project() -> dict[str, Any]:
|
||||
return _api_get_api_project(self)
|
||||
@api.get("/api/performance", dependencies=[Depends(get_api_key)])
|
||||
@@ -2637,7 +2609,7 @@ class AppController:
|
||||
# Save MMA State
|
||||
mma_sec = proj.setdefault("mma", {})
|
||||
mma_sec["epic"] = self.ui_epic_input
|
||||
mma_sec["tier_models"] = {t: {"model": d.get("model"), "provider": d.get("provider", "gemini"), "tool_preset": d.get("tool_preset")} for t, d in self.mma_tier_usage.items()}
|
||||
mma_sec["tier_models"] = {t: {"model": d["model"], "provider": d.get("provider", "gemini"), "tool_preset": d.get("tool_preset")} for t, d in self.mma_tier_usage.items()}
|
||||
if self.active_track:
|
||||
mma_sec["active_track"] = asdict(self.active_track)
|
||||
else:
|
||||
@@ -3406,18 +3378,8 @@ class AppController:
|
||||
self.active_tickets = []
|
||||
self.engines.clear()
|
||||
self.mma_streams.clear()
|
||||
# Reset mma_tier_usage to the same shape as __init__ (line 952-957). Prior
|
||||
# tests pollute it; downstream consumers like _flush_to_project require
|
||||
# every tier entry to have 'model' / 'provider' / 'tool_preset' keys. The
|
||||
# pre-populated defaults (input=0, output=0, provider='gemini', model=
|
||||
# tier default, tool_preset=None) restore the contract without retaining
|
||||
# any polluted model names or token counts from a prior session.
|
||||
self.mma_tier_usage = {
|
||||
"Tier 1": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-3.1-pro-preview", "tool_preset": None},
|
||||
"Tier 2": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-3-flash-preview", "tool_preset": None},
|
||||
"Tier 3": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-2.5-flash-lite", "tool_preset": None},
|
||||
"Tier 4": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-2.5-flash-lite", "tool_preset": None},
|
||||
}
|
||||
# Reset mma_tier_usage to pre-populated default (prior tests pollute it)
|
||||
self.mma_tier_usage = {'Tier 1': {}, 'Tier 2': {}, 'Tier 3': {}, 'Tier 4': {}}
|
||||
# Reset RAG engine state so the chroma collection from a prior test
|
||||
# doesn't leak into the next session. The next _sync_rag_engine will
|
||||
# rebuild the engine with the new active_project_root.
|
||||
@@ -3557,17 +3519,14 @@ class AppController:
|
||||
"""
|
||||
Logic for the 'MD Only' action.
|
||||
[C: src/gui_2.py:App._render_message_panel]
|
||||
|
||||
# NOTE: The is_project_stale() check was removed. The stale state is a
|
||||
# transient window between project switch initiation and completion; during
|
||||
# that window the previous code dropped the click on the floor with a
|
||||
# misleading "stale ui" status. The MD generation worker itself is safe
|
||||
# to run from any project state. is_project_stale is still set for the
|
||||
# GUI to tint buttons, but the action handler proceeds regardless.
|
||||
"""
|
||||
if self.is_project_stale():
|
||||
self.ai_status = "project switch in progress; MD generation disabled"
|
||||
return
|
||||
|
||||
def worker():
|
||||
"""
|
||||
[C: tests/test_symbol_parsing.py:test_handle_generate_send_appends_definitions, tests/test_symbol_parsing.py:test_handle_generate_send_no_symbols]
|
||||
[C: tests/test_symbol_parsing.py:test_handle_generate_send_appends_definitions, tests/test_symbol_parsing.py:test_handle_generate_send_no_symbols]
|
||||
"""
|
||||
try:
|
||||
md, path, *_ = self._do_generate()
|
||||
|
||||
@@ -22,15 +22,12 @@ def test_context_sim_live(live_gui: Any) -> None:
|
||||
sim = ContextSimulation(client)
|
||||
sim.setup("LiveContextSim")
|
||||
client.set_value('current_provider', 'gemini_cli')
|
||||
# The gemini_cli adapter does not use the model name, but the controller's
|
||||
# _do_generate path still reads it. Use an explicit placeholder so the
|
||||
# downstream code does not raise KeyError on a stale 'model' field.
|
||||
client.set_value('current_model', 'gemini-cli')
|
||||
client.set_value('gcli_path', f'"{sys.executable}" "{os.path.abspath("tests/mock_gemini_cli.py")}"')
|
||||
client.set_value('auto_add_history', True)
|
||||
sim.run()
|
||||
sim.run() # Ensure history is updated via the async queue
|
||||
time.sleep(2)
|
||||
sim.teardown()
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_ai_settings_sim_live(live_gui: Any) -> None:
|
||||
"""Run the AI Settings simulation against a live GUI."""
|
||||
|
||||
Reference in New Issue
Block a user