Code/tests/unit/test_gitea.py
Stéphan Peccini 8e2556c2b0
test(unit): +381 tests unitaires — couverture 16%→35%
- 9 nouveaux fichiers de tests (persistance, translations, fiches, indices, IHH)
- Enrichissement des tests existants (graph_utils, gitea, widgets, tickets)
- 67→448 tests, tous passent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 11:52:21 +01:00

298 lines
11 KiB
Python

"""Tests unitaires pour le module utils.gitea.
Ces tests vérifient les fonctions d'interaction avec l'API Gitea.
"""
import base64
from datetime import datetime, timezone
from unittest.mock import 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")
def test_telechargement_fichier_inexistant(self, mock_date_commit, mock_get):
"""Test le téléchargement quand le fichier local n'existe pas."""
# 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
m_open = mock_open()
with patch("pathlib.Path.exists", return_value=False), \
patch("pathlib.Path.open", m_open):
resultat = charger_instructions_depuis_gitea("Instructions.md")
# Vérifie que le fichier a été écrit
m_open.assert_called_once_with("w", encoding="utf-8")
assert resultat == contenu_md
@patch("utils.gitea.requests.get")
@patch("utils.gitea.recuperer_date_dernier_commit")
def test_utilisation_cache_local_recent(self, mock_date_commit, mock_get):
"""Test l'utilisation du cache local si plus récent."""
# Date fichier local plus récent
local_time = datetime(2025, 1, 20, tzinfo=timezone.utc)
mock_stat = Mock()
mock_stat.st_mtime = local_time.timestamp()
# Date commit distant plus ancien
mock_date_commit.return_value = datetime(2025, 1, 15, tzinfo=timezone.utc)
with patch("pathlib.Path.exists", return_value=True), \
patch("pathlib.Path.stat", return_value=mock_stat), \
patch("pathlib.Path.open", mock_open(read_data="# Local Instructions")):
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("pathlib.Path.exists", return_value=True), \
patch("pathlib.Path.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")
def test_erreur_reseau_sans_cache(self, mock_date_commit, _mock_get):
"""Test le retour None si erreur et pas de cache."""
mock_date_commit.side_effect = requests.RequestException("Network error")
with patch("pathlib.Path.exists", return_value=False):
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")
def test_telechargement_schema_file(self, mock_date_commit, mock_get):
"""Test le téléchargement d'un fichier schema depuis Gitea."""
# 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
m_open = mock_open()
with patch("pathlib.Path.exists", return_value=False), \
patch("pathlib.Path.open", m_open):
resultat = charger_schema_depuis_gitea("test_schema.txt")
# Vérifie que le fichier a été écrit
assert m_open.called
assert resultat == "OK"
@patch("utils.gitea.requests.get")
@patch("utils.gitea.recuperer_date_dernier_commit")
def test_cache_schema_file(self, mock_date_commit, mock_get):
"""Test l'utilisation du cache pour le fichier schema."""
# Date fichier local plus récent
local_time = datetime(2025, 1, 20, tzinfo=timezone.utc)
mock_stat = Mock()
mock_stat.st_mtime = 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(b"digraph G { cached }").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("pathlib.Path.exists", return_value=True), \
patch("pathlib.Path.stat", return_value=mock_stat):
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 == {}