Rebranding, refactoring, renaming, cleanup, updated docs

This commit is contained in:
Fahad
2025-06-12 10:40:43 +04:00
parent 9a55ca8898
commit fb66825bf6
55 changed files with 1048 additions and 1474 deletions

View File

@@ -1 +1 @@
# Tests for Gemini MCP Server
# Tests for Zen MCP Server

View File

@@ -1,5 +1,5 @@
"""
Pytest configuration for Gemini MCP Server tests
Pytest configuration for Zen MCP Server tests
"""
import asyncio
@@ -27,13 +27,15 @@ os.environ["DEFAULT_MODEL"] = "gemini-2.0-flash-exp"
# Force reload of config module to pick up the env var
import importlib
import config
importlib.reload(config)
# Set MCP_PROJECT_ROOT to a temporary directory for tests
# This provides a safe sandbox for file operations during testing
# Create a temporary directory that will be used as the project root for all tests
test_root = tempfile.mkdtemp(prefix="gemini_mcp_test_")
test_root = tempfile.mkdtemp(prefix="zen_mcp_test_")
os.environ["MCP_PROJECT_ROOT"] = test_root
# Configure asyncio for Windows compatibility
@@ -42,9 +44,9 @@ if sys.platform == "win32":
# Register providers for all tests
from providers import ModelProviderRegistry
from providers.base import ProviderType
from providers.gemini import GeminiModelProvider
from providers.openai import OpenAIModelProvider
from providers.base import ProviderType
# Register providers at test startup
ModelProviderRegistry.register_provider(ProviderType.GOOGLE, GeminiModelProvider)

View File

@@ -1,12 +1,14 @@
"""Helper functions for test mocking."""
from unittest.mock import Mock
from providers.base import ModelCapabilities, ProviderType
from providers.base import ModelCapabilities, ProviderType, RangeTemperatureConstraint
def create_mock_provider(model_name="gemini-2.0-flash-exp", max_tokens=1_048_576):
"""Create a properly configured mock provider."""
mock_provider = Mock()
# Set up capabilities
mock_capabilities = ModelCapabilities(
provider=ProviderType.GOOGLE,
@@ -17,14 +19,14 @@ def create_mock_provider(model_name="gemini-2.0-flash-exp", max_tokens=1_048_576
supports_system_prompts=True,
supports_streaming=True,
supports_function_calling=True,
temperature_range=(0.0, 2.0),
temperature_constraint=RangeTemperatureConstraint(0.0, 2.0, 0.7),
)
mock_provider.get_capabilities.return_value = mock_capabilities
mock_provider.get_provider_type.return_value = ProviderType.GOOGLE
mock_provider.supports_thinking_mode.return_value = False
mock_provider.validate_model_name.return_value = True
# Set up generate_content response
mock_response = Mock()
mock_response.content = "Test response"
@@ -33,7 +35,7 @@ def create_mock_provider(model_name="gemini-2.0-flash-exp", max_tokens=1_048_576
mock_response.friendly_name = "Gemini"
mock_response.provider = ProviderType.GOOGLE
mock_response.metadata = {"finish_reason": "STOP"}
mock_provider.generate_content.return_value = mock_response
return mock_provider

View File

@@ -1,11 +1,11 @@
"""Tests for auto mode functionality"""
import os
import pytest
from unittest.mock import patch, Mock
import importlib
import os
from unittest.mock import patch
import pytest
from mcp.types import TextContent
from tools.analyze import AnalyzeTool
@@ -16,23 +16,24 @@ class TestAutoMode:
"""Test that auto mode is detected correctly"""
# Save original
original = os.environ.get("DEFAULT_MODEL", "")
try:
# Test auto mode
os.environ["DEFAULT_MODEL"] = "auto"
import config
importlib.reload(config)
assert config.DEFAULT_MODEL == "auto"
assert config.IS_AUTO_MODE is True
# Test non-auto mode
os.environ["DEFAULT_MODEL"] = "pro"
importlib.reload(config)
assert config.DEFAULT_MODEL == "pro"
assert config.IS_AUTO_MODE is False
finally:
# Restore
if original:
@@ -44,7 +45,7 @@ class TestAutoMode:
def test_model_capabilities_descriptions(self):
"""Test that model capabilities are properly defined"""
from config import MODEL_CAPABILITIES_DESC
# Check all expected models are present
expected_models = ["flash", "pro", "o3", "o3-mini"]
for model in expected_models:
@@ -56,25 +57,26 @@ class TestAutoMode:
"""Test that tool schemas require model in auto mode"""
# Save original
original = os.environ.get("DEFAULT_MODEL", "")
try:
# Enable auto mode
os.environ["DEFAULT_MODEL"] = "auto"
import config
importlib.reload(config)
tool = AnalyzeTool()
schema = tool.get_input_schema()
# Model should be required
assert "model" in schema["required"]
# Model field should have detailed descriptions
model_schema = schema["properties"]["model"]
assert "enum" in model_schema
assert "flash" in model_schema["enum"]
assert "Choose the best model" in model_schema["description"]
finally:
# Restore
if original:
@@ -88,10 +90,10 @@ class TestAutoMode:
# This test uses the default from conftest.py which sets non-auto mode
tool = AnalyzeTool()
schema = tool.get_input_schema()
# Model should not be required
assert "model" not in schema["required"]
# Model field should have simpler description
model_schema = schema["properties"]["model"]
assert "enum" not in model_schema
@@ -102,29 +104,27 @@ class TestAutoMode:
"""Test that auto mode enforces model parameter"""
# Save original
original = os.environ.get("DEFAULT_MODEL", "")
try:
# Enable auto mode
os.environ["DEFAULT_MODEL"] = "auto"
import config
importlib.reload(config)
tool = AnalyzeTool()
# Mock the provider to avoid real API calls
with patch.object(tool, 'get_model_provider') as mock_provider:
with patch.object(tool, "get_model_provider"):
# Execute without model parameter
result = await tool.execute({
"files": ["/tmp/test.py"],
"prompt": "Analyze this"
})
result = await tool.execute({"files": ["/tmp/test.py"], "prompt": "Analyze this"})
# Should get error
assert len(result) == 1
response = result[0].text
assert "error" in response
assert "Model parameter is required" in response
finally:
# Restore
if original:
@@ -136,45 +136,57 @@ class TestAutoMode:
def test_model_field_schema_generation(self):
"""Test the get_model_field_schema method"""
from tools.base import BaseTool
# Create a minimal concrete tool for testing
class TestTool(BaseTool):
def get_name(self): return "test"
def get_description(self): return "test"
def get_input_schema(self): return {}
def get_system_prompt(self): return ""
def get_request_model(self): return None
async def prepare_prompt(self, request): return ""
def get_name(self):
return "test"
def get_description(self):
return "test"
def get_input_schema(self):
return {}
def get_system_prompt(self):
return ""
def get_request_model(self):
return None
async def prepare_prompt(self, request):
return ""
tool = TestTool()
# Save original
original = os.environ.get("DEFAULT_MODEL", "")
try:
# Test auto mode
os.environ["DEFAULT_MODEL"] = "auto"
os.environ["DEFAULT_MODEL"] = "auto"
import config
importlib.reload(config)
schema = tool.get_model_field_schema()
assert "enum" in schema
assert all(model in schema["enum"] for model in ["flash", "pro", "o3"])
assert "Choose the best model" in schema["description"]
# Test normal mode
os.environ["DEFAULT_MODEL"] = "pro"
importlib.reload(config)
schema = tool.get_model_field_schema()
assert "enum" not in schema
assert "Available:" in schema["description"]
assert "'pro'" in schema["description"]
finally:
# Restore
if original:
os.environ["DEFAULT_MODEL"] = original
else:
os.environ.pop("DEFAULT_MODEL", None)
importlib.reload(config)
importlib.reload(config)

View File

@@ -7,11 +7,11 @@ when Gemini doesn't explicitly ask a follow-up question.
import json
from unittest.mock import Mock, patch
from tests.mock_helpers import create_mock_provider
import pytest
from pydantic import Field
from tests.mock_helpers import create_mock_provider
from tools.base import BaseTool, ToolRequest
from tools.models import ContinuationOffer, ToolOutput
from utils.conversation_memory import MAX_CONVERSATION_TURNS
@@ -125,7 +125,7 @@ class TestClaudeContinuationOffers:
content="Analysis complete. The code looks good.",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -176,7 +176,7 @@ class TestClaudeContinuationOffers:
content=content_with_followup,
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -220,7 +220,7 @@ class TestClaudeContinuationOffers:
content="Continued analysis complete.",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider

View File

@@ -4,10 +4,10 @@ Tests for dynamic context request and collaboration features
import json
from unittest.mock import Mock, patch
from tests.mock_helpers import create_mock_provider
import pytest
from tests.mock_helpers import create_mock_provider
from tools.analyze import AnalyzeTool
from tools.debug import DebugIssueTool
from tools.models import ClarificationRequest, ToolOutput
@@ -41,10 +41,7 @@ class TestDynamicContextRequests:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content=clarification_json,
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content=clarification_json, usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -85,10 +82,7 @@ class TestDynamicContextRequests:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content=normal_response,
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content=normal_response, usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -112,10 +106,7 @@ class TestDynamicContextRequests:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content=malformed_json,
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content=malformed_json, usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -155,10 +146,7 @@ class TestDynamicContextRequests:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content=clarification_json,
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content=clarification_json, usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -245,10 +233,7 @@ class TestCollaborationWorkflow:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content=clarification_json,
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content=clarification_json, usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -287,10 +272,7 @@ class TestCollaborationWorkflow:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content=clarification_json,
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content=clarification_json, usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -317,10 +299,7 @@ class TestCollaborationWorkflow:
"""
mock_provider.generate_content.return_value = Mock(
content=final_response,
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content=final_response, usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
result2 = await tool.execute(

View File

@@ -2,21 +2,20 @@
Test that conversation history is correctly mapped to tool-specific fields
"""
import json
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from tests.mock_helpers import create_mock_provider
from datetime import datetime
from unittest.mock import MagicMock, patch
import pytest
from providers.base import ProviderType
from server import reconstruct_thread_context
from utils.conversation_memory import ConversationTurn, ThreadContext
from providers.base import ProviderType
@pytest.mark.asyncio
async def test_conversation_history_field_mapping():
"""Test that enhanced prompts are mapped to prompt field for all tools"""
# Test data for different tools - all use 'prompt' now
test_cases = [
{
@@ -40,7 +39,7 @@ async def test_conversation_history_field_mapping():
"original_value": "My analysis so far",
},
]
for test_case in test_cases:
# Create mock conversation context
mock_context = ThreadContext(
@@ -63,7 +62,7 @@ async def test_conversation_history_field_mapping():
],
initial_context={},
)
# Mock get_thread to return our test context
with patch("utils.conversation_memory.get_thread", return_value=mock_context):
with patch("utils.conversation_memory.add_turn", return_value=True):
@@ -71,43 +70,44 @@ async def test_conversation_history_field_mapping():
# Mock provider registry to avoid model lookup errors
with patch("providers.registry.ModelProviderRegistry.get_provider_for_model") as mock_get_provider:
from providers.base import ModelCapabilities
mock_provider = MagicMock()
mock_provider.get_capabilities.return_value = ModelCapabilities(
provider=ProviderType.GOOGLE,
model_name="gemini-2.0-flash-exp",
friendly_name="Gemini",
max_tokens=200000,
supports_extended_thinking=True
supports_extended_thinking=True,
)
mock_get_provider.return_value = mock_provider
# Mock conversation history building
mock_build.return_value = (
"=== CONVERSATION HISTORY ===\nPrevious conversation content\n=== END HISTORY ===",
1000 # mock token count
1000, # mock token count
)
# Create arguments with continuation_id
arguments = {
"continuation_id": "test-thread-123",
"prompt": test_case["original_value"],
"files": ["/test/file2.py"],
}
# Call reconstruct_thread_context
enhanced_args = await reconstruct_thread_context(arguments)
# Verify the enhanced prompt is in the prompt field
assert "prompt" in enhanced_args
enhanced_value = enhanced_args["prompt"]
# Should contain conversation history
assert "=== CONVERSATION HISTORY ===" in enhanced_value
assert "Previous conversation content" in enhanced_value
# Should contain the new user input
assert "=== NEW USER INPUT ===" in enhanced_value
assert test_case["original_value"] in enhanced_value
# Should have token budget
assert "_remaining_tokens" in enhanced_args
assert enhanced_args["_remaining_tokens"] > 0
@@ -116,7 +116,7 @@ async def test_conversation_history_field_mapping():
@pytest.mark.asyncio
async def test_unknown_tool_defaults_to_prompt():
"""Test that unknown tools default to using 'prompt' field"""
mock_context = ThreadContext(
thread_id="test-thread-456",
tool_name="unknown_tool",
@@ -125,7 +125,7 @@ async def test_unknown_tool_defaults_to_prompt():
turns=[],
initial_context={},
)
with patch("utils.conversation_memory.get_thread", return_value=mock_context):
with patch("utils.conversation_memory.add_turn", return_value=True):
with patch("utils.conversation_memory.build_conversation_history", return_value=("History", 500)):
@@ -133,9 +133,9 @@ async def test_unknown_tool_defaults_to_prompt():
"continuation_id": "test-thread-456",
"prompt": "User input",
}
enhanced_args = await reconstruct_thread_context(arguments)
# Should default to 'prompt' field
assert "prompt" in enhanced_args
assert "History" in enhanced_args["prompt"]
@@ -145,27 +145,27 @@ async def test_unknown_tool_defaults_to_prompt():
async def test_tool_parameter_standardization():
"""Test that all tools use standardized 'prompt' parameter"""
from tools.analyze import AnalyzeRequest
from tools.debug import DebugIssueRequest
from tools.codereview import CodeReviewRequest
from tools.thinkdeep import ThinkDeepRequest
from tools.debug import DebugIssueRequest
from tools.precommit import PrecommitRequest
from tools.thinkdeep import ThinkDeepRequest
# Test analyze tool uses prompt
analyze = AnalyzeRequest(files=["/test.py"], prompt="What does this do?")
assert analyze.prompt == "What does this do?"
# Test debug tool uses prompt
debug = DebugIssueRequest(prompt="Error occurred")
assert debug.prompt == "Error occurred"
# Test codereview tool uses prompt
review = CodeReviewRequest(files=["/test.py"], prompt="Review this")
assert review.prompt == "Review this"
# Test thinkdeep tool uses prompt
think = ThinkDeepRequest(prompt="My analysis")
assert think.prompt == "My analysis"
# Test precommit tool uses prompt (optional)
precommit = PrecommitRequest(path="/repo", prompt="Fix bug")
assert precommit.prompt == "Fix bug"
assert precommit.prompt == "Fix bug"

View File

@@ -12,11 +12,11 @@ Claude had shared in earlier turns.
import json
from unittest.mock import Mock, patch
from tests.mock_helpers import create_mock_provider
import pytest
from pydantic import Field
from tests.mock_helpers import create_mock_provider
from tools.base import BaseTool, ToolRequest
from utils.conversation_memory import ConversationTurn, ThreadContext
@@ -116,7 +116,7 @@ class TestConversationHistoryBugFix:
content="Response with conversation context",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_provider.generate_content.side_effect = capture_prompt
@@ -176,7 +176,7 @@ class TestConversationHistoryBugFix:
content="Response without history",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_provider.generate_content.side_effect = capture_prompt
@@ -214,7 +214,7 @@ class TestConversationHistoryBugFix:
content="New conversation response",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_provider.generate_content.side_effect = capture_prompt
@@ -298,7 +298,7 @@ class TestConversationHistoryBugFix:
content="Analysis of new files complete",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_provider.generate_content.side_effect = capture_prompt

View File

@@ -7,11 +7,11 @@ allowing multi-turn conversations to span multiple tool types.
import json
from unittest.mock import Mock, patch
from tests.mock_helpers import create_mock_provider
import pytest
from pydantic import Field
from tests.mock_helpers import create_mock_provider
from tools.base import BaseTool, ToolRequest
from utils.conversation_memory import ConversationTurn, ThreadContext
@@ -117,7 +117,7 @@ class TestCrossToolContinuation:
content=content_with_followup,
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -165,7 +165,7 @@ class TestCrossToolContinuation:
content="Critical security vulnerability confirmed. The authentication function always returns true, bypassing all security checks.",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -285,7 +285,7 @@ class TestCrossToolContinuation:
content="Security review of auth.py shows vulnerabilities",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider

View File

@@ -11,7 +11,6 @@ import os
import shutil
import tempfile
from unittest.mock import MagicMock, patch
from tests.mock_helpers import create_mock_provider
import pytest
from mcp.types import TextContent
@@ -77,7 +76,7 @@ class TestLargePromptHandling:
content="This is a test response",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -102,7 +101,7 @@ class TestLargePromptHandling:
content="Processed large prompt",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -214,7 +213,7 @@ class TestLargePromptHandling:
content="Success",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -247,7 +246,7 @@ class TestLargePromptHandling:
content="Success",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -278,7 +277,7 @@ class TestLargePromptHandling:
content="Success",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider
@@ -300,7 +299,7 @@ class TestLargePromptHandling:
content="Success",
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
mock_get_provider.return_value = mock_provider

View File

@@ -1,141 +0,0 @@
"""
Live integration tests for google-genai library
These tests require GEMINI_API_KEY to be set and will make real API calls
To run these tests manually:
python tests/test_live_integration.py
Note: These tests are excluded from regular pytest runs to avoid API rate limits.
They confirm that the google-genai library integration works correctly with live data.
"""
import asyncio
import os
import sys
import tempfile
from pathlib import Path
# Add parent directory to path to allow imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import json
from tools.analyze import AnalyzeTool
from tools.thinkdeep import ThinkDeepTool
async def run_manual_live_tests():
"""Run live tests manually without pytest"""
print("🚀 Running manual live integration tests...")
# Check API key
if not os.environ.get("GEMINI_API_KEY"):
print("❌ GEMINI_API_KEY not found. Set it to run live tests.")
return False
try:
# Test google-genai import
print("✅ google-genai library import successful")
# Test tool integration
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write("def hello(): return 'world'")
temp_path = f.name
try:
# Test AnalyzeTool
tool = AnalyzeTool()
result = await tool.execute(
{
"files": [temp_path],
"prompt": "What does this code do?",
"thinking_mode": "low",
}
)
if result and result[0].text:
print("✅ AnalyzeTool live test successful")
else:
print("❌ AnalyzeTool live test failed")
return False
# Test ThinkDeepTool
think_tool = ThinkDeepTool()
result = await think_tool.execute(
{
"prompt": "Testing live integration",
"thinking_mode": "minimal", # Fast test
}
)
if result and result[0].text and "Extended Analysis" in result[0].text:
print("✅ ThinkDeepTool live test successful")
else:
print("❌ ThinkDeepTool live test failed")
return False
# Test collaboration/clarification request
print("\n🔄 Testing dynamic context request (collaboration)...")
# Create a specific test case designed to trigger clarification
# We'll use analyze tool with a question that requires seeing files
analyze_tool = AnalyzeTool()
# Ask about dependencies without providing package files
result = await analyze_tool.execute(
{
"files": [temp_path], # Only Python file, no package.json
"prompt": "What npm packages and their versions does this project depend on? List all dependencies.",
"thinking_mode": "minimal", # Fast test
}
)
if result and result[0].text:
response_data = json.loads(result[0].text)
print(f" Response status: {response_data['status']}")
if response_data["status"] == "requires_clarification":
print("✅ Dynamic context request successfully triggered!")
clarification = json.loads(response_data["content"])
print(f" Gemini asks: {clarification.get('question', 'N/A')}")
if "files_needed" in clarification:
print(f" Files requested: {clarification['files_needed']}")
# Verify it's asking for package-related files
expected_files = [
"package.json",
"package-lock.json",
"yarn.lock",
]
if any(f in str(clarification["files_needed"]) for f in expected_files):
print(" ✅ Correctly identified missing package files!")
else:
print(" ⚠️ Unexpected files requested")
else:
# This is a failure - we specifically designed this to need clarification
print("❌ Expected clarification request but got direct response")
print(" This suggests the dynamic context feature may not be working")
print(" Response:", response_data.get("content", "")[:200])
return False
else:
print("❌ Collaboration test failed - no response")
return False
finally:
Path(temp_path).unlink(missing_ok=True)
print("\n🎉 All manual live tests passed!")
print("✅ google-genai library working correctly")
print("✅ All tools can make live API calls")
print("✅ Thinking modes functioning properly")
return True
except Exception as e:
print(f"❌ Live test failed: {e}")
return False
if __name__ == "__main__":
# Run live tests when script is executed directly
success = asyncio.run(run_manual_live_tests())
exit(0 if success else 1)

View File

@@ -167,9 +167,7 @@ TEMPERATURE_ANALYTICAL = 0.2 # For code review, debugging
add_turn(thread_id, "assistant", "First response", files=[config_path], tool_name="precommit")
# Second request with continuation - should skip already embedded files
PrecommitRequest(
path=temp_dir, files=[config_path], continuation_id=thread_id, prompt="Follow-up review"
)
PrecommitRequest(path=temp_dir, files=[config_path], continuation_id=thread_id, prompt="Follow-up review")
files_to_embed_2 = tool.filter_new_files([config_path], thread_id)
assert len(files_to_embed_2) == 0, "Continuation should skip already embedded files"

View File

@@ -7,7 +7,6 @@ normal-sized prompts after implementing the large prompt handling feature.
import json
from unittest.mock import MagicMock, patch
from tests.mock_helpers import create_mock_provider
import pytest
@@ -33,7 +32,7 @@ class TestPromptRegression:
content=text,
usage={"input_tokens": 10, "output_tokens": 20, "total_tokens": 30},
model_name="gemini-2.0-flash-exp",
metadata={"finish_reason": "STOP"}
metadata={"finish_reason": "STOP"},
)
return _create_response
@@ -47,7 +46,9 @@ class TestPromptRegression:
mock_provider = MagicMock()
mock_provider.get_provider_type.return_value = MagicMock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = mock_model_response("This is a helpful response about Python.")
mock_provider.generate_content.return_value = mock_model_response(
"This is a helpful response about Python."
)
mock_get_provider.return_value = mock_provider
result = await tool.execute({"prompt": "Explain Python decorators"})

View File

@@ -1,10 +1,9 @@
"""Tests for the model provider abstraction system"""
import pytest
from unittest.mock import Mock, patch
import os
from unittest.mock import Mock, patch
from providers import ModelProviderRegistry, ModelProvider, ModelResponse, ModelCapabilities
from providers import ModelProviderRegistry, ModelResponse
from providers.base import ProviderType
from providers.gemini import GeminiModelProvider
from providers.openai import OpenAIModelProvider
@@ -12,56 +11,56 @@ from providers.openai import OpenAIModelProvider
class TestModelProviderRegistry:
"""Test the model provider registry"""
def setup_method(self):
"""Clear registry before each test"""
ModelProviderRegistry._providers.clear()
ModelProviderRegistry._initialized_providers.clear()
def test_register_provider(self):
"""Test registering a provider"""
ModelProviderRegistry.register_provider(ProviderType.GOOGLE, GeminiModelProvider)
assert ProviderType.GOOGLE in ModelProviderRegistry._providers
assert ModelProviderRegistry._providers[ProviderType.GOOGLE] == GeminiModelProvider
@patch.dict(os.environ, {"GEMINI_API_KEY": "test-key"})
def test_get_provider(self):
"""Test getting a provider instance"""
ModelProviderRegistry.register_provider(ProviderType.GOOGLE, GeminiModelProvider)
provider = ModelProviderRegistry.get_provider(ProviderType.GOOGLE)
assert provider is not None
assert isinstance(provider, GeminiModelProvider)
assert provider.api_key == "test-key"
@patch.dict(os.environ, {}, clear=True)
def test_get_provider_no_api_key(self):
"""Test getting provider without API key returns None"""
ModelProviderRegistry.register_provider(ProviderType.GOOGLE, GeminiModelProvider)
provider = ModelProviderRegistry.get_provider(ProviderType.GOOGLE)
assert provider is None
@patch.dict(os.environ, {"GEMINI_API_KEY": "test-key"})
def test_get_provider_for_model(self):
"""Test getting provider for a specific model"""
ModelProviderRegistry.register_provider(ProviderType.GOOGLE, GeminiModelProvider)
provider = ModelProviderRegistry.get_provider_for_model("gemini-2.0-flash-exp")
assert provider is not None
assert isinstance(provider, GeminiModelProvider)
def test_get_available_providers(self):
"""Test getting list of available providers"""
ModelProviderRegistry.register_provider(ProviderType.GOOGLE, GeminiModelProvider)
ModelProviderRegistry.register_provider(ProviderType.OPENAI, OpenAIModelProvider)
providers = ModelProviderRegistry.get_available_providers()
assert len(providers) == 2
assert ProviderType.GOOGLE in providers
assert ProviderType.OPENAI in providers
@@ -69,50 +68,50 @@ class TestModelProviderRegistry:
class TestGeminiProvider:
"""Test Gemini model provider"""
def test_provider_initialization(self):
"""Test provider initialization"""
provider = GeminiModelProvider(api_key="test-key")
assert provider.api_key == "test-key"
assert provider.get_provider_type() == ProviderType.GOOGLE
def test_get_capabilities(self):
"""Test getting model capabilities"""
provider = GeminiModelProvider(api_key="test-key")
capabilities = provider.get_capabilities("gemini-2.0-flash-exp")
assert capabilities.provider == ProviderType.GOOGLE
assert capabilities.model_name == "gemini-2.0-flash-exp"
assert capabilities.max_tokens == 1_048_576
assert not capabilities.supports_extended_thinking
def test_get_capabilities_pro_model(self):
"""Test getting capabilities for Pro model with thinking support"""
provider = GeminiModelProvider(api_key="test-key")
capabilities = provider.get_capabilities("gemini-2.5-pro-preview-06-05")
assert capabilities.supports_extended_thinking
def test_model_shorthand_resolution(self):
"""Test model shorthand resolution"""
provider = GeminiModelProvider(api_key="test-key")
assert provider.validate_model_name("flash")
assert provider.validate_model_name("pro")
capabilities = provider.get_capabilities("flash")
assert capabilities.model_name == "gemini-2.0-flash-exp"
def test_supports_thinking_mode(self):
"""Test thinking mode support detection"""
provider = GeminiModelProvider(api_key="test-key")
assert not provider.supports_thinking_mode("gemini-2.0-flash-exp")
assert provider.supports_thinking_mode("gemini-2.5-pro-preview-06-05")
@patch("google.genai.Client")
def test_generate_content(self, mock_client_class):
"""Test content generation"""
@@ -131,15 +130,11 @@ class TestGeminiProvider:
mock_response.usage_metadata = mock_usage
mock_client.models.generate_content.return_value = mock_response
mock_client_class.return_value = mock_client
provider = GeminiModelProvider(api_key="test-key")
response = provider.generate_content(
prompt="Test prompt",
model_name="gemini-2.0-flash-exp",
temperature=0.7
)
response = provider.generate_content(prompt="Test prompt", model_name="gemini-2.0-flash-exp", temperature=0.7)
assert isinstance(response, ModelResponse)
assert response.content == "Generated content"
assert response.model_name == "gemini-2.0-flash-exp"
@@ -151,38 +146,38 @@ class TestGeminiProvider:
class TestOpenAIProvider:
"""Test OpenAI model provider"""
def test_provider_initialization(self):
"""Test provider initialization"""
provider = OpenAIModelProvider(api_key="test-key", organization="test-org")
assert provider.api_key == "test-key"
assert provider.organization == "test-org"
assert provider.get_provider_type() == ProviderType.OPENAI
def test_get_capabilities_o3(self):
"""Test getting O3 model capabilities"""
provider = OpenAIModelProvider(api_key="test-key")
capabilities = provider.get_capabilities("o3-mini")
assert capabilities.provider == ProviderType.OPENAI
assert capabilities.model_name == "o3-mini"
assert capabilities.max_tokens == 200_000
assert not capabilities.supports_extended_thinking
def test_validate_model_names(self):
"""Test model name validation"""
provider = OpenAIModelProvider(api_key="test-key")
assert provider.validate_model_name("o3")
assert provider.validate_model_name("o3-mini")
assert not provider.validate_model_name("gpt-4o")
assert not provider.validate_model_name("invalid-model")
def test_no_thinking_mode_support(self):
"""Test that no OpenAI models support thinking mode"""
provider = OpenAIModelProvider(api_key="test-key")
assert not provider.supports_thinking_mode("o3")
assert not provider.supports_thinking_mode("o3-mini")
assert not provider.supports_thinking_mode("o3-mini")

View File

@@ -3,11 +3,11 @@ Tests for the main server functionality
"""
from unittest.mock import Mock, patch
from tests.mock_helpers import create_mock_provider
import pytest
from server import handle_call_tool, handle_list_tools
from tests.mock_helpers import create_mock_provider
class TestServerTools:
@@ -56,10 +56,7 @@ class TestServerTools:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content="Chat response",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Chat response", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -81,6 +78,6 @@ class TestServerTools:
assert len(result) == 1
response = result[0].text
assert "Gemini MCP Server v" in response # Version agnostic check
assert "Zen MCP Server v" in response # Version agnostic check
assert "Available Tools:" in response
assert "thinkdeep" in response

View File

@@ -3,10 +3,10 @@ Tests for thinking_mode functionality across all tools
"""
from unittest.mock import Mock, patch
from tests.mock_helpers import create_mock_provider
import pytest
from tests.mock_helpers import create_mock_provider
from tools.analyze import AnalyzeTool
from tools.codereview import CodeReviewTool
from tools.debug import DebugIssueTool
@@ -45,10 +45,7 @@ class TestThinkingModes:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = True
mock_provider.generate_content.return_value = Mock(
content="Minimal thinking response",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Minimal thinking response", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -66,7 +63,9 @@ class TestThinkingModes:
# Verify generate_content was called with thinking_mode
mock_provider.generate_content.assert_called_once()
call_kwargs = mock_provider.generate_content.call_args[1]
assert call_kwargs.get("thinking_mode") == "minimal" or (not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None) # thinking_mode parameter
assert call_kwargs.get("thinking_mode") == "minimal" or (
not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None
) # thinking_mode parameter
# Parse JSON response
import json
@@ -83,10 +82,7 @@ class TestThinkingModes:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = True
mock_provider.generate_content.return_value = Mock(
content="Low thinking response",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Low thinking response", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -104,7 +100,9 @@ class TestThinkingModes:
# Verify generate_content was called with thinking_mode
mock_provider.generate_content.assert_called_once()
call_kwargs = mock_provider.generate_content.call_args[1]
assert call_kwargs.get("thinking_mode") == "low" or (not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None)
assert call_kwargs.get("thinking_mode") == "low" or (
not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None
)
assert "Code Review" in result[0].text
@@ -116,10 +114,7 @@ class TestThinkingModes:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = True
mock_provider.generate_content.return_value = Mock(
content="Medium thinking response",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Medium thinking response", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -136,7 +131,9 @@ class TestThinkingModes:
# Verify generate_content was called with thinking_mode
mock_provider.generate_content.assert_called_once()
call_kwargs = mock_provider.generate_content.call_args[1]
assert call_kwargs.get("thinking_mode") == "medium" or (not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None)
assert call_kwargs.get("thinking_mode") == "medium" or (
not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None
)
assert "Debug Analysis" in result[0].text
@@ -148,10 +145,7 @@ class TestThinkingModes:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = True
mock_provider.generate_content.return_value = Mock(
content="High thinking response",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="High thinking response", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -169,7 +163,9 @@ class TestThinkingModes:
# Verify generate_content was called with thinking_mode
mock_provider.generate_content.assert_called_once()
call_kwargs = mock_provider.generate_content.call_args[1]
assert call_kwargs.get("thinking_mode") == "high" or (not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None)
assert call_kwargs.get("thinking_mode") == "high" or (
not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None
)
@pytest.mark.asyncio
@patch("tools.base.BaseTool.get_model_provider")
@@ -179,10 +175,7 @@ class TestThinkingModes:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = True
mock_provider.generate_content.return_value = Mock(
content="Max thinking response",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Max thinking response", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -199,7 +192,9 @@ class TestThinkingModes:
# Verify generate_content was called with thinking_mode
mock_provider.generate_content.assert_called_once()
call_kwargs = mock_provider.generate_content.call_args[1]
assert call_kwargs.get("thinking_mode") == "high" or (not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None)
assert call_kwargs.get("thinking_mode") == "high" or (
not mock_provider.supports_thinking_mode.return_value and call_kwargs.get("thinking_mode") is None
)
assert "Extended Analysis by Gemini" in result[0].text

View File

@@ -4,10 +4,10 @@ Tests for individual tool implementations
import json
from unittest.mock import Mock, patch
from tests.mock_helpers import create_mock_provider
import pytest
from tests.mock_helpers import create_mock_provider
from tools import AnalyzeTool, ChatTool, CodeReviewTool, DebugIssueTool, ThinkDeepTool
@@ -37,10 +37,7 @@ class TestThinkDeepTool:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = True
mock_provider.generate_content.return_value = Mock(
content="Extended analysis",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Extended analysis", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -91,10 +88,7 @@ class TestCodeReviewTool:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content="Security issues found",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Security issues found", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -139,10 +133,7 @@ class TestDebugIssueTool:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content="Root cause: race condition",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Root cause: race condition", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -190,10 +181,7 @@ class TestAnalyzeTool:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content="Architecture analysis",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Architecture analysis", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider
@@ -307,10 +295,7 @@ class TestAbsolutePathValidation:
mock_provider.get_provider_type.return_value = Mock(value="google")
mock_provider.supports_thinking_mode.return_value = False
mock_provider.generate_content.return_value = Mock(
content="Analysis complete",
usage={},
model_name="gemini-2.0-flash-exp",
metadata={}
content="Analysis complete", usage={}, model_name="gemini-2.0-flash-exp", metadata={}
)
mock_get_provider.return_value = mock_provider