WIP
This commit is contained in:
@@ -28,10 +28,11 @@ class TestServerTools:
|
||||
assert "precommit" in tool_names
|
||||
assert "testgen" in tool_names
|
||||
assert "refactor" in tool_names
|
||||
assert "tracer" in tool_names
|
||||
assert "version" in tool_names
|
||||
|
||||
# Should have exactly 9 tools (including refactor)
|
||||
assert len(tools) == 9
|
||||
# Should have exactly 10 tools (including refactor and tracer)
|
||||
assert len(tools) == 10
|
||||
|
||||
# Check descriptions are verbose
|
||||
for tool in tools:
|
||||
|
||||
@@ -321,3 +321,83 @@ class TestAbsolutePathValidation:
|
||||
response = json.loads(result[0].text)
|
||||
assert response["status"] == "success"
|
||||
assert "Analysis complete" in response["content"]
|
||||
|
||||
|
||||
class TestSpecialStatusModels:
|
||||
"""Test SPECIAL_STATUS_MODELS registry and structured response handling"""
|
||||
|
||||
def test_trace_complete_status_in_registry(self):
|
||||
"""Test that trace_complete status is properly registered"""
|
||||
from tools.models import SPECIAL_STATUS_MODELS, TraceComplete
|
||||
|
||||
assert "trace_complete" in SPECIAL_STATUS_MODELS
|
||||
assert SPECIAL_STATUS_MODELS["trace_complete"] == TraceComplete
|
||||
|
||||
def test_trace_complete_model_validation(self):
|
||||
"""Test TraceComplete model validation"""
|
||||
from tools.models import TraceComplete
|
||||
|
||||
# Test precision mode
|
||||
precision_data = {
|
||||
"status": "trace_complete",
|
||||
"trace_type": "precision",
|
||||
"entry_point": {
|
||||
"file": "/path/to/file.py",
|
||||
"class_or_struct": "MyClass",
|
||||
"method": "myMethod",
|
||||
"signature": "def myMethod(self, param1: str) -> bool",
|
||||
"parameters": {"param1": "test"},
|
||||
},
|
||||
"call_path": [
|
||||
{
|
||||
"from": {"file": "/path/to/file.py", "class": "MyClass", "method": "myMethod", "line": 10},
|
||||
"to": {"file": "/path/to/other.py", "class": "OtherClass", "method": "otherMethod", "line": 20},
|
||||
"reason": "direct call",
|
||||
"condition": None,
|
||||
"ambiguous": False,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
model = TraceComplete(**precision_data)
|
||||
assert model.status == "trace_complete"
|
||||
assert model.trace_type == "precision"
|
||||
assert model.entry_point.file == "/path/to/file.py"
|
||||
assert len(model.call_path) == 1
|
||||
|
||||
# Test dependencies mode
|
||||
dependencies_data = {
|
||||
"status": "trace_complete",
|
||||
"trace_type": "dependencies",
|
||||
"target": {
|
||||
"file": "/path/to/file.py",
|
||||
"class_or_struct": "MyClass",
|
||||
"method": "myMethod",
|
||||
"signature": "def myMethod(self, param1: str) -> bool",
|
||||
},
|
||||
"incoming_dependencies": [
|
||||
{
|
||||
"from_file": "/path/to/caller.py",
|
||||
"from_class": "CallerClass",
|
||||
"from_method": "callerMethod",
|
||||
"line": 15,
|
||||
"type": "direct_call",
|
||||
}
|
||||
],
|
||||
"outgoing_dependencies": [
|
||||
{
|
||||
"to_file": "/path/to/dependency.py",
|
||||
"to_class": "DepClass",
|
||||
"to_method": "depMethod",
|
||||
"line": 25,
|
||||
"type": "method_call",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
model = TraceComplete(**dependencies_data)
|
||||
assert model.status == "trace_complete"
|
||||
assert model.trace_type == "dependencies"
|
||||
assert model.target.file == "/path/to/file.py"
|
||||
assert len(model.incoming_dependencies) == 1
|
||||
assert len(model.outgoing_dependencies) == 1
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
"""
|
||||
Tests for the tracepath tool functionality
|
||||
"""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from tools.models import ToolModelCategory
|
||||
from tools.tracepath import TracePathRequest, TracePathTool
|
||||
|
||||
|
||||
class TestTracePathTool:
|
||||
"""Test suite for the TracePath tool"""
|
||||
|
||||
@pytest.fixture
|
||||
def tracepath_tool(self):
|
||||
"""Create a tracepath tool instance for testing"""
|
||||
return TracePathTool()
|
||||
|
||||
@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, tracepath_tool):
|
||||
"""Test that the tool returns the correct name"""
|
||||
assert tracepath_tool.get_name() == "tracepath"
|
||||
|
||||
def test_get_description(self, tracepath_tool):
|
||||
"""Test that the tool returns a comprehensive description"""
|
||||
description = tracepath_tool.get_description()
|
||||
assert "STATIC CALL PATH ANALYSIS" in description
|
||||
assert "control flow" in description
|
||||
assert "confidence levels" in description
|
||||
assert "polymorphism" in description
|
||||
assert "side effects" in description
|
||||
|
||||
def test_get_input_schema(self, tracepath_tool):
|
||||
"""Test that the input schema includes all required fields"""
|
||||
schema = tracepath_tool.get_input_schema()
|
||||
|
||||
assert schema["type"] == "object"
|
||||
assert "entry_point" in schema["properties"]
|
||||
assert "files" in schema["properties"]
|
||||
|
||||
# Check required fields
|
||||
required_fields = schema["required"]
|
||||
assert "entry_point" in required_fields
|
||||
assert "files" in required_fields
|
||||
|
||||
# Check optional parameters
|
||||
assert "parameters" in schema["properties"]
|
||||
assert "analysis_depth" in schema["properties"]
|
||||
assert "language" in schema["properties"]
|
||||
assert "confidence_threshold" in schema["properties"]
|
||||
|
||||
# Check enum values for analysis_depth
|
||||
depth_enum = schema["properties"]["analysis_depth"]["enum"]
|
||||
expected_depths = ["shallow", "medium", "deep"]
|
||||
assert all(depth in depth_enum for depth in expected_depths)
|
||||
|
||||
# Check enum values for language
|
||||
language_enum = schema["properties"]["language"]["enum"]
|
||||
expected_languages = ["python", "javascript", "typescript", "csharp", "java"]
|
||||
assert all(lang in language_enum for lang in expected_languages)
|
||||
|
||||
def test_get_model_category(self, tracepath_tool):
|
||||
"""Test that the tool uses extended reasoning category"""
|
||||
category = tracepath_tool.get_model_category()
|
||||
assert category == ToolModelCategory.EXTENDED_REASONING
|
||||
|
||||
def test_request_model_validation(self):
|
||||
"""Test request model validation"""
|
||||
# Valid request
|
||||
request = TracePathRequest(
|
||||
entry_point="BookingManager::finalizeInvoice",
|
||||
files=["/test/booking.py", "/test/payment.py"],
|
||||
parameters={"invoice_id": 123, "payment_method": "credit_card"},
|
||||
analysis_depth="medium",
|
||||
)
|
||||
assert request.entry_point == "BookingManager::finalizeInvoice"
|
||||
assert len(request.files) == 2
|
||||
assert request.analysis_depth == "medium"
|
||||
assert request.confidence_threshold == 0.7 # default value
|
||||
|
||||
# Test validation with invalid confidence threshold
|
||||
with pytest.raises(ValueError):
|
||||
TracePathRequest(
|
||||
entry_point="test::method", files=["/test/file.py"], confidence_threshold=1.5 # Invalid: > 1.0
|
||||
)
|
||||
|
||||
# Invalid request (missing required fields)
|
||||
with pytest.raises(ValueError):
|
||||
TracePathRequest(files=["/test/file.py"]) # Missing entry_point
|
||||
|
||||
def test_language_detection_python(self, tracepath_tool):
|
||||
"""Test language detection for Python files"""
|
||||
files = ["/test/booking.py", "/test/payment.py", "/test/utils.py"]
|
||||
language = tracepath_tool.detect_primary_language(files)
|
||||
assert language == "python"
|
||||
|
||||
def test_language_detection_javascript(self, tracepath_tool):
|
||||
"""Test language detection for JavaScript files"""
|
||||
files = ["/test/app.js", "/test/component.jsx", "/test/utils.js"]
|
||||
language = tracepath_tool.detect_primary_language(files)
|
||||
assert language == "javascript"
|
||||
|
||||
def test_language_detection_typescript(self, tracepath_tool):
|
||||
"""Test language detection for TypeScript files"""
|
||||
files = ["/test/app.ts", "/test/component.tsx", "/test/utils.ts"]
|
||||
language = tracepath_tool.detect_primary_language(files)
|
||||
assert language == "typescript"
|
||||
|
||||
def test_language_detection_csharp(self, tracepath_tool):
|
||||
"""Test language detection for C# files"""
|
||||
files = ["/test/BookingService.cs", "/test/PaymentProcessor.cs"]
|
||||
language = tracepath_tool.detect_primary_language(files)
|
||||
assert language == "csharp"
|
||||
|
||||
def test_language_detection_java(self, tracepath_tool):
|
||||
"""Test language detection for Java files"""
|
||||
files = ["/test/BookingManager.java", "/test/PaymentService.java"]
|
||||
language = tracepath_tool.detect_primary_language(files)
|
||||
assert language == "java"
|
||||
|
||||
def test_language_detection_mixed(self, tracepath_tool):
|
||||
"""Test language detection for mixed language files"""
|
||||
files = ["/test/app.py", "/test/service.js", "/test/model.java"]
|
||||
language = tracepath_tool.detect_primary_language(files)
|
||||
assert language == "mixed"
|
||||
|
||||
def test_language_detection_unknown(self, tracepath_tool):
|
||||
"""Test language detection for unknown extensions"""
|
||||
files = ["/test/config.xml", "/test/readme.txt"]
|
||||
language = tracepath_tool.detect_primary_language(files)
|
||||
assert language == "unknown"
|
||||
|
||||
def test_parse_entry_point_class_method_double_colon(self, tracepath_tool):
|
||||
"""Test parsing entry point with double colon syntax"""
|
||||
result = tracepath_tool.parse_entry_point("BookingManager::finalizeInvoice", "python")
|
||||
|
||||
assert result["raw"] == "BookingManager::finalizeInvoice"
|
||||
assert result["class_or_module"] == "BookingManager"
|
||||
assert result["method_or_function"] == "finalizeInvoice"
|
||||
assert result["type"] == "method"
|
||||
|
||||
def test_parse_entry_point_module_function_dot(self, tracepath_tool):
|
||||
"""Test parsing entry point with dot syntax"""
|
||||
result = tracepath_tool.parse_entry_point("utils.validate_input", "python")
|
||||
|
||||
assert result["raw"] == "utils.validate_input"
|
||||
assert result["class_or_module"] == "utils"
|
||||
assert result["method_or_function"] == "validate_input"
|
||||
assert result["type"] == "function"
|
||||
|
||||
def test_parse_entry_point_nested_module(self, tracepath_tool):
|
||||
"""Test parsing entry point with nested module syntax"""
|
||||
result = tracepath_tool.parse_entry_point("payment.services.process_payment", "python")
|
||||
|
||||
assert result["raw"] == "payment.services.process_payment"
|
||||
assert result["class_or_module"] == "payment.services"
|
||||
assert result["method_or_function"] == "process_payment"
|
||||
assert result["type"] == "function"
|
||||
|
||||
def test_parse_entry_point_function_only(self, tracepath_tool):
|
||||
"""Test parsing entry point with function name only"""
|
||||
result = tracepath_tool.parse_entry_point("validate_payment", "python")
|
||||
|
||||
assert result["raw"] == "validate_payment"
|
||||
assert result["class_or_module"] == ""
|
||||
assert result["method_or_function"] == "validate_payment"
|
||||
assert result["type"] == "function"
|
||||
|
||||
def test_parse_entry_point_camelcase_class(self, tracepath_tool):
|
||||
"""Test parsing entry point with CamelCase class (method detection)"""
|
||||
result = tracepath_tool.parse_entry_point("PaymentProcessor.process", "java")
|
||||
|
||||
assert result["raw"] == "PaymentProcessor.process"
|
||||
assert result["class_or_module"] == "PaymentProcessor"
|
||||
assert result["method_or_function"] == "process"
|
||||
assert result["type"] == "method" # CamelCase suggests class method
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_structural_summary_phase1(self, tracepath_tool):
|
||||
"""Test structural summary generation (Phase 1 returns empty)"""
|
||||
files = ["/test/booking.py", "/test/payment.py"]
|
||||
summary = await tracepath_tool._generate_structural_summary(files, "python")
|
||||
|
||||
# Phase 1 implementation should return empty string
|
||||
assert summary == ""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_prompt_basic(self, tracepath_tool):
|
||||
"""Test basic prompt preparation"""
|
||||
request = TracePathRequest(
|
||||
entry_point="BookingManager::finalizeInvoice",
|
||||
files=["/test/booking.py"],
|
||||
parameters={"invoice_id": 123},
|
||||
analysis_depth="medium",
|
||||
)
|
||||
|
||||
# Mock file content preparation
|
||||
with patch.object(tracepath_tool, "_prepare_file_content_for_prompt") as mock_prep:
|
||||
mock_prep.return_value = "def finalizeInvoice(self, invoice_id):\n pass"
|
||||
with patch.object(tracepath_tool, "_validate_token_limit"):
|
||||
prompt = await tracepath_tool.prepare_prompt(request)
|
||||
|
||||
assert "ANALYSIS REQUEST" in prompt
|
||||
assert "BookingManager::finalizeInvoice" in prompt
|
||||
assert "medium" in prompt
|
||||
assert "CODE TO ANALYZE" in prompt
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_prompt_with_parameters(self, tracepath_tool):
|
||||
"""Test prompt preparation with parameter values"""
|
||||
request = TracePathRequest(
|
||||
entry_point="payment.process_payment",
|
||||
files=["/test/payment.py"],
|
||||
parameters={"amount": 100.50, "method": "credit_card"},
|
||||
analysis_depth="deep",
|
||||
include_db=True,
|
||||
include_network=True,
|
||||
include_fs=False,
|
||||
)
|
||||
|
||||
with patch.object(tracepath_tool, "_prepare_file_content_for_prompt") as mock_prep:
|
||||
mock_prep.return_value = "def process_payment(amount, method):\n pass"
|
||||
with patch.object(tracepath_tool, "_validate_token_limit"):
|
||||
prompt = await tracepath_tool.prepare_prompt(request)
|
||||
|
||||
assert "Parameter Values: {'amount': 100.5, 'method': 'credit_card'}" in prompt
|
||||
assert "Analysis Depth: deep" in prompt
|
||||
assert "Include Side Effects: database, network" in prompt
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_prompt_with_context(self, tracepath_tool):
|
||||
"""Test prompt preparation with additional context"""
|
||||
request = TracePathRequest(
|
||||
entry_point="UserService::authenticate",
|
||||
files=["/test/auth.py"],
|
||||
context="Focus on security implications and potential vulnerabilities",
|
||||
focus_areas=["security", "error_handling"],
|
||||
)
|
||||
|
||||
with patch.object(tracepath_tool, "_prepare_file_content_for_prompt") as mock_prep:
|
||||
mock_prep.return_value = "def authenticate(self, username, password):\n pass"
|
||||
with patch.object(tracepath_tool, "_validate_token_limit"):
|
||||
prompt = await tracepath_tool.prepare_prompt(request)
|
||||
|
||||
assert "Additional Context: Focus on security implications" in prompt
|
||||
assert "Focus Areas: security, error_handling" in prompt
|
||||
|
||||
def test_format_response_markdown(self, tracepath_tool):
|
||||
"""Test response formatting for markdown output"""
|
||||
request = TracePathRequest(
|
||||
entry_point="BookingManager::finalizeInvoice", files=["/test/booking.py"], export_format="markdown"
|
||||
)
|
||||
|
||||
response = "## Call Path Summary\n1. BookingManager::finalizeInvoice..."
|
||||
model_info = {"model_response": Mock(friendly_name="Gemini Pro")}
|
||||
|
||||
formatted = tracepath_tool.format_response(response, request, model_info)
|
||||
|
||||
assert response in formatted
|
||||
assert "Analysis Complete" in formatted
|
||||
assert "Gemini Pro" in formatted
|
||||
assert "confidence assessments" in formatted
|
||||
|
||||
def test_format_response_json(self, tracepath_tool):
|
||||
"""Test response formatting for JSON output"""
|
||||
request = TracePathRequest(entry_point="payment.process", files=["/test/payment.py"], export_format="json")
|
||||
|
||||
response = '{"call_path": [...], "confidence": "high"}'
|
||||
|
||||
formatted = tracepath_tool.format_response(response, request)
|
||||
|
||||
assert response in formatted
|
||||
assert "structured JSON analysis" in formatted
|
||||
assert "confidence levels" in formatted
|
||||
|
||||
def test_format_response_plantuml(self, tracepath_tool):
|
||||
"""Test response formatting for PlantUML output"""
|
||||
request = TracePathRequest(entry_point="service.execute", files=["/test/service.py"], export_format="plantuml")
|
||||
|
||||
response = "@startuml\nBooking -> Payment\n@enduml"
|
||||
|
||||
formatted = tracepath_tool.format_response(response, request)
|
||||
|
||||
assert response in formatted
|
||||
assert "PlantUML diagram" in formatted
|
||||
assert "Render the PlantUML" in formatted
|
||||
|
||||
def test_get_default_temperature(self, tracepath_tool):
|
||||
"""Test that the tool uses analytical temperature"""
|
||||
from config import TEMPERATURE_ANALYTICAL
|
||||
|
||||
assert tracepath_tool.get_default_temperature() == TEMPERATURE_ANALYTICAL
|
||||
|
||||
def test_wants_line_numbers_by_default(self, tracepath_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(tracepath_tool, "wants_line_numbers_by_default")
|
||||
|
||||
def test_side_effects_configuration(self):
|
||||
"""Test side effects boolean configuration"""
|
||||
request = TracePathRequest(
|
||||
entry_point="test.function",
|
||||
files=["/test/file.py"],
|
||||
include_db=True,
|
||||
include_network=False,
|
||||
include_fs=True,
|
||||
)
|
||||
|
||||
assert request.include_db is True
|
||||
assert request.include_network is False
|
||||
assert request.include_fs is True
|
||||
|
||||
def test_confidence_threshold_bounds(self):
|
||||
"""Test confidence threshold validation bounds"""
|
||||
# Valid thresholds
|
||||
request1 = TracePathRequest(entry_point="test.function", files=["/test/file.py"], confidence_threshold=0.0)
|
||||
assert request1.confidence_threshold == 0.0
|
||||
|
||||
request2 = TracePathRequest(entry_point="test.function", files=["/test/file.py"], confidence_threshold=1.0)
|
||||
assert request2.confidence_threshold == 1.0
|
||||
|
||||
# Invalid thresholds should raise ValidationError
|
||||
with pytest.raises(ValueError):
|
||||
TracePathRequest(entry_point="test.function", files=["/test/file.py"], confidence_threshold=-0.1)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
TracePathRequest(entry_point="test.function", files=["/test/file.py"], confidence_threshold=1.1)
|
||||
|
||||
def test_signature_parameter(self):
|
||||
"""Test signature parameter for overload resolution"""
|
||||
request = TracePathRequest(
|
||||
entry_point="Calculator.add",
|
||||
files=["/test/calc.cs"],
|
||||
signature="public int Add(int a, int b)",
|
||||
language="csharp",
|
||||
)
|
||||
|
||||
assert request.signature == "public int Add(int a, int b)"
|
||||
assert request.language == "csharp"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_prompt_with_language_override(self, tracepath_tool):
|
||||
"""Test prompt preparation with language override"""
|
||||
request = TracePathRequest(
|
||||
entry_point="Calculator::Add",
|
||||
files=["/test/calc.py"], # Python extension
|
||||
language="csharp", # Override to C#
|
||||
)
|
||||
|
||||
with patch.object(tracepath_tool, "_prepare_file_content_for_prompt") as mock_prep:
|
||||
mock_prep.return_value = "public class Calculator { }"
|
||||
with patch.object(tracepath_tool, "_validate_token_limit"):
|
||||
prompt = await tracepath_tool.prepare_prompt(request)
|
||||
|
||||
assert "Language: csharp" in prompt # Should use override, not detected
|
||||
|
||||
def test_export_format_options(self):
|
||||
"""Test all export format options"""
|
||||
formats = ["markdown", "json", "plantuml"]
|
||||
|
||||
for fmt in formats:
|
||||
request = TracePathRequest(entry_point="test.function", files=["/test/file.py"], export_format=fmt)
|
||||
assert request.export_format == fmt
|
||||
|
||||
# Invalid format should raise ValidationError
|
||||
with pytest.raises(ValueError):
|
||||
TracePathRequest(entry_point="test.function", files=["/test/file.py"], export_format="invalid_format")
|
||||
420
tests/test_tracer.py
Normal file
420
tests/test_tracer.py
Normal file
@@ -0,0 +1,420 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user