421 lines
19 KiB
Python
421 lines
19 KiB
Python
"""
|
|
Tests for the tracer tool functionality
|
|
"""
|
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
|
|
from tools.models import ToolModelCategory
|
|
from tools.tracer import TracerRequest, TracerTool
|
|
|
|
|
|
class TestTracerTool:
|
|
"""Test suite for the Tracer tool"""
|
|
|
|
@pytest.fixture
|
|
def tracer_tool(self):
|
|
"""Create a tracer tool instance for testing"""
|
|
return TracerTool()
|
|
|
|
@pytest.fixture
|
|
def mock_model_response(self):
|
|
"""Create a mock model response for call path analysis"""
|
|
|
|
def _create_response(content=None):
|
|
if content is None:
|
|
content = """## Call Path Summary
|
|
|
|
1. 🟢 `BookingManager::finalizeInvoice()` at booking.py:45 → calls `PaymentProcessor.process()`
|
|
2. 🟢 `PaymentProcessor::process()` at payment.py:123 → calls `validation.validate_payment()`
|
|
3. 🟡 `validation.validate_payment()` at validation.py:67 → conditionally calls `Logger.log()`
|
|
|
|
## Value-Driven Flow Analysis
|
|
|
|
**Scenario 1**: `invoice_id=123, payment_method="credit_card"`
|
|
- Path: BookingManager → PaymentProcessor → CreditCardValidator → StripeGateway
|
|
- Key decision at payment.py:156: routes to Stripe integration
|
|
|
|
## Side Effects & External Dependencies
|
|
|
|
### Database Interactions
|
|
- **Transaction.save()** at models.py:234 → inserts payment record
|
|
|
|
### Network Calls
|
|
- **StripeGateway.charge()** → HTTPS POST to Stripe API
|
|
|
|
## Code Anchors
|
|
|
|
- Entry point: `BookingManager::finalizeInvoice` at booking.py:45
|
|
- Critical branch: Payment method selection at payment.py:156
|
|
"""
|
|
|
|
return Mock(
|
|
content=content,
|
|
usage={"input_tokens": 150, "output_tokens": 300, "total_tokens": 450},
|
|
model_name="test-model",
|
|
metadata={"finish_reason": "STOP"},
|
|
)
|
|
|
|
return _create_response
|
|
|
|
def test_get_name(self, tracer_tool):
|
|
"""Test that the tool returns the correct name"""
|
|
assert tracer_tool.get_name() == "tracer"
|
|
|
|
def test_get_description(self, tracer_tool):
|
|
"""Test that the tool returns a comprehensive description"""
|
|
description = tracer_tool.get_description()
|
|
assert "STATIC CODE ANALYSIS" in description
|
|
assert "execution flow" in description
|
|
assert "dependency mappings" in description
|
|
assert "precision" in description
|
|
assert "dependencies" in description
|
|
|
|
def test_get_input_schema(self, tracer_tool):
|
|
"""Test that the input schema includes all required fields"""
|
|
schema = tracer_tool.get_input_schema()
|
|
|
|
assert schema["type"] == "object"
|
|
assert "prompt" in schema["properties"]
|
|
assert "files" in schema["properties"]
|
|
assert "trace_mode" in schema["properties"]
|
|
|
|
# Check required fields
|
|
required_fields = schema["required"]
|
|
assert "prompt" in required_fields
|
|
assert "files" in required_fields
|
|
assert "trace_mode" in required_fields
|
|
|
|
# Check enum values for trace_mode
|
|
trace_mode_enum = schema["properties"]["trace_mode"]["enum"]
|
|
assert "precision" in trace_mode_enum
|
|
assert "dependencies" in trace_mode_enum
|
|
|
|
def test_get_model_category(self, tracer_tool):
|
|
"""Test that the tool uses extended reasoning category"""
|
|
category = tracer_tool.get_model_category()
|
|
assert category == ToolModelCategory.EXTENDED_REASONING
|
|
|
|
def test_request_model_validation(self):
|
|
"""Test request model validation"""
|
|
# Valid request
|
|
request = TracerRequest(
|
|
prompt="Trace BookingManager::finalizeInvoice method with invoice_id=123",
|
|
files=["/test/booking.py", "/test/payment.py"],
|
|
trace_mode="precision",
|
|
)
|
|
assert request.prompt == "Trace BookingManager::finalizeInvoice method with invoice_id=123"
|
|
assert len(request.files) == 2
|
|
assert request.trace_mode == "precision"
|
|
|
|
# Invalid request (missing required fields)
|
|
with pytest.raises(ValueError):
|
|
TracerRequest(files=["/test/file.py"]) # Missing prompt and trace_mode
|
|
|
|
# Invalid trace_mode value
|
|
with pytest.raises(ValueError):
|
|
TracerRequest(prompt="Test", files=["/test/file.py"], trace_mode="invalid_type")
|
|
|
|
def test_language_detection_python(self, tracer_tool):
|
|
"""Test language detection for Python files"""
|
|
files = ["/test/booking.py", "/test/payment.py", "/test/utils.py"]
|
|
language = tracer_tool.detect_primary_language(files)
|
|
assert language == "python"
|
|
|
|
def test_language_detection_javascript(self, tracer_tool):
|
|
"""Test language detection for JavaScript files"""
|
|
files = ["/test/app.js", "/test/component.jsx", "/test/utils.js"]
|
|
language = tracer_tool.detect_primary_language(files)
|
|
assert language == "javascript"
|
|
|
|
def test_language_detection_typescript(self, tracer_tool):
|
|
"""Test language detection for TypeScript files"""
|
|
files = ["/test/app.ts", "/test/component.tsx", "/test/utils.ts"]
|
|
language = tracer_tool.detect_primary_language(files)
|
|
assert language == "typescript"
|
|
|
|
def test_language_detection_csharp(self, tracer_tool):
|
|
"""Test language detection for C# files"""
|
|
files = ["/test/BookingService.cs", "/test/PaymentProcessor.cs"]
|
|
language = tracer_tool.detect_primary_language(files)
|
|
assert language == "csharp"
|
|
|
|
def test_language_detection_java(self, tracer_tool):
|
|
"""Test language detection for Java files"""
|
|
files = ["/test/BookingManager.java", "/test/PaymentService.java"]
|
|
language = tracer_tool.detect_primary_language(files)
|
|
assert language == "java"
|
|
|
|
def test_language_detection_mixed(self, tracer_tool):
|
|
"""Test language detection for mixed language files"""
|
|
files = ["/test/app.py", "/test/service.js", "/test/model.java"]
|
|
language = tracer_tool.detect_primary_language(files)
|
|
assert language == "mixed"
|
|
|
|
def test_language_detection_unknown(self, tracer_tool):
|
|
"""Test language detection for unknown extensions"""
|
|
files = ["/test/config.xml", "/test/readme.txt"]
|
|
language = tracer_tool.detect_primary_language(files)
|
|
assert language == "unknown"
|
|
|
|
# Removed parse_entry_point tests as method no longer exists in simplified interface
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prepare_prompt_basic(self, tracer_tool):
|
|
"""Test basic prompt preparation"""
|
|
request = TracerRequest(
|
|
prompt="Trace BookingManager::finalizeInvoice method with invoice_id=123",
|
|
files=["/test/booking.py"],
|
|
trace_mode="precision",
|
|
)
|
|
|
|
# Mock file content preparation
|
|
with patch.object(tracer_tool, "_prepare_file_content_for_prompt") as mock_prep:
|
|
mock_prep.return_value = "def finalizeInvoice(self, invoice_id):\n pass"
|
|
with patch.object(tracer_tool, "check_prompt_size") as mock_check:
|
|
mock_check.return_value = None
|
|
prompt = await tracer_tool.prepare_prompt(request)
|
|
|
|
assert "ANALYSIS REQUEST" in prompt
|
|
assert "Trace BookingManager::finalizeInvoice method" in prompt
|
|
assert "precision" in prompt
|
|
assert "CODE TO ANALYZE" in prompt
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prepare_prompt_with_dependencies(self, tracer_tool):
|
|
"""Test prompt preparation with dependencies type"""
|
|
request = TracerRequest(
|
|
prompt="Analyze dependencies for payment.process_payment function with amount=100.50",
|
|
files=["/test/payment.py"],
|
|
trace_mode="dependencies",
|
|
)
|
|
|
|
with patch.object(tracer_tool, "_prepare_file_content_for_prompt") as mock_prep:
|
|
mock_prep.return_value = "def process_payment(amount, method):\n pass"
|
|
with patch.object(tracer_tool, "check_prompt_size") as mock_check:
|
|
mock_check.return_value = None
|
|
prompt = await tracer_tool.prepare_prompt(request)
|
|
|
|
assert "Analyze dependencies for payment.process_payment" in prompt
|
|
assert "Trace Mode: dependencies" in prompt
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prepare_prompt_with_security_context(self, tracer_tool):
|
|
"""Test prompt preparation with security context"""
|
|
request = TracerRequest(
|
|
prompt="Trace UserService::authenticate method focusing on security implications and potential vulnerabilities",
|
|
files=["/test/auth.py"],
|
|
trace_mode="precision",
|
|
)
|
|
|
|
with patch.object(tracer_tool, "_prepare_file_content_for_prompt") as mock_prep:
|
|
mock_prep.return_value = "def authenticate(self, username, password):\n pass"
|
|
with patch.object(tracer_tool, "check_prompt_size") as mock_check:
|
|
mock_check.return_value = None
|
|
prompt = await tracer_tool.prepare_prompt(request)
|
|
|
|
assert "security implications and potential vulnerabilities" in prompt
|
|
assert "Trace Mode: precision" in prompt
|
|
|
|
def test_format_response_precision(self, tracer_tool):
|
|
"""Test response formatting for precision trace"""
|
|
request = TracerRequest(
|
|
prompt="Trace BookingManager::finalizeInvoice method", files=["/test/booking.py"], trace_mode="precision"
|
|
)
|
|
|
|
response = '{"status": "trace_complete", "trace_type": "precision"}'
|
|
model_info = {"model_response": Mock(friendly_name="Gemini Pro")}
|
|
|
|
formatted = tracer_tool.format_response(response, request, model_info)
|
|
|
|
assert response in formatted
|
|
assert "Analysis Complete" in formatted
|
|
assert "Gemini Pro" in formatted
|
|
assert "precision analysis" in formatted
|
|
assert "CALL FLOW DIAGRAM" in formatted
|
|
assert "BRANCHING & SIDE EFFECT TABLE" in formatted
|
|
|
|
def test_format_response_dependencies(self, tracer_tool):
|
|
"""Test response formatting for dependencies trace"""
|
|
request = TracerRequest(
|
|
prompt="Analyze dependencies for payment.process function",
|
|
files=["/test/payment.py"],
|
|
trace_mode="dependencies",
|
|
)
|
|
|
|
response = '{"status": "trace_complete", "trace_type": "dependencies"}'
|
|
|
|
formatted = tracer_tool.format_response(response, request)
|
|
|
|
assert response in formatted
|
|
assert "dependencies analysis" in formatted
|
|
assert "DEPENDENCY FLOW GRAPH" in formatted
|
|
assert "DEPENDENCY TABLE" in formatted
|
|
|
|
# Removed PlantUML test as export_format is no longer a parameter
|
|
|
|
def test_get_default_temperature(self, tracer_tool):
|
|
"""Test that the tool uses analytical temperature"""
|
|
from config import TEMPERATURE_ANALYTICAL
|
|
|
|
assert tracer_tool.get_default_temperature() == TEMPERATURE_ANALYTICAL
|
|
|
|
def test_wants_line_numbers_by_default(self, tracer_tool):
|
|
"""Test that line numbers are enabled by default"""
|
|
# The base class should enable line numbers by default for precise references
|
|
# We test that this isn't overridden to disable them
|
|
assert hasattr(tracer_tool, "wants_line_numbers_by_default")
|
|
|
|
def test_trace_mode_validation(self):
|
|
"""Test trace mode validation"""
|
|
# Valid trace modes
|
|
request1 = TracerRequest(prompt="Test precision", files=["/test/file.py"], trace_mode="precision")
|
|
assert request1.trace_mode == "precision"
|
|
|
|
request2 = TracerRequest(prompt="Test dependencies", files=["/test/file.py"], trace_mode="dependencies")
|
|
assert request2.trace_mode == "dependencies"
|
|
|
|
# Invalid trace mode should raise ValidationError
|
|
with pytest.raises(ValueError):
|
|
TracerRequest(prompt="Test", files=["/test/file.py"], trace_mode="invalid_type")
|
|
|
|
def test_get_rendering_instructions(self, tracer_tool):
|
|
"""Test the main rendering instructions dispatcher method"""
|
|
# Test precision mode
|
|
precision_instructions = tracer_tool._get_rendering_instructions("precision")
|
|
assert "MANDATORY RENDERING INSTRUCTIONS FOR PRECISION TRACE" in precision_instructions
|
|
assert "CALL FLOW DIAGRAM" in precision_instructions
|
|
assert "BRANCHING & SIDE EFFECT TABLE" in precision_instructions
|
|
|
|
# Test dependencies mode
|
|
dependencies_instructions = tracer_tool._get_rendering_instructions("dependencies")
|
|
assert "MANDATORY RENDERING INSTRUCTIONS FOR DEPENDENCIES TRACE" in dependencies_instructions
|
|
assert "DEPENDENCY FLOW GRAPH" in dependencies_instructions
|
|
assert "DEPENDENCY TABLE" in dependencies_instructions
|
|
|
|
def test_get_precision_rendering_instructions(self, tracer_tool):
|
|
"""Test precision mode rendering instructions"""
|
|
instructions = tracer_tool._get_precision_rendering_instructions()
|
|
|
|
# Check for required sections
|
|
assert "MANDATORY RENDERING INSTRUCTIONS FOR PRECISION TRACE" in instructions
|
|
assert "1. CALL FLOW DIAGRAM (TOP-DOWN)" in instructions
|
|
assert "2. BRANCHING & SIDE EFFECT TABLE" in instructions
|
|
|
|
# Check for specific formatting requirements
|
|
assert "[Class::Method] (file: /path, line: ##)" in instructions
|
|
assert "Chain each call using ↓ or → for readability" in instructions
|
|
assert "If ambiguous, mark with `⚠️ ambiguous branch`" in instructions
|
|
assert "Side Effects:" in instructions
|
|
assert "[database] description (File.ext:##)" in instructions
|
|
|
|
# Check for critical rules
|
|
assert "CRITICAL RULES:" in instructions
|
|
assert "Use exact filenames, class names, and line numbers from JSON" in instructions
|
|
assert "DO NOT invent function names or examples" in instructions
|
|
|
|
def test_get_dependencies_rendering_instructions(self, tracer_tool):
|
|
"""Test dependencies mode rendering instructions"""
|
|
instructions = tracer_tool._get_dependencies_rendering_instructions()
|
|
|
|
# Check for required sections
|
|
assert "MANDATORY RENDERING INSTRUCTIONS FOR DEPENDENCIES TRACE" in instructions
|
|
assert "1. DEPENDENCY FLOW GRAPH" in instructions
|
|
assert "2. DEPENDENCY TABLE" in instructions
|
|
|
|
# Check for specific formatting requirements
|
|
assert "Called by:" in instructions
|
|
assert "[CallerClass::callerMethod] ← /path/file.ext:##" in instructions
|
|
assert "Calls:" in instructions
|
|
assert "[Logger::logAction] → /utils/log.ext:##" in instructions
|
|
assert "Type Dependencies:" in instructions
|
|
assert "State Access:" in instructions
|
|
|
|
# Check for arrow rules
|
|
assert "`←` for incoming (who calls this)" in instructions
|
|
assert "`→` for outgoing (what this calls)" in instructions
|
|
|
|
# Check for dependency table format
|
|
assert "| Type | From/To | Method | File | Line |" in instructions
|
|
assert "| direct_call | From: CallerClass | callerMethod |" in instructions
|
|
|
|
# Check for critical rules
|
|
assert "CRITICAL RULES:" in instructions
|
|
assert "Use exact filenames, class names, and line numbers from JSON" in instructions
|
|
assert "Show directional dependencies with proper arrows" in instructions
|
|
|
|
def test_format_response_uses_private_methods(self, tracer_tool):
|
|
"""Test that format_response correctly uses the refactored private methods"""
|
|
# Test precision mode
|
|
precision_request = TracerRequest(prompt="Test precision", files=["/test/file.py"], trace_mode="precision")
|
|
precision_response = tracer_tool.format_response('{"test": "response"}', precision_request)
|
|
|
|
# Should contain precision-specific instructions
|
|
assert "CALL FLOW DIAGRAM" in precision_response
|
|
assert "BRANCHING & SIDE EFFECT TABLE" in precision_response
|
|
assert "precision analysis" in precision_response
|
|
|
|
# Test dependencies mode
|
|
dependencies_request = TracerRequest(
|
|
prompt="Test dependencies", files=["/test/file.py"], trace_mode="dependencies"
|
|
)
|
|
dependencies_response = tracer_tool.format_response('{"test": "response"}', dependencies_request)
|
|
|
|
# Should contain dependencies-specific instructions
|
|
assert "DEPENDENCY FLOW GRAPH" in dependencies_response
|
|
assert "DEPENDENCY TABLE" in dependencies_response
|
|
assert "dependencies analysis" in dependencies_response
|
|
|
|
def test_rendering_instructions_consistency(self, tracer_tool):
|
|
"""Test that private methods return consistent instructions"""
|
|
# Get instructions through both paths
|
|
precision_direct = tracer_tool._get_precision_rendering_instructions()
|
|
precision_via_dispatcher = tracer_tool._get_rendering_instructions("precision")
|
|
|
|
dependencies_direct = tracer_tool._get_dependencies_rendering_instructions()
|
|
dependencies_via_dispatcher = tracer_tool._get_rendering_instructions("dependencies")
|
|
|
|
# Should be identical
|
|
assert precision_direct == precision_via_dispatcher
|
|
assert dependencies_direct == dependencies_via_dispatcher
|
|
|
|
def test_rendering_instructions_completeness(self, tracer_tool):
|
|
"""Test that rendering instructions contain all required elements"""
|
|
precision_instructions = tracer_tool._get_precision_rendering_instructions()
|
|
dependencies_instructions = tracer_tool._get_dependencies_rendering_instructions()
|
|
|
|
# Both should have mandatory sections
|
|
for instructions in [precision_instructions, dependencies_instructions]:
|
|
assert "MANDATORY RENDERING INSTRUCTIONS" in instructions
|
|
assert "You MUST render" in instructions
|
|
assert "exactly two views" in instructions
|
|
assert "CRITICAL RULES:" in instructions
|
|
assert "ALWAYS render both views unless data is missing" in instructions
|
|
assert "Use exact filenames, class names, and line numbers from JSON" in instructions
|
|
assert "DO NOT invent function names or examples" in instructions
|
|
|
|
def test_rendering_instructions_mode_specific_content(self, tracer_tool):
|
|
"""Test that each mode has unique content"""
|
|
precision_instructions = tracer_tool._get_precision_rendering_instructions()
|
|
dependencies_instructions = tracer_tool._get_dependencies_rendering_instructions()
|
|
|
|
# Precision-specific content should not be in dependencies
|
|
assert "CALL FLOW DIAGRAM" in precision_instructions
|
|
assert "CALL FLOW DIAGRAM" not in dependencies_instructions
|
|
assert "BRANCHING & SIDE EFFECT TABLE" in precision_instructions
|
|
assert "BRANCHING & SIDE EFFECT TABLE" not in dependencies_instructions
|
|
|
|
# Dependencies-specific content should not be in precision
|
|
assert "DEPENDENCY FLOW GRAPH" in dependencies_instructions
|
|
assert "DEPENDENCY FLOW GRAPH" not in precision_instructions
|
|
assert "DEPENDENCY TABLE" in dependencies_instructions
|
|
assert "DEPENDENCY TABLE" not in precision_instructions
|
|
|
|
# Mode-specific symbols and patterns
|
|
assert "↓" in precision_instructions # Flow arrows
|
|
assert "←" in dependencies_instructions # Incoming arrow
|
|
assert "→" in dependencies_instructions # Outgoing arrow
|
|
assert "Side Effects:" in precision_instructions
|
|
assert "Called by:" in dependencies_instructions
|