fix: improve UTF-8 integration tests and response handling by adding details and fixes in mocks
This commit is contained in:
@@ -1,308 +0,0 @@
|
|||||||
"""
|
|
||||||
Unit tests to validate UTF-8 encoding in workflow tools
|
|
||||||
and the generation of properly encoded JSON responses.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import unittest
|
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
|
||||||
|
|
||||||
from tools.analyze import AnalyzeTool
|
|
||||||
from tools.codereview import CodeReviewTool
|
|
||||||
from tools.debug import DebugIssueTool
|
|
||||||
|
|
||||||
|
|
||||||
class TestWorkflowToolsUTF8(unittest.IsolatedAsyncioTestCase):
|
|
||||||
"""Tests for UTF-8 encoding in workflow tools."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Test setup."""
|
|
||||||
self.original_locale = os.getenv("LOCALE")
|
|
||||||
# Default to French for tests
|
|
||||||
os.environ["LOCALE"] = "fr-FR"
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Cleanup after tests."""
|
|
||||||
if self.original_locale is not None:
|
|
||||||
os.environ["LOCALE"] = self.original_locale
|
|
||||||
else:
|
|
||||||
os.environ.pop("LOCALE", None)
|
|
||||||
|
|
||||||
def test_workflow_json_response_structure(self):
|
|
||||||
"""Test the structure of JSON responses from workflow tools."""
|
|
||||||
# Mock response with UTF-8 characters
|
|
||||||
test_response = {
|
|
||||||
"status": "pause_for_analysis",
|
|
||||||
"step_number": 1,
|
|
||||||
"total_steps": 3,
|
|
||||||
"next_step_required": True,
|
|
||||||
"findings": "Code analysis reveals performance issues 🔍",
|
|
||||||
"files_checked": ["/src/main.py"],
|
|
||||||
"relevant_files": ["/src/main.py"],
|
|
||||||
"issues_found": [
|
|
||||||
{
|
|
||||||
"severity": "high",
|
|
||||||
"description": "Function too complex - refactoring needed"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"investigation_required": True,
|
|
||||||
"required_actions": [
|
|
||||||
"Review code dependencies",
|
|
||||||
"Analyze architectural patterns"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test JSON serialization with ensure_ascii=False
|
|
||||||
json_str = json.dumps(test_response, indent=2, ensure_ascii=False)
|
|
||||||
|
|
||||||
# Check UTF-8 characters are preserved
|
|
||||||
self.assertIn("🔍", json_str)
|
|
||||||
|
|
||||||
# No escaped characters
|
|
||||||
self.assertNotIn("\\u", json_str)
|
|
||||||
|
|
||||||
# Test parsing
|
|
||||||
parsed = json.loads(json_str)
|
|
||||||
self.assertEqual(parsed["findings"], test_response["findings"])
|
|
||||||
self.assertEqual(len(parsed["issues_found"]), 1)
|
|
||||||
|
|
||||||
@patch("tools.shared.base_tool.BaseTool.get_model_provider")
|
|
||||||
async def test_analyze_tool_utf8_response(self, mock_get_provider):
|
|
||||||
"""Test that the analyze tool returns correct UTF-8 responses."""
|
|
||||||
# Mock provider with more complete setup
|
|
||||||
mock_provider = Mock()
|
|
||||||
mock_provider.get_provider_type.return_value = Mock(value="test")
|
|
||||||
mock_provider.supports_thinking_mode.return_value = False
|
|
||||||
mock_provider.generate_content = AsyncMock(
|
|
||||||
return_value=Mock(
|
|
||||||
content=json.dumps({
|
|
||||||
"status": "analysis_complete",
|
|
||||||
"step_number": 1,
|
|
||||||
"total_steps": 2,
|
|
||||||
"next_step_required": True,
|
|
||||||
"findings": "Architectural analysis completed successfully",
|
|
||||||
"relevant_files": ["/test/main.py"],
|
|
||||||
"issues_found": [],
|
|
||||||
"confidence": "high"
|
|
||||||
}, ensure_ascii=False),
|
|
||||||
usage={},
|
|
||||||
model_name="test-model",
|
|
||||||
metadata={},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
mock_get_provider.return_value = mock_provider
|
|
||||||
|
|
||||||
# Test the tool
|
|
||||||
analyze_tool = AnalyzeTool()
|
|
||||||
result = await analyze_tool.execute(
|
|
||||||
{
|
|
||||||
"step": "Analyze system architecture to identify issues",
|
|
||||||
"step_number": 1,
|
|
||||||
"total_steps": 2,
|
|
||||||
"next_step_required": True,
|
|
||||||
"findings": "Starting architectural analysis of Python code",
|
|
||||||
"relevant_files": ["/test/main.py"],
|
|
||||||
"model": "test-model",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Checks
|
|
||||||
self.assertIsNotNone(result)
|
|
||||||
self.assertEqual(len(result), 1)
|
|
||||||
|
|
||||||
# Parse the response - must be valid UTF-8 JSON
|
|
||||||
response_text = result[0].text
|
|
||||||
response_data = json.loads(response_text)
|
|
||||||
|
|
||||||
# Structure checks
|
|
||||||
self.assertIn("status", response_data)
|
|
||||||
self.assertIn("step_number", response_data)
|
|
||||||
|
|
||||||
# Check that the French instruction was added
|
|
||||||
mock_provider.generate_content.assert_called()
|
|
||||||
call_args = mock_provider.generate_content.call_args
|
|
||||||
system_prompt = call_args.kwargs.get("system_prompt", "")
|
|
||||||
self.assertIn("fr-FR", system_prompt)
|
|
||||||
|
|
||||||
@patch("tools.shared.base_tool.BaseTool.get_model_provider")
|
|
||||||
async def test_codereview_tool_french_findings(self, mock_get_provider):
|
|
||||||
"""Test that the codereview tool produces findings in French."""
|
|
||||||
# Mock with analysis in French
|
|
||||||
mock_provider = Mock()
|
|
||||||
mock_provider.get_provider_type.return_value = Mock(value="test")
|
|
||||||
mock_provider.supports_thinking_mode.return_value = False
|
|
||||||
mock_provider.generate_content = AsyncMock(
|
|
||||||
return_value=Mock(
|
|
||||||
content=json.dumps(
|
|
||||||
{
|
|
||||||
"status": "analysis_complete",
|
|
||||||
"raw_analysis": """
|
|
||||||
🔴 CRITIQUE: Aucun problème critique trouvé.
|
|
||||||
|
|
||||||
🟠 ÉLEVÉ: Fichier example.py:42 - Fonction trop complexe
|
|
||||||
→ Problème: La fonction process_data() contient trop de responsabilités
|
|
||||||
→ Solution: Décomposer en fonctions plus petites et spécialisées
|
|
||||||
|
|
||||||
🟡 MOYEN: Gestion d'erreurs insuffisante
|
|
||||||
→ Problème: Plusieurs fonctions n'ont pas de gestion d'erreurs appropriée
|
|
||||||
→ Solution: Ajouter des try-catch et validation des paramètres
|
|
||||||
|
|
||||||
✅ Points positifs:
|
|
||||||
• Code bien commenté et lisible
|
|
||||||
• Nomenclature cohérente
|
|
||||||
• Tests unitaires présents
|
|
||||||
""",
|
|
||||||
},
|
|
||||||
ensure_ascii=False,
|
|
||||||
),
|
|
||||||
usage={},
|
|
||||||
model_name="test-model",
|
|
||||||
metadata={},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
mock_get_provider.return_value = mock_provider
|
|
||||||
|
|
||||||
# Test the tool
|
|
||||||
codereview_tool = CodeReviewTool()
|
|
||||||
result = await codereview_tool.execute(
|
|
||||||
{
|
|
||||||
"step": "Complete review of Python code",
|
|
||||||
"step_number": 1,
|
|
||||||
"total_steps": 1,
|
|
||||||
"next_step_required": False,
|
|
||||||
"findings": "Code review complete",
|
|
||||||
"relevant_files": ["/test/example.py"],
|
|
||||||
"model": "test-model",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Checks
|
|
||||||
self.assertIsNotNone(result)
|
|
||||||
response_text = result[0].text
|
|
||||||
response_data = json.loads(response_text)
|
|
||||||
|
|
||||||
# Check UTF-8 characters in analysis
|
|
||||||
if "expert_analysis" in response_data:
|
|
||||||
analysis = response_data["expert_analysis"]["raw_analysis"]
|
|
||||||
# Check for French characters
|
|
||||||
self.assertIn("ÉLEVÉ", analysis)
|
|
||||||
self.assertIn("problème", analysis)
|
|
||||||
self.assertIn("spécialisées", analysis)
|
|
||||||
self.assertIn("appropriée", analysis)
|
|
||||||
self.assertIn("paramètres", analysis)
|
|
||||||
self.assertIn("présents", analysis)
|
|
||||||
# Check for emojis
|
|
||||||
self.assertIn("🔴", analysis)
|
|
||||||
self.assertIn("🟠", analysis)
|
|
||||||
self.assertIn("🟡", analysis)
|
|
||||||
self.assertIn("✅", analysis)
|
|
||||||
|
|
||||||
@patch("tools.shared.base_tool.BaseTool.get_model_provider")
|
|
||||||
async def test_debug_tool_french_error_analysis(self, mock_get_provider):
|
|
||||||
"""Test that the debug tool analyzes errors in French."""
|
|
||||||
# Mock provider
|
|
||||||
mock_provider = Mock()
|
|
||||||
mock_provider.get_provider_type.return_value = Mock(value="test")
|
|
||||||
mock_provider.supports_thinking_mode.return_value = False
|
|
||||||
mock_provider.generate_content = AsyncMock(
|
|
||||||
return_value=Mock(
|
|
||||||
content=json.dumps({
|
|
||||||
"status": "pause_for_investigation",
|
|
||||||
"step_number": 1,
|
|
||||||
"total_steps": 2,
|
|
||||||
"next_step_required": True,
|
|
||||||
"findings": "Erreur analysée: variable 'données' non définie. Cause probable: import manquant.",
|
|
||||||
"files_checked": ["/src/data_processor.py"],
|
|
||||||
"relevant_files": ["/src/data_processor.py"],
|
|
||||||
"hypothesis": "Variable 'données' not defined - missing import",
|
|
||||||
"confidence": "medium",
|
|
||||||
"investigation_status": "in_progress",
|
|
||||||
"error_analysis": "L'erreur concerne la variable 'données' qui n'est pas définie.",
|
|
||||||
}, ensure_ascii=False),
|
|
||||||
usage={},
|
|
||||||
model_name="test-model",
|
|
||||||
metadata={},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
mock_get_provider.return_value = mock_provider
|
|
||||||
|
|
||||||
# Test the debug tool
|
|
||||||
debug_tool = DebugIssueTool()
|
|
||||||
result = await debug_tool.execute(
|
|
||||||
{
|
|
||||||
"step": "Analyze NameError in data processing file",
|
|
||||||
"step_number": 1,
|
|
||||||
"total_steps": 2,
|
|
||||||
"next_step_required": True,
|
|
||||||
"findings": "Error detected during script execution",
|
|
||||||
"files_checked": ["/src/data_processor.py"],
|
|
||||||
"relevant_files": ["/src/data_processor.py"],
|
|
||||||
"hypothesis": "Variable 'données' not defined - missing import",
|
|
||||||
"confidence": "medium",
|
|
||||||
"model": "test-model",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Checks
|
|
||||||
self.assertIsNotNone(result)
|
|
||||||
response_text = result[0].text
|
|
||||||
response_data = json.loads(response_text)
|
|
||||||
|
|
||||||
# Check response structure
|
|
||||||
self.assertIn("status", response_data)
|
|
||||||
self.assertIn("investigation_status", response_data)
|
|
||||||
|
|
||||||
# Check that UTF-8 characters are preserved
|
|
||||||
response_str = json.dumps(response_data, ensure_ascii=False)
|
|
||||||
self.assertIn("données", response_str)
|
|
||||||
|
|
||||||
def test_utf8_emoji_preservation_in_workflow_responses(self):
|
|
||||||
"""Test that emojis are preserved in workflow tool responses."""
|
|
||||||
# Mock workflow response with various emojis
|
|
||||||
test_data = {
|
|
||||||
"status": "analysis_complete",
|
|
||||||
"severity_indicators": {
|
|
||||||
"critical": "🔴",
|
|
||||||
"high": "🟠",
|
|
||||||
"medium": "🟡",
|
|
||||||
"low": "🟢",
|
|
||||||
"success": "✅",
|
|
||||||
"error": "❌",
|
|
||||||
"warning": "⚠️",
|
|
||||||
},
|
|
||||||
"progress": "Analysis completed 🎉",
|
|
||||||
"recommendations": [
|
|
||||||
"Optimize performance 🚀",
|
|
||||||
"Improve documentation 📚",
|
|
||||||
"Add unit tests 🧪",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test JSON encoding with ensure_ascii=False
|
|
||||||
json_str = json.dumps(test_data, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
# Check emojis are preserved
|
|
||||||
self.assertIn("🔴", json_str)
|
|
||||||
self.assertIn("🟠", json_str)
|
|
||||||
self.assertIn("🟡", json_str)
|
|
||||||
self.assertIn("🟢", json_str)
|
|
||||||
self.assertIn("✅", json_str)
|
|
||||||
self.assertIn("❌", json_str)
|
|
||||||
self.assertIn("⚠️", json_str)
|
|
||||||
self.assertIn("🎉", json_str)
|
|
||||||
self.assertIn("🚀", json_str)
|
|
||||||
self.assertIn("📚", json_str)
|
|
||||||
self.assertIn("🧪", json_str)
|
|
||||||
|
|
||||||
# No escaped Unicode
|
|
||||||
self.assertNotIn("\\u", json_str)
|
|
||||||
|
|
||||||
# Test parsing preserves emojis
|
|
||||||
parsed = json.loads(json_str)
|
|
||||||
self.assertEqual(parsed["severity_indicators"]["critical"], "🔴")
|
|
||||||
self.assertEqual(parsed["progress"], "Analysis completed 🎉")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main(verbosity=2)
|
|
||||||
Reference in New Issue
Block a user