Files
rpa_vision_v3/tests/unit/test_workflow_components.py
Dom a27b74cf22 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>
2026-01-29 11:23:51 +01:00

341 lines
12 KiB
Python

"""
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"])