KI-Konzil/backend/tests/test_god_mode.py
Claude 7becffcc89
Fix React hook anti-patterns, incorrect mock paths, and unused code
- Replace useState+useEffect sync pattern with useMemo in
  EdgeSettingsPanel.tsx to eliminate cascading re-renders
- Remove redundant draft state in NodeSettingsPanel.tsx and use
  store data directly, eliminating useEffect sync loop
- Fix mock paths in test_tools.py: patch tavily.TavilyClient and
  pypdf.PdfReader at their source modules (lazy imports)
- Remove unused variable assignment in routes.py (god mode reject)
- Remove unused node_lookup dicts in dynamic_graph_builder.py
- Remove unused imports across test files (Blueprint, CouncilState,
  pytest, llm assignments)
- Remove unused CouncilBlueprint type import in types.test.ts
- Run npm audit fix to resolve moderate vulnerability

All 107 backend tests and 26 frontend tests pass. Ruff, ESLint,
and TypeScript checks are clean.

https://claude.ai/code/session_01XqzyT6fhS8sUe9P5fCmuVU
2026-02-22 10:29:54 +00:00

191 lines
6.5 KiB
Python

"""
Tests for God Mode (interrupt_before) functionality.
All LLM calls are mocked — no real API calls are made in these tests.
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import pytest
from unittest.mock import patch, MagicMock
class TestBuildGraphGodMode:
"""Tests for graph compilation with god mode (interrupt_before)."""
def _make_simple_blueprint(self):
return {
"version": 1,
"name": "Test Council",
"nodes": [
{
"id": "master",
"label": "Master AI",
"systemPrompt": "You are the master writer.",
"model": "claude-3-5-sonnet",
"tools": {"webSearch": False, "pdfReader": False},
},
{
"id": "critic",
"label": "Critic AI",
"systemPrompt": "You are a critic who evaluates and scores drafts.",
"model": "claude-3-5-sonnet",
"tools": {"webSearch": False, "pdfReader": False},
},
],
"edges": [
{"id": "e1", "source": "master", "target": "critic", "type": "linear"},
],
}
@patch("services.dynamic_graph_builder._get_llm")
def test_build_graph_with_god_mode_compiles(self, mock_get_llm):
"""God mode graph should compile without error."""
from services.dynamic_graph_builder import build_graph_from_blueprint
blueprint = self._make_simple_blueprint()
graph = build_graph_from_blueprint(blueprint, god_mode=False)
assert graph is not None
def test_build_graph_without_god_mode(self):
"""Normal graph should compile without interrupt_before."""
from services.dynamic_graph_builder import build_graph_from_blueprint
blueprint = self._make_simple_blueprint()
graph = build_graph_from_blueprint(blueprint, god_mode=False)
assert graph is not None
class TestGodModeSessionManagement:
"""Tests for god mode session management functions."""
def test_get_god_mode_state_returns_none_for_unknown_run(self):
from services.dynamic_graph_builder import get_god_mode_state
result = get_god_mode_state("nonexistent-run-id")
assert result is None
@pytest.mark.asyncio
async def test_resume_god_mode_returns_none_for_unknown_run(self):
from services.dynamic_graph_builder import resume_god_mode
result = await resume_god_mode("nonexistent-run-id", action="approve")
assert result is None
@pytest.mark.asyncio
async def test_resume_god_mode_reject_cleans_up(self):
from services.dynamic_graph_builder import (
_god_mode_sessions,
resume_god_mode,
)
# Manually insert a fake session
_god_mode_sessions["test-run"] = {
"graph": MagicMock(),
"checkpointer": MagicMock(),
"thread_config": {"configurable": {"thread_id": "test-run"}},
}
result = await resume_god_mode("test-run", action="reject")
assert result is None
assert "test-run" not in _god_mode_sessions
class TestToolResolution:
"""Tests for the tool resolution helper."""
def test_resolve_tools_none_config(self):
from services.dynamic_graph_builder import _resolve_tools
assert _resolve_tools(None) == []
def test_resolve_tools_empty_config(self):
from services.dynamic_graph_builder import _resolve_tools
assert _resolve_tools({}) == []
def test_resolve_tools_web_search_only(self):
from services.dynamic_graph_builder import _resolve_tools
tools = _resolve_tools({"webSearch": True, "pdfReader": False})
assert len(tools) == 1
assert tools[0].name == "web_search"
def test_resolve_tools_pdf_only(self):
from services.dynamic_graph_builder import _resolve_tools
tools = _resolve_tools({"webSearch": False, "pdfReader": True})
assert len(tools) == 1
assert tools[0].name == "pdf_search"
def test_resolve_tools_both(self):
from services.dynamic_graph_builder import _resolve_tools
tools = _resolve_tools({"webSearch": True, "pdfReader": True})
assert len(tools) == 2
names = {t.name for t in tools}
assert names == {"web_search", "pdf_search"}
class TestInvokeWithTools:
"""Tests for the _invoke_with_tools helper."""
def test_invoke_without_tools_calls_llm_directly(self):
from services.dynamic_graph_builder import _invoke_with_tools
mock_llm = MagicMock()
mock_response = MagicMock()
mock_response.content = "Test response"
mock_llm.invoke.return_value = mock_response
result = _invoke_with_tools(mock_llm, ["msg1", "msg2"], [])
mock_llm.invoke.assert_called_once_with(["msg1", "msg2"])
assert result == mock_response
def test_invoke_with_tools_no_tool_calls(self):
from services.dynamic_graph_builder import _invoke_with_tools
mock_llm = MagicMock()
mock_bound = MagicMock()
mock_llm.bind_tools.return_value = mock_bound
mock_response = MagicMock()
mock_response.tool_calls = []
mock_response.content = "No tools needed"
mock_bound.invoke.return_value = mock_response
mock_tool = MagicMock()
mock_tool.name = "test_tool"
result = _invoke_with_tools(mock_llm, ["msg"], [mock_tool])
assert result == mock_response
def test_invoke_with_tools_executes_tool_calls(self):
from services.dynamic_graph_builder import _invoke_with_tools
mock_llm = MagicMock()
mock_bound = MagicMock()
mock_llm.bind_tools.return_value = mock_bound
# First call returns tool_calls
mock_response_with_tools = MagicMock()
mock_response_with_tools.tool_calls = [
{"name": "web_search", "args": {"query": "test"}, "id": "call-1"}
]
# Second call returns final answer
mock_final_response = MagicMock()
mock_final_response.content = "Final answer"
mock_bound.invoke.side_effect = [mock_response_with_tools, mock_final_response]
mock_tool = MagicMock()
mock_tool.name = "web_search"
mock_tool.invoke.return_value = "Search results"
result = _invoke_with_tools(mock_llm, ["msg"], [mock_tool])
mock_tool.invoke.assert_called_once_with({"query": "test"})
assert result == mock_final_response