Files
rpa_vision_v3/tests/unit/test_storage_manager.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

408 lines
14 KiB
Python

"""
Tests unitaires pour StorageManager
"""
import pytest
import tempfile
import shutil
from pathlib import Path
import numpy as np
from datetime import datetime
from core.persistence import StorageManager
# Mock classes pour les tests (en attendant l'implémentation complète)
class MockRawSession:
"""Mock de RawSession pour les tests"""
def __init__(self, session_id, started_at, events=None, screenshots=None):
self.session_id = session_id
self.started_at = started_at
self.start_time = started_at # Alias pour compatibilité
self.events = events or []
self.screenshots = screenshots or []
self.agent_version = "test_v1.0.0"
self.environment = {"os": "test"}
self.user = {"name": "test_user"}
self.context = {"test": "context"}
def to_json(self):
return {
"schema_version": "rawsession_v1",
"session_id": self.session_id,
"agent_version": self.agent_version,
"environment": self.environment,
"user": self.user,
"context": self.context,
"started_at": self.started_at,
"start_time": self.started_at,
"ended_at": None,
"events": self.events,
"screenshots": self.screenshots
}
@classmethod
def from_json(cls, data):
session = cls(
session_id=data["session_id"],
started_at=data.get("started_at", data.get("start_time")),
events=data.get("events", []),
screenshots=data.get("screenshots", [])
)
session.agent_version = data.get("agent_version", "test_v1.0.0")
session.environment = data.get("environment", {"os": "test"})
session.user = data.get("user", {"name": "test_user"})
session.context = data.get("context", {"test": "context"})
return session
class MockObject:
"""Mock object pour accès par attributs"""
def __init__(self, **kwargs):
for key, value in kwargs.items():
if isinstance(value, dict):
setattr(self, key, MockObject(**value))
else:
setattr(self, key, value)
class MockScreenState:
"""Mock de ScreenState pour les tests"""
def __init__(self, state_id, timestamp, raw=None, perception=None, context=None, window=None, session_id="test_session"):
self.state_id = state_id
self.screen_state_id = state_id # Alias pour compatibilité avec le vrai modèle
self.timestamp = timestamp
self.session_id = session_id
# Convertir dicts en objets pour accès par attributs
self.raw = MockObject(**(raw or {}))
self.perception = MockObject(**(perception or {}))
self.context = MockObject(**(context or {}))
self.window = MockObject(**(window or {"app_name": "test.exe", "window_title": "Test", "screen_resolution": [1920, 1080]}))
def to_json(self):
# Convertir les MockObjects en dicts
def to_dict(obj):
if isinstance(obj, MockObject):
return {k: to_dict(v) for k, v in obj.__dict__.items()}
return obj
return {
"schema_version": "screenstate_v1",
"screen_state_id": self.state_id,
"state_id": self.state_id,
"session_id": self.session_id,
"timestamp": self.timestamp,
"window": to_dict(self.window),
"raw": to_dict(self.raw),
"perception": to_dict(self.perception),
"context": to_dict(self.context)
}
@classmethod
def from_json(cls, data):
return cls(
state_id=data.get("screen_state_id", data.get("state_id")),
timestamp=data["timestamp"],
session_id=data.get("session_id", "test_session"),
window=data.get("window", {"app_name": "test.exe", "window_title": "Test", "screen_resolution": [1920, 1080]}),
raw=data.get("raw", {}),
perception=data.get("perception", {}),
context=data.get("context", {})
)
@pytest.fixture
def temp_storage():
"""Crée un répertoire temporaire pour les tests."""
temp_dir = tempfile.mkdtemp()
storage = StorageManager(base_path=temp_dir)
yield storage
# Cleanup
shutil.rmtree(temp_dir)
@pytest.fixture
def sample_raw_session():
"""Crée une RawSession de test."""
# Créer une session minimale avec to_json/from_json
session = MockRawSession(
session_id="test_session_001",
started_at=datetime.now().isoformat(),
events=[],
screenshots=[]
)
return session
@pytest.fixture
def sample_screen_state():
"""Crée un ScreenState de test."""
# Créer un state minimal avec to_json/from_json
state = MockScreenState(
state_id="test_state_001",
timestamp=datetime.now().isoformat(),
window={
"app_name": "test.exe",
"window_title": "Test",
"screen_resolution": [1920, 1080],
"workspace": "main"
},
raw={
"screenshot_path": "test.png",
"capture_method": "mss",
"file_size_bytes": 1024
},
perception={
"embedding": {
"provider": "openclip_ViT-B-32",
"vector_id": "test_vector_001",
"dimensions": 512
},
"detected_text": ["Hello", "World"],
"text_detection_method": "qwen_vl",
"confidence_avg": 0.95
},
context={
"current_workflow_candidate": None,
"workflow_step": None,
"user_id": "test_user",
"tags": [],
"business_variables": {}
}
)
return state
class TestStorageManagerBasics:
"""Tests de base du StorageManager."""
def test_initialization(self, temp_storage):
"""Test que le StorageManager initialise correctement les répertoires."""
assert temp_storage.base_path.exists()
assert (temp_storage.base_path / "sessions").exists()
assert (temp_storage.base_path / "screen_states").exists()
assert (temp_storage.base_path / "embeddings").exists()
assert (temp_storage.base_path / "faiss_index").exists()
assert (temp_storage.base_path / "workflows").exists()
def test_get_date_path(self, temp_storage):
"""Test que les chemins de date sont créés correctement."""
date_path = temp_storage._get_date_path("sessions")
today = datetime.now().strftime("%Y-%m-%d")
assert today in str(date_path)
assert date_path.exists()
class TestRawSessionPersistence:
"""Tests de persistence pour RawSession."""
def test_save_raw_session(self, temp_storage, sample_raw_session):
"""Test sauvegarde d'une RawSession."""
filepath = temp_storage.save_raw_session(sample_raw_session)
assert filepath.exists()
assert filepath.suffix == ".json"
assert "session_" in filepath.name
def test_load_raw_session(self, temp_storage, sample_raw_session):
"""Test chargement d'une RawSession."""
# Sauvegarder
filepath = temp_storage.save_raw_session(sample_raw_session)
# Charger
loaded_session = temp_storage.load_raw_session(filepath)
assert loaded_session.session_id == sample_raw_session.session_id
assert len(loaded_session.events) == len(sample_raw_session.events)
assert len(loaded_session.screenshots) == len(sample_raw_session.screenshots)
def test_raw_session_round_trip(self, temp_storage, sample_raw_session):
"""Test round-trip: save puis load doit retourner les mêmes données."""
filepath = temp_storage.save_raw_session(sample_raw_session)
loaded_session = temp_storage.load_raw_session(filepath)
# Vérifier que les données sont identiques
assert loaded_session.session_id == sample_raw_session.session_id
# Le vrai modèle convertit started_at en datetime, donc on compare les ISO strings
assert loaded_session.started_at.isoformat() == sample_raw_session.started_at
assert len(loaded_session.events) == len(sample_raw_session.events)
def test_list_sessions(self, temp_storage, sample_raw_session):
"""Test listage des sessions."""
# Sauvegarder quelques sessions
temp_storage.save_raw_session(sample_raw_session, "session_001")
temp_storage.save_raw_session(sample_raw_session, "session_002")
# Lister
sessions = temp_storage.list_sessions()
assert len(sessions) == 2
assert all("session_id" in s for s in sessions)
class TestScreenStatePersistence:
"""Tests de persistence pour ScreenState."""
def test_save_screen_state(self, temp_storage, sample_screen_state):
"""Test sauvegarde d'un ScreenState."""
filepath = temp_storage.save_screen_state(sample_screen_state)
assert filepath.exists()
assert filepath.suffix == ".json"
assert "state_" in filepath.name
def test_load_screen_state(self, temp_storage, sample_screen_state):
"""Test chargement d'un ScreenState."""
# Sauvegarder
filepath = temp_storage.save_screen_state(sample_screen_state)
# Charger
loaded_state = temp_storage.load_screen_state(filepath)
assert loaded_state.screen_state_id == sample_screen_state.state_id
assert loaded_state.raw.screenshot_path == sample_screen_state.raw.screenshot_path
def test_screen_state_round_trip(self, temp_storage, sample_screen_state):
"""Test round-trip pour ScreenState."""
filepath = temp_storage.save_screen_state(sample_screen_state)
loaded_state = temp_storage.load_screen_state(filepath)
assert loaded_state.screen_state_id == sample_screen_state.state_id
# Le vrai modèle a window comme objet séparé, pas dans context
assert loaded_state.window.window_title == sample_screen_state.window.window_title
class TestEmbeddingPersistence:
"""Tests de persistence pour embeddings."""
def test_save_embedding(self, temp_storage):
"""Test sauvegarde d'un embedding."""
vector = np.random.rand(512).astype(np.float32)
filepath = temp_storage.save_embedding(
vector,
embedding_id="test_001",
embedding_type="state"
)
assert filepath.exists()
assert filepath.suffix == ".npy"
def test_load_embedding(self, temp_storage):
"""Test chargement d'un embedding."""
original_vector = np.random.rand(512).astype(np.float32)
# Sauvegarder
temp_storage.save_embedding(
original_vector,
embedding_id="test_001",
embedding_type="state"
)
# Charger
loaded_vector, metadata = temp_storage.load_embedding(
embedding_id="test_001",
embedding_type="state"
)
assert np.allclose(loaded_vector, original_vector)
def test_embedding_with_metadata(self, temp_storage):
"""Test sauvegarde d'embedding avec métadonnées."""
vector = np.random.rand(512).astype(np.float32)
metadata = {
"source": "test",
"model": "openclip"
}
filepath = temp_storage.save_embedding(
vector,
embedding_id="test_001",
embedding_type="state",
metadata=metadata
)
# Vérifier que le fichier de métadonnées existe
metadata_file = filepath.with_suffix('.json')
assert metadata_file.exists()
# Charger et vérifier
loaded_vector, loaded_metadata = temp_storage.load_embedding(
embedding_id="test_001",
embedding_type="state"
)
assert loaded_metadata["source"] == "test"
assert loaded_metadata["model"] == "openclip"
def test_save_embeddings_batch(self, temp_storage):
"""Test sauvegarde en batch."""
embeddings = {
"emb_001": np.random.rand(512).astype(np.float32),
"emb_002": np.random.rand(512).astype(np.float32),
"emb_003": np.random.rand(512).astype(np.float32)
}
paths = temp_storage.save_embeddings_batch(embeddings, embedding_type="state")
assert len(paths) == 3
assert all(p.exists() for p in paths)
def test_list_embeddings(self, temp_storage):
"""Test listage des embeddings."""
# Sauvegarder quelques embeddings
for i in range(3):
vector = np.random.rand(512).astype(np.float32)
temp_storage.save_embedding(
vector,
embedding_id=f"test_{i:03d}",
embedding_type="state"
)
# Lister
embeddings = temp_storage.list_embeddings(embedding_type="state")
assert len(embeddings) == 3
assert all("embedding_id" in e for e in embeddings)
class TestStorageStats:
"""Tests des statistiques de stockage."""
def test_get_storage_stats(self, temp_storage, sample_raw_session):
"""Test récupération des statistiques."""
# Sauvegarder quelques fichiers
temp_storage.save_raw_session(sample_raw_session)
temp_storage.save_embedding(
np.random.rand(512).astype(np.float32),
embedding_id="test_001",
embedding_type="state"
)
# Récupérer les stats
stats = temp_storage.get_storage_stats()
assert "sessions" in stats
assert "embeddings" in stats
assert "total_size_mb" in stats
assert stats["sessions"] >= 1
assert stats["embeddings"] >= 1
class TestCleanup:
"""Tests du nettoyage des fichiers."""
def test_cleanup_old_files(self, temp_storage):
"""Test nettoyage des vieux fichiers."""
# Pour ce test, on ne peut pas facilement créer de vieux fichiers
# On teste juste que la méthode s'exécute sans erreur
deleted = temp_storage.cleanup_old_files(days_to_keep=30)
assert isinstance(deleted, dict)
assert "sessions" in deleted
assert "screen_states" in deleted
assert "embeddings" in deleted
if __name__ == "__main__":
pytest.main([__file__, "-v"])