Code/tests/unit/test_gitea.py
Stéphan Peccini f812fac89e
feat: Amelioration structure - tests, documentation et qualite du code
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>
2026-02-07 19:00:49 +01:00

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 == {}