test: fix broken tests across suite and resolve port conflicts

This commit is contained in:
2026-03-11 23:49:23 -04:00
parent 036c2f360a
commit 1a14cee3ce
6 changed files with 113 additions and 39 deletions

View File

@@ -643,7 +643,7 @@ class HookServer:
if not _has_app_attr(self.app, '_api_event_queue'): _set_app_attr(self.app, '_api_event_queue', [])
if not _has_app_attr(self.app, '_api_event_queue_lock'): _set_app_attr(self.app, '_api_event_queue_lock', threading.Lock())
self.websocket_server = WebSocketServer(self.app)
self.websocket_server = WebSocketServer(self.app, port=self.port + 1)
self.websocket_server.start()
eq = _get_app_attr(self.app, 'event_queue')

View File

@@ -154,6 +154,7 @@ class AppController:
self._loop_thread: Optional[threading.Thread] = None
self.tracks: List[Dict[str, Any]] = []
self.active_track: Optional[models.Track] = None
self.engine: Optional[multi_agent_conductor.ConductorEngine] = None
self.active_tickets: List[Dict[str, Any]] = []
self.mma_streams: Dict[str, str] = {}
self._worker_status: Dict[str, str] = {} # stream_id -> "running" | "completed" | "failed" | "killed"
@@ -1795,6 +1796,11 @@ class AppController:
try:
self.project = project_manager.load_project(path)
self.active_project_path = path
new_root = Path(path).parent
self.preset_manager = presets.PresetManager(new_root)
self.tool_preset_manager = tool_presets.ToolPresetManager(new_root)
from src.personas import PersonaManager
self.persona_manager = PersonaManager(new_root)
except Exception as e:
self._set_status(f"failed to load project: {e}")
return
@@ -1870,6 +1876,7 @@ class AppController:
self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles()
def _apply_preset(self, name: str, scope: str) -> None:
print(f"[DEBUG] _apply_preset: name={name}, scope={scope}")
if name == "None":
if scope == "global":
self.ui_global_preset_name = ""
@@ -1878,6 +1885,7 @@ class AppController:
return
preset = self.presets.get(name)
if not preset:
print(f"[DEBUG] _apply_preset: preset {name} not found in {list(self.presets.keys())}")
return
if scope == "global":
self.ui_global_system_prompt = preset.system_prompt
@@ -1887,6 +1895,7 @@ class AppController:
self.ui_project_preset_name = name
def _cb_save_preset(self, name, content, scope):
print(f"[DEBUG] _cb_save_preset: name={name}, scope={scope}")
if not name or not name.strip():
raise ValueError("Preset name cannot be empty or whitespace.")
preset = models.Preset(
@@ -1895,6 +1904,7 @@ class AppController:
)
self.preset_manager.save_preset(preset, scope)
self.presets = self.preset_manager.load_all()
print(f"[DEBUG] _cb_save_preset: saved {name}, total presets now {len(self.presets)}")
def _cb_delete_preset(self, name, scope):
self.preset_manager.delete_preset(name, scope)
@@ -2425,6 +2435,7 @@ class AppController:
# Use the active track object directly to start execution
self._set_mma_status("running")
engine = multi_agent_conductor.ConductorEngine(self.active_track, self.event_queue, auto_queue=not self.mma_step_mode)
self.engine = engine
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=self.active_track.id)
full_md, _, _ = aggregate.run(flat)
threading.Thread(target=engine.run, kwargs={"md_content": full_md}, daemon=True).start()
@@ -2498,6 +2509,7 @@ class AppController:
self._pending_gui_tasks.append({'action': 'refresh_from_project'})
# 4. Initialize ConductorEngine and run loop
engine = multi_agent_conductor.ConductorEngine(track, self.event_queue, auto_queue=not self.mma_step_mode)
self.engine = engine
# Use current full markdown context for the track execution
track_id_param = track.id
flat = project_manager.flat_config(self.project, self.active_discussion, track_id=track_id_param)
@@ -2522,6 +2534,66 @@ class AppController:
break
self.event_queue.put("mma_skip", {"ticket_id": ticket_id})
def _spawn_worker(self, ticket_id: str, data: dict = None) -> None:
"""Manually initiates a sub-agent execution for a ticket."""
if self.engine:
for t in self.active_track.tickets:
if t.id == ticket_id:
t.status = "todo"
t.step_mode = False
break
self.engine.engine.auto_queue = True
self.event_queue.put("mma_retry", {"ticket_id": ticket_id})
def kill_worker(self, worker_id: str) -> None:
"""Aborts a running worker."""
if self.engine:
self.engine.kill_worker(worker_id)
def pause_mma(self) -> None:
"""Pauses the global MMA loop."""
self.mma_step_mode = True
if self.engine:
self.engine.pause()
def resume_mma(self) -> None:
"""Resumes the global MMA loop."""
self.mma_step_mode = False
if self.engine:
self.engine.resume()
def inject_context(self, data: dict) -> None:
"""Programmatic context injection."""
file_path = data.get("file_path")
if file_path:
if not os.path.isabs(file_path):
file_path = os.path.relpath(file_path, self.ui_files_base_dir)
existing = next((f for f in self.files if (f.path if hasattr(f, "path") else str(f)) == file_path), None)
if not existing:
item = models.FileItem(path=file_path)
self.files.append(item)
self._refresh_from_project()
def mutate_dag(self, data: dict) -> None:
"""Modifies task dependencies."""
ticket_id = data.get("ticket_id")
depends_on = data.get("depends_on")
if ticket_id and depends_on is not None:
for t in self.active_tickets:
if t.get("id") == ticket_id:
t["depends_on"] = depends_on
break
if self.active_track:
for t in self.active_track.tickets:
if t.id == ticket_id:
t.depends_on = depends_on
break
if self.engine:
from src.dag_engine import TrackDAG, ExecutionEngine
self.engine.dag = TrackDAG(self.active_track.tickets)
self.engine.engine = ExecutionEngine(self.engine.dag, auto_queue=self.engine.engine.auto_queue)
self._push_mma_state_update()
def _cb_run_conductor_setup(self) -> None:
base = paths.get_conductor_dir()
if not base.exists():

View File

@@ -33,9 +33,16 @@ def _make_app(**kwargs):
app.ui_new_ticket_desc = ""
app.ui_new_ticket_target = ""
app.ui_new_ticket_deps = ""
app.ui_new_ticket_deps = ""
app.ui_selected_ticket_id = ""
app.is_viewing_prior_session = False
app.ui_separate_task_dag = False
app.ui_separate_tier1 = False
app.ui_separate_tier2 = False
app.ui_separate_tier3 = False
app.ui_separate_tier4 = False
app.show_windows = {}
app.proposed_tracks = []
app._avg_ticket_time = 300
mock_engine = MagicMock()
mock_engine._pause_event = MagicMock()
mock_engine._pause_event.is_set.return_value = False

View File

@@ -14,7 +14,6 @@ def test_load_all_merged(tmp_path, monkeypatch):
global_file.write_text("""
[presets.global_only]
system_prompt = "global prompt"
temperature = 0.5
[presets.override_me]
system_prompt = "original prompt"
@@ -24,7 +23,6 @@ system_prompt = "original prompt"
project_file.write_text("""
[presets.project_only]
system_prompt = "project prompt"
max_output_tokens = 100
[presets.override_me]
system_prompt = "overridden prompt"
@@ -38,9 +36,7 @@ system_prompt = "overridden prompt"
assert len(presets) == 3
assert presets["global_only"].system_prompt == "global prompt"
assert presets["global_only"].temperature == 0.5
assert presets["project_only"].system_prompt == "project prompt"
assert presets["project_only"].max_output_tokens == 100
assert presets["override_me"].system_prompt == "overridden prompt"
def test_save_preset_global(tmp_path, monkeypatch):
@@ -49,14 +45,13 @@ def test_save_preset_global(tmp_path, monkeypatch):
monkeypatch.setattr("src.presets.get_global_presets_path", lambda: global_file)
pm = PresetManager()
preset = Preset(name="new_global", system_prompt="new global prompt", temperature=0.7)
preset = Preset(name="new_global", system_prompt="new global prompt")
pm.save_preset(preset, scope="global")
assert global_file.exists()
loaded_presets = pm.load_all()
assert "new_global" in loaded_presets
assert loaded_presets["new_global"].system_prompt == "new global prompt"
assert loaded_presets["new_global"].temperature == 0.7
def test_save_preset_project(tmp_path, monkeypatch):
"""Tests saving a preset to the project scope."""
@@ -69,7 +64,7 @@ def test_save_preset_project(tmp_path, monkeypatch):
monkeypatch.setattr("src.presets.get_project_presets_path", lambda p: project_file)
pm = PresetManager(project_root=project_root)
preset = Preset(name="new_project", system_prompt="new project prompt", max_output_tokens=500)
preset = Preset(name="new_project", system_prompt="new project prompt")
pm.save_preset(preset, scope="project")
assert project_file.exists()
@@ -79,7 +74,6 @@ def test_save_preset_project(tmp_path, monkeypatch):
loaded_presets = pm.load_all()
assert "new_project" in loaded_presets
assert loaded_presets["new_project"].system_prompt == "new project prompt"
assert loaded_presets["new_project"].max_output_tokens == 500
def test_save_preset_project_no_root():
"""Tests that saving to project scope fails if no project root is provided."""

View File

@@ -54,8 +54,7 @@ def test_preset_switching(live_gui):
global_presets_path.write_text(tomli_w.dumps({
"presets": {
"TestGlobal": {
"system_prompt": "Global Prompt",
"temperature": 0.7
"system_prompt": "Global Prompt"
}
}
}))
@@ -64,12 +63,10 @@ def test_preset_switching(live_gui):
project_presets_path.write_text(tomli_w.dumps({
"presets": {
"TestProject": {
"system_prompt": "Project Prompt",
"temperature": 0.3
"system_prompt": "Project Prompt"
},
"TestGlobal": { # Override
"system_prompt": "Overridden Prompt",
"temperature": 0.5
"system_prompt": "Overridden Prompt"
}
}
}))
@@ -93,37 +90,35 @@ def test_preset_switching(live_gui):
"callback": "_apply_preset",
"args": ["TestGlobal", "global"]
})
time.sleep(1)
time.sleep(2)
# Verify state
state = client.get_gui_state()
assert state["global_preset_name"] == "TestGlobal"
assert state["global_system_prompt"] == "Overridden Prompt"
assert state["temperature"] == 0.5
assert state.get("global_preset_name") == "TestGlobal", f"Expected TestGlobal, got {state.get('global_preset_name')}. Full state: {state}"
assert state.get("global_system_prompt") == "Overridden Prompt", f"Expected Overridden Prompt, got {state.get('global_system_prompt')}"
# Apply Project Preset
client.push_event("custom_callback", {
"callback": "_apply_preset",
"args": ["TestProject", "project"]
})
time.sleep(1)
time.sleep(2)
state = client.get_gui_state()
assert state["project_preset_name"] == "TestProject"
assert state["project_system_prompt"] == "Project Prompt"
assert state["temperature"] == 0.3
assert state.get("project_preset_name") == "TestProject", f"Expected TestProject, got {state.get('project_preset_name')}. Full state: {state}"
assert state.get("project_system_prompt") == "Project Prompt", f"Expected Project Prompt, got {state.get('project_system_prompt')}"
# Select "None"
client.push_event("custom_callback", {
"callback": "_apply_preset",
"args": ["None", "global"]
})
time.sleep(1)
time.sleep(2)
state = client.get_gui_state()
assert not state.get("global_preset_name") # Should be None or ""
assert not state.get("global_preset_name"), f"Expected global_preset_name to be empty, got {state.get('global_preset_name')}" # Should be None or ""
# Prompt remains from previous application
assert state["global_system_prompt"] == "Overridden Prompt"
assert state.get("global_system_prompt") == "Overridden Prompt"
finally:
# Cleanup
@@ -138,22 +133,26 @@ def test_preset_manager_modal(live_gui):
# Open Modal
client.set_value("show_preset_manager_modal", True)
time.sleep(1)
time.sleep(2)
# Create New Preset via Modal Logic (triggering the callback directly for reliability in headless)
client.push_event("custom_callback", {
"callback": "_cb_save_preset",
"args": ["ModalPreset", "Modal Content", 0.9, 1.0, 4096, "global"]
"args": ["ModalPreset", "Modal Content", "global"]
})
time.sleep(2)
time.sleep(3)
# Verify file exists
assert global_presets_path.exists()
if not global_presets_path.exists():
state = client.get_gui_state()
assert global_presets_path.exists(), f"Global presets file not found at {global_presets_path}. Full state: {state}"
with open(global_presets_path, "rb") as f:
import tomllib
data = tomllib.load(f)
assert "ModalPreset" in data["presets"]
assert data["presets"]["ModalPreset"]["temperature"] == 0.9
assert data["presets"]["ModalPreset"]["system_prompt"] == "Modal Content"
# Delete Preset via Modal Logic
client.push_event("custom_callback", {

View File

@@ -20,15 +20,17 @@ class TestThemeNervFx(unittest.TestCase):
# Assert
mock_imgui.get_foreground_draw_list.assert_called_once()
# height is 600, range(0, 600, 2) is 300 calls
self.assertEqual(mock_draw_list.add_line.call_count, 300)
# Vignette: v_steps = 15. height=600 is plenty for all insets.
self.assertEqual(mock_draw_list.add_rect.call_count, 15)
# Noise: 30 calls to add_rect_filled
self.assertEqual(mock_draw_list.add_rect_filled.call_count, 30)
# width is 800, range(0, 800, 3) is 267 calls
# total = 300 + 267 = 567 calls
self.assertEqual(mock_draw_list.add_line.call_count, 567)
# Vignette: v_steps = 20. height=600 is plenty for all insets.
self.assertEqual(mock_draw_list.add_rect.call_count, 20)
# Noise: 40 calls to add_rect_filled
self.assertEqual(mock_draw_list.add_rect_filled.call_count, 40)
# Verify some calls
mock_draw_list.add_line.assert_any_call((0.0, 0.0), (800.0, 0.0), 0x12345678, 1.0)
mock_draw_list.add_line.assert_any_call((0.0, 598.0), (800.0, 598.0), 0x12345678, 1.0)
mock_draw_list.add_line.assert_any_call((0.0, 0.0), (800.0, 0.0), 0x12345678, 1.2)
mock_draw_list.add_line.assert_any_call((0.0, 598.0), (800.0, 598.0), 0x12345678, 0.8)
@patch("src.theme_nerv_fx.imgui")
def test_crt_filter_disabled(self, mock_imgui):