Cette mise a jour complete ameliore significativement la qualite et la maintenabilite du projet. 1. Extension de la couverture de tests Couverture globale passee de 8% a 16% (+100%) - Ajout de 25 nouveaux tests (total: 67 tests, 100% passent) - Nouveaux fichiers de tests: * tests/unit/test_gitea.py (17 tests) * tests/unit/test_fiches_tickets.py (8 tests) Etat de la couverture par module: - utils/gitea.py: 100% - utils/widgets.py: 100% - utils/logger.py: 94% - app/fiches/utils/tickets/core.py: 77% - utils/graph_utils.py: 59% 2. Documentation d'architecture complete Creation de 3 nouveaux documents (30 Ko total): - docs/ARCHITECTURE.md (15 Ko) * Architecture complete du projet * Flux de donnees detailles * Indices de vulnerabilite (IHH, ISG, ICS, IVC) * Structure du graphe NetworkX - docs/MODULES.md (15 Ko) * Guide des 11 modules principaux * Exemples de code (15+ snippets) * Bonnes pratiques * Guide de depannage - docs/README.md (4 Ko) * Index de toute la documentation Contenu documente: - 5 modules applicatifs - 6 modules utilitaires - 4 indices de vulnerabilite avec formules et seuils - Conventions de code 3. Reorganisation de la documentation Structure finale optimisee: - Racine: README.md (mis a jour) + Instructions.md - docs/: 11 documents organises par categorie Fichiers deplaces vers docs/: - README_connexion.md -> docs/CONNEXION.md - GUIDE_LOGS.md -> docs/ - GUIDE_RUFF.md -> docs/ - RAPPORT_RUFF.md -> docs/ - RAPPORT_CORRECTIONS_AUTO.md -> docs/ - REFACTORING_REPORT.md -> docs/ - VERIFICATION_LOGS.md -> docs/ - TODO_IA_BATCH.md -> docs/ 4. Ajout de docstrings 52 fonctions documentees en style Google (100%) Documentation en francais avec Args, Returns, Raises 5. Corrections automatiques Ruff Application de 347 corrections automatiques: - Formatage du code (line-length: 120) - Organisation des imports - Simplifications syntaxiques - Suppressions de code mort - Ameliorations de performance 6. Configuration qualite du code Nouveaux fichiers: - pyproject.toml: configuration Ruff complete - .vscode/settings.json: integration Ruff avec formatOnSave - GUIDE_RUFF.md: documentation du linter - GUIDE_LOGS.md: documentation du logging - .gitignore: ajout htmlcov/ pour rapports de couverture Etat final du projet: - Linter: Ruff configure (15 regles actives) - Tests: 67 tests (100% passent) - Couverture de code: 16% - Docstrings: 52/52 (100%) - Documentation: 11 fichiers organises Impact: - Tests plus robustes et maintenables - Documentation technique complete - Meilleure organisation des fichiers - Workflow optimise avec Ruff - Code pret pour integration continue References: - Architecture: docs/ARCHITECTURE.md - Guide modules: docs/MODULES.md - Tests: tests/unit/ - Configuration: pyproject.toml Co-Authored-By: Claude <noreply@anthropic.com>
309 lines
12 KiB
Python
309 lines
12 KiB
Python
"""Tests unitaires pour le module utils.gitea.
|
|
|
|
Ces tests vérifient les fonctions d'interaction avec l'API Gitea.
|
|
"""
|
|
|
|
import base64
|
|
import os
|
|
from datetime import datetime, timezone
|
|
from unittest.mock import MagicMock, Mock, mock_open, patch
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
from utils.gitea import (
|
|
charger_arborescence_fiches,
|
|
charger_instructions_depuis_gitea,
|
|
charger_schema_depuis_gitea,
|
|
lire_fichier_local,
|
|
recuperer_date_dernier_commit,
|
|
)
|
|
|
|
|
|
class TestLireFichierLocal:
|
|
"""Tests pour la fonction lire_fichier_local."""
|
|
|
|
def test_lecture_fichier_utf8(self, tmp_path):
|
|
"""Test la lecture d'un fichier UTF-8 standard."""
|
|
fichier = tmp_path / "test.txt"
|
|
contenu_attendu = "Contenu de test avec caractères spéciaux: éàç"
|
|
fichier.write_text(contenu_attendu, encoding="utf-8")
|
|
|
|
resultat = lire_fichier_local(str(fichier))
|
|
|
|
assert resultat == contenu_attendu
|
|
|
|
def test_lecture_fichier_avec_accents(self, tmp_path):
|
|
"""Test la lecture d'un fichier avec caractères accentués."""
|
|
fichier = tmp_path / "accents.txt"
|
|
contenu = "Voici des accents: é, è, à, ç, ù"
|
|
fichier.write_text(contenu, encoding="utf-8")
|
|
|
|
resultat = lire_fichier_local(str(fichier))
|
|
|
|
assert resultat == contenu
|
|
|
|
def test_lecture_fichier_vide(self, tmp_path):
|
|
"""Test la lecture d'un fichier vide."""
|
|
fichier = tmp_path / "vide.txt"
|
|
fichier.write_text("", encoding="utf-8")
|
|
|
|
resultat = lire_fichier_local(str(fichier))
|
|
|
|
assert resultat == ""
|
|
|
|
def test_fichier_inexistant(self):
|
|
"""Test la gestion d'un fichier inexistant."""
|
|
with pytest.raises(FileNotFoundError):
|
|
lire_fichier_local("fichier_inexistant.txt")
|
|
|
|
|
|
class TestRecupererDateDernierCommit:
|
|
"""Tests pour la fonction recuperer_date_dernier_commit."""
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
def test_recuperation_date_commit(self, mock_get):
|
|
"""Test la récupération de la date du dernier commit."""
|
|
# Mock de la réponse Gitea
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = [
|
|
{
|
|
"commit": {
|
|
"author": {
|
|
"date": "2025-01-15T10:30:00Z"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
mock_get.return_value = mock_response
|
|
|
|
resultat = recuperer_date_dernier_commit("https://gitea.example.com/api/v1/repos/org/repo/commits")
|
|
|
|
assert resultat is not None
|
|
assert isinstance(resultat, datetime)
|
|
assert resultat.year == 2025
|
|
assert resultat.month == 1
|
|
assert resultat.day == 15
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
def test_aucun_commit(self, mock_get):
|
|
"""Test avec un dépôt sans commits."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = []
|
|
mock_get.return_value = mock_response
|
|
|
|
resultat = recuperer_date_dernier_commit("https://gitea.example.com/api/v1/repos/org/repo/commits")
|
|
|
|
assert resultat is None
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
def test_erreur_requete(self, mock_get):
|
|
"""Test la gestion d'une erreur de requête."""
|
|
mock_get.side_effect = requests.RequestException("Network error")
|
|
|
|
resultat = recuperer_date_dernier_commit("https://gitea.example.com/api/v1/repos/org/repo/commits")
|
|
|
|
assert resultat is None
|
|
|
|
|
|
class TestChargerInstructionsDepuisGitea:
|
|
"""Tests pour la fonction charger_instructions_depuis_gitea."""
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
@patch("utils.gitea.recuperer_date_dernier_commit")
|
|
@patch("os.path.exists")
|
|
@patch("os.path.getmtime")
|
|
def test_telechargement_fichier_inexistant(self, mock_getmtime, mock_exists, mock_date_commit, mock_get):
|
|
"""Test le téléchargement quand le fichier local n'existe pas."""
|
|
# Fichier local n'existe pas
|
|
mock_exists.return_value = False
|
|
|
|
# Commit distant disponible
|
|
mock_date_commit.return_value = datetime(2025, 1, 15, tzinfo=timezone.utc)
|
|
|
|
# Contenu encodé en base64
|
|
contenu_md = "# Instructions\nCeci est un test"
|
|
contenu_base64 = base64.b64encode(contenu_md.encode("utf-8")).decode("utf-8")
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {"content": contenu_base64}
|
|
mock_get.return_value = mock_response
|
|
|
|
with patch("builtins.open", mock_open()) as mock_file:
|
|
resultat = charger_instructions_depuis_gitea("Instructions.md")
|
|
|
|
# Vérifie que le fichier a été écrit
|
|
mock_file.assert_called_once_with("Instructions.md", "w", encoding="utf-8")
|
|
assert resultat == contenu_md
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
@patch("utils.gitea.recuperer_date_dernier_commit")
|
|
@patch("os.path.exists")
|
|
@patch("os.path.getmtime")
|
|
@patch("builtins.open", new_callable=mock_open, read_data="# Local Instructions")
|
|
def test_utilisation_cache_local_recent(self, mock_file, mock_getmtime, mock_exists, mock_date_commit, mock_get):
|
|
"""Test l'utilisation du cache local si plus récent."""
|
|
# Fichier local existe
|
|
mock_exists.return_value = True
|
|
|
|
# Date fichier local plus récent
|
|
local_time = datetime(2025, 1, 20, tzinfo=timezone.utc)
|
|
mock_getmtime.return_value = local_time.timestamp()
|
|
|
|
# Date commit distant plus ancien
|
|
mock_date_commit.return_value = datetime(2025, 1, 15, tzinfo=timezone.utc)
|
|
|
|
resultat = charger_instructions_depuis_gitea("Instructions.md")
|
|
|
|
# Doit lire le fichier local sans appeler l'API
|
|
assert mock_get.call_count == 0
|
|
assert resultat == "# Local Instructions"
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
@patch("utils.gitea.recuperer_date_dernier_commit")
|
|
def test_erreur_reseau_avec_cache(self, mock_date_commit, mock_get):
|
|
"""Test le fallback sur le cache en cas d'erreur réseau."""
|
|
mock_date_commit.side_effect = requests.RequestException("Network error")
|
|
|
|
with patch("os.path.exists", return_value=True):
|
|
with patch("builtins.open", mock_open(read_data="# Cached content")):
|
|
resultat = charger_instructions_depuis_gitea("Instructions.md")
|
|
|
|
assert resultat == "# Cached content"
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
@patch("utils.gitea.recuperer_date_dernier_commit")
|
|
@patch("os.path.exists")
|
|
def test_erreur_reseau_sans_cache(self, mock_exists, mock_date_commit, mock_get):
|
|
"""Test le retour None si erreur et pas de cache."""
|
|
mock_exists.return_value = False
|
|
mock_date_commit.side_effect = requests.RequestException("Network error")
|
|
|
|
resultat = charger_instructions_depuis_gitea("Instructions.md")
|
|
|
|
assert resultat is None
|
|
|
|
|
|
class TestChargerSchemaDepuisGitea:
|
|
"""Tests pour la fonction charger_schema_depuis_gitea."""
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
@patch("utils.gitea.recuperer_date_dernier_commit")
|
|
@patch("os.path.exists")
|
|
@patch("os.path.getmtime")
|
|
def test_telechargement_schema_file(self, mock_getmtime, mock_exists, mock_date_commit, mock_get):
|
|
"""Test le téléchargement d'un fichier schema depuis Gitea."""
|
|
# Fichier local n'existe pas
|
|
mock_exists.return_value = False
|
|
|
|
# Commit distant disponible
|
|
mock_date_commit.return_value = datetime(2025, 1, 15, tzinfo=timezone.utc)
|
|
|
|
# Contenu DOT encodé en base64
|
|
contenu_dot = "digraph G { A -> B; }"
|
|
contenu_base64 = base64.b64encode(contenu_dot.encode("utf-8")).decode("utf-8")
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {"content": contenu_base64}
|
|
mock_get.return_value = mock_response
|
|
|
|
with patch("builtins.open", mock_open()) as mock_file:
|
|
resultat = charger_schema_depuis_gitea("test_schema.txt")
|
|
|
|
# Vérifie que le fichier a été écrit
|
|
assert mock_file.called
|
|
assert resultat == "OK"
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
@patch("utils.gitea.recuperer_date_dernier_commit")
|
|
@patch("os.path.exists")
|
|
@patch("os.path.getmtime")
|
|
@patch("builtins.open", new_callable=mock_open)
|
|
def test_cache_schema_file(self, mock_file, mock_getmtime, mock_exists, mock_date_commit, mock_get):
|
|
"""Test l'utilisation du cache pour le fichier schema."""
|
|
# Fichier local existe et plus récent
|
|
mock_exists.return_value = True
|
|
local_time = datetime(2025, 1, 20, tzinfo=timezone.utc)
|
|
mock_getmtime.return_value = local_time.timestamp()
|
|
|
|
# Date commit distant plus ancien
|
|
mock_date_commit.return_value = datetime(2025, 1, 15, tzinfo=timezone.utc)
|
|
|
|
# Mock response pour le premier appel (get file info)
|
|
contenu_base64 = base64.b64encode("digraph G { cached }".encode("utf-8")).decode("utf-8")
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {"content": contenu_base64}
|
|
mock_get.return_value = mock_response
|
|
|
|
resultat = charger_schema_depuis_gitea("test_schema.txt")
|
|
|
|
# Doit retourner OK sans réécrire (fichier déjà à jour)
|
|
assert resultat == "OK"
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
def test_erreur_chargement_schema(self, mock_get):
|
|
"""Test la gestion d'erreur lors du chargement du schema."""
|
|
mock_get.side_effect = requests.RequestException("Network error")
|
|
|
|
resultat = charger_schema_depuis_gitea("test_schema.txt")
|
|
|
|
assert resultat is None
|
|
|
|
|
|
class TestChargerArborescenceFiches:
|
|
"""Tests pour la fonction charger_arborescence_fiches."""
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
def test_arborescence_vide(self, mock_get):
|
|
"""Test avec un dépôt sans dossiers."""
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = []
|
|
mock_get.return_value = mock_response
|
|
|
|
resultat = charger_arborescence_fiches()
|
|
|
|
assert resultat == {}
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
def test_arborescence_avec_dossiers(self, mock_get):
|
|
"""Test avec des dossiers contenant des fiches."""
|
|
# Mock réponse pour la liste des dossiers
|
|
mock_response_dossiers = Mock()
|
|
mock_response_dossiers.status_code = 200
|
|
mock_response_dossiers.json.return_value = [
|
|
{"name": "Composants", "type": "dir", "url": "https://gitea.example.com/api/v1/repos/org/repo/contents/Documents/Composants"}
|
|
]
|
|
|
|
# Mock réponse pour le contenu du dossier
|
|
mock_response_fichiers = Mock()
|
|
mock_response_fichiers.status_code = 200
|
|
mock_response_fichiers.json.return_value = [
|
|
{"name": "Processeur.md", "download_url": "https://gitea.example.com/download/Processeur.md"},
|
|
{"name": "Memoire.md", "download_url": "https://gitea.example.com/download/Memoire.md"}
|
|
]
|
|
|
|
# Configure les appels successifs
|
|
mock_get.side_effect = [mock_response_dossiers, mock_response_fichiers]
|
|
|
|
resultat = charger_arborescence_fiches()
|
|
|
|
assert "Composants" in resultat
|
|
assert len(resultat["Composants"]) == 2
|
|
assert resultat["Composants"][0]["nom"] == "Memoire.md" # Trié alphabétiquement
|
|
assert resultat["Composants"][1]["nom"] == "Processeur.md"
|
|
|
|
@patch("utils.gitea.requests.get")
|
|
def test_erreur_requete(self, mock_get):
|
|
"""Test la gestion d'erreur lors de la requête."""
|
|
mock_get.side_effect = requests.RequestException("Network error")
|
|
|
|
resultat = charger_arborescence_fiches()
|
|
|
|
assert resultat == {}
|