v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution
- Frontend v4 accessible sur réseau local (192.168.1.40) - Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard) - Ollama GPU fonctionnel - Self-healing interactif - Dashboard confiance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
340
tests/unit/test_workflow_components.py
Normal file
340
tests/unit/test_workflow_components.py
Normal file
@@ -0,0 +1,340 @@
|
||||
"""
|
||||
Tests unitaires pour les composants de workflow
|
||||
|
||||
Teste:
|
||||
- VariableManager : Gestion des variables
|
||||
- SemanticMatcher : Matching sémantique
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from core.workflow import (
|
||||
VariableManager,
|
||||
VariableDefinition,
|
||||
SemanticMatcher,
|
||||
WorkflowMatch,
|
||||
create_variable_manager_from_workflow
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tests VariableManager
|
||||
# =============================================================================
|
||||
|
||||
class TestVariableManager:
|
||||
"""Tests pour VariableManager."""
|
||||
|
||||
def test_define_variable(self):
|
||||
"""Test définition de variable."""
|
||||
vm = VariableManager()
|
||||
vm.define_variable("client", "Nom du client", required=True)
|
||||
|
||||
definitions = vm.get_definitions()
|
||||
assert "client" in definitions
|
||||
assert definitions["client"].required is True
|
||||
|
||||
def test_set_and_get_variable(self):
|
||||
"""Test set/get de variable."""
|
||||
vm = VariableManager()
|
||||
vm.set_variable("name", "John")
|
||||
|
||||
assert vm.get_variable("name") == "John"
|
||||
assert vm.get_variable("unknown") is None
|
||||
assert vm.get_variable("unknown", "default") == "default"
|
||||
|
||||
def test_substitute_simple(self):
|
||||
"""Test substitution simple."""
|
||||
vm = VariableManager()
|
||||
vm.set_variable("client", "Acme")
|
||||
|
||||
result = vm.substitute("Facturer {{client}}")
|
||||
assert result == "Facturer Acme"
|
||||
|
||||
def test_substitute_with_default(self):
|
||||
"""Test substitution avec valeur par défaut."""
|
||||
vm = VariableManager()
|
||||
|
||||
result = vm.substitute("Montant: {{montant|0}} euros")
|
||||
assert result == "Montant: 0 euros"
|
||||
|
||||
def test_substitute_missing_variable(self):
|
||||
"""Test substitution avec variable manquante."""
|
||||
vm = VariableManager()
|
||||
|
||||
result = vm.substitute("Client: {{client}}")
|
||||
# Variable non définie reste telle quelle
|
||||
assert "{{client}}" in result
|
||||
|
||||
def test_substitute_dict(self):
|
||||
"""Test substitution dans un dictionnaire."""
|
||||
vm = VariableManager()
|
||||
vm.set_variable("client", "Acme")
|
||||
vm.set_variable("montant", "1000")
|
||||
|
||||
data = {
|
||||
"name": "Facture {{client}}",
|
||||
"amount": "{{montant}}",
|
||||
"nested": {
|
||||
"description": "Pour {{client}}"
|
||||
}
|
||||
}
|
||||
|
||||
result = vm.substitute_dict(data)
|
||||
assert result["name"] == "Facture Acme"
|
||||
assert result["amount"] == "1000"
|
||||
assert result["nested"]["description"] == "Pour Acme"
|
||||
|
||||
def test_extract_variables(self):
|
||||
"""Test extraction de variables."""
|
||||
vm = VariableManager()
|
||||
|
||||
text = "Facturer {{client}} pour {{montant}} euros"
|
||||
variables = vm.extract_variables(text)
|
||||
|
||||
assert "client" in variables
|
||||
assert "montant" in variables
|
||||
assert len(variables) == 2
|
||||
|
||||
def test_validation_required(self):
|
||||
"""Test validation des variables requises."""
|
||||
vm = VariableManager()
|
||||
vm.define_variable("client", required=True)
|
||||
vm.define_variable("optional", required=False)
|
||||
|
||||
# Sans valeur pour client
|
||||
errors = vm.validate()
|
||||
assert len(errors) == 1
|
||||
assert "client" in errors[0]
|
||||
|
||||
# Avec valeur pour client
|
||||
vm.set_variable("client", "Acme")
|
||||
errors = vm.validate()
|
||||
assert len(errors) == 0
|
||||
|
||||
def test_validation_with_default(self):
|
||||
"""Test validation avec valeur par défaut."""
|
||||
vm = VariableManager()
|
||||
vm.define_variable("montant", required=True, default_value="0")
|
||||
|
||||
# La valeur par défaut satisfait la validation
|
||||
errors = vm.validate()
|
||||
assert len(errors) == 0
|
||||
|
||||
def test_type_conversion(self):
|
||||
"""Test conversion de type."""
|
||||
vm = VariableManager()
|
||||
vm.define_variable("count", var_type="integer")
|
||||
vm.define_variable("active", var_type="boolean")
|
||||
|
||||
vm.set_variable("count", "42")
|
||||
vm.set_variable("active", "true")
|
||||
|
||||
assert vm.get_variable("count") == 42
|
||||
assert vm.get_variable("active") is True
|
||||
|
||||
def test_serialization(self):
|
||||
"""Test sérialisation/désérialisation."""
|
||||
vm = VariableManager()
|
||||
vm.define_variable("client", "Nom", required=True)
|
||||
vm.set_variable("client", "Acme")
|
||||
|
||||
# Sérialiser
|
||||
data = vm.to_dict()
|
||||
|
||||
# Désérialiser
|
||||
vm2 = VariableManager.from_dict(data)
|
||||
|
||||
assert vm2.get_variable("client") == "Acme"
|
||||
assert "client" in vm2.get_definitions()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tests SemanticMatcher
|
||||
# =============================================================================
|
||||
|
||||
class TestSemanticMatcher:
|
||||
"""Tests pour SemanticMatcher."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_workflows_dir(self):
|
||||
"""Créer un répertoire temporaire avec des workflows de test."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
workflows_dir = Path(tmpdir)
|
||||
|
||||
# Créer workflow de facturation
|
||||
facturation = {
|
||||
"name": "Facturation Client",
|
||||
"description": "Créer une facture pour un client",
|
||||
"tags": ["facturer", "facture", "client", "invoice"],
|
||||
"param_patterns": [
|
||||
"(?:client|customer)\\s+(?P<client>[A-Za-z0-9_\\-]+)"
|
||||
]
|
||||
}
|
||||
with open(workflows_dir / "facturation.json", "w") as f:
|
||||
json.dump(facturation, f)
|
||||
|
||||
# Créer workflow d'export
|
||||
export = {
|
||||
"name": "Export Rapport",
|
||||
"description": "Exporter un rapport",
|
||||
"tags": ["export", "rapport", "pdf", "excel"],
|
||||
"param_patterns": [
|
||||
"(?:format|en)\\s+(?P<format>pdf|excel)"
|
||||
]
|
||||
}
|
||||
with open(workflows_dir / "export.json", "w") as f:
|
||||
json.dump(export, f)
|
||||
|
||||
yield workflows_dir
|
||||
|
||||
def test_load_workflows(self, temp_workflows_dir):
|
||||
"""Test chargement des workflows."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
workflows = matcher.get_all_workflows()
|
||||
assert len(workflows) == 2
|
||||
|
||||
def test_find_workflow_exact_match(self, temp_workflows_dir):
|
||||
"""Test matching exact."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
match = matcher.find_workflow("Facturation Client")
|
||||
assert match is not None
|
||||
assert match.workflow_name == "Facturation Client"
|
||||
assert match.confidence > 0.5
|
||||
|
||||
def test_find_workflow_by_tag(self, temp_workflows_dir):
|
||||
"""Test matching par tag."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
match = matcher.find_workflow("facturer quelque chose")
|
||||
assert match is not None
|
||||
assert "Facturation" in match.workflow_name
|
||||
|
||||
def test_find_workflow_by_keywords(self, temp_workflows_dir):
|
||||
"""Test matching par mots-clés."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
match = matcher.find_workflow("créer une facture")
|
||||
assert match is not None
|
||||
assert "Facturation" in match.workflow_name
|
||||
|
||||
def test_extract_params(self, temp_workflows_dir):
|
||||
"""Test extraction de paramètres."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
match = matcher.find_workflow("facturer client Acme")
|
||||
assert match is not None
|
||||
assert "client" in match.extracted_params
|
||||
assert match.extracted_params["client"].lower() == "acme"
|
||||
|
||||
def test_extract_range_params(self, temp_workflows_dir):
|
||||
"""Test extraction de paramètres de plage."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
match = matcher.find_workflow("facturer de A à Z")
|
||||
assert match is not None
|
||||
assert "start" in match.extracted_params
|
||||
assert "end" in match.extracted_params
|
||||
|
||||
def test_find_multiple_workflows(self, temp_workflows_dir):
|
||||
"""Test recherche de plusieurs workflows."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
matches = matcher.find_workflows("rapport", limit=5)
|
||||
assert len(matches) >= 1
|
||||
assert any("Export" in m.workflow_name for m in matches)
|
||||
|
||||
def test_min_confidence_filter(self, temp_workflows_dir):
|
||||
"""Test filtre de confiance minimale."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
# Avec confiance élevée, moins de résultats
|
||||
matches_high = matcher.find_workflows("xyz random", min_confidence=0.8)
|
||||
matches_low = matcher.find_workflows("xyz random", min_confidence=0.1)
|
||||
|
||||
assert len(matches_high) <= len(matches_low)
|
||||
|
||||
def test_suggest_commands(self, temp_workflows_dir):
|
||||
"""Test suggestions de commandes."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
suggestions = matcher.suggest_commands("Fact")
|
||||
assert len(suggestions) > 0
|
||||
assert any("Facturation" in s for s in suggestions)
|
||||
|
||||
def test_get_workflow_help(self, temp_workflows_dir):
|
||||
"""Test aide pour un workflow."""
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
|
||||
help_text = matcher.get_workflow_help("facturation")
|
||||
assert "Facturation Client" in help_text
|
||||
assert "Tags" in help_text
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tests d'intégration
|
||||
# =============================================================================
|
||||
|
||||
class TestWorkflowIntegration:
|
||||
"""Tests d'intégration VariableManager + SemanticMatcher."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_workflows_dir(self):
|
||||
"""Créer un répertoire temporaire avec un workflow paramétré."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
workflows_dir = Path(tmpdir)
|
||||
|
||||
workflow = {
|
||||
"name": "Facturation",
|
||||
"description": "Facturer {{client}} pour {{montant}} euros",
|
||||
"tags": ["facturer"],
|
||||
"variables": [
|
||||
{"name": "client", "required": True},
|
||||
{"name": "montant", "required": False, "default_value": "0"}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"action": {
|
||||
"type": "text_input",
|
||||
"parameters": {"text": "{{client}}"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
with open(workflows_dir / "facturation.json", "w") as f:
|
||||
json.dump(workflow, f)
|
||||
|
||||
yield workflows_dir
|
||||
|
||||
def test_full_workflow_execution(self, temp_workflows_dir):
|
||||
"""Test exécution complète avec variables."""
|
||||
# 1. Trouver le workflow
|
||||
matcher = SemanticMatcher(str(temp_workflows_dir))
|
||||
match = matcher.find_workflow("facturer client Acme")
|
||||
|
||||
assert match is not None
|
||||
|
||||
# 2. Charger le workflow
|
||||
with open(match.workflow_path) as f:
|
||||
workflow_data = json.load(f)
|
||||
|
||||
# 3. Créer le VariableManager
|
||||
vm = create_variable_manager_from_workflow(workflow_data)
|
||||
|
||||
# 4. Injecter les paramètres extraits
|
||||
vm.set_variables(match.extracted_params)
|
||||
|
||||
# 5. Substituer les variables
|
||||
result = vm.substitute_dict(workflow_data)
|
||||
|
||||
# Vérifier la substitution (le matcher normalise en minuscules)
|
||||
assert "acme" in result["description"].lower()
|
||||
assert result["edges"][0]["action"]["parameters"]["text"].lower() == "acme"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user