Add automated tests with GitHub Actions - TP4
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
name: 🧪 Tests API - TP4
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📥 Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 🐳 Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: 🧪 Run tests with Docker Compose
|
||||||
|
run: |
|
||||||
|
echo "🚀 Démarrage des tests automatisés..."
|
||||||
|
docker-compose --profile test up --build --abort-on-container-exit test
|
||||||
|
|
||||||
|
- name: 📊 Show test results
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "📋 Résultats des tests:"
|
||||||
|
docker-compose --profile test logs test
|
||||||
|
|
||||||
|
- name: 🧹 Cleanup
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker-compose --profile test down -v
|
||||||
|
docker system prune -f
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
# 🧪 TP4 - Tests automatisés avec Docker
|
||||||
|
|
||||||
|
## 📋 Objectif
|
||||||
|
Automatiser les tests de l'application dans la CI avec Docker.
|
||||||
|
|
||||||
|
## ✅ Consignes réalisées
|
||||||
|
|
||||||
|
### 1. ✅ Fichier `test_api.py` créé
|
||||||
|
- **Pytest** configuré pour tester l'API
|
||||||
|
- Tests pour vérifier que les endpoints retournent `status_code == 200`
|
||||||
|
- Tests complets pour tous les endpoints de l'API
|
||||||
|
|
||||||
|
### 2. ✅ Workflow GitHub Actions ajouté
|
||||||
|
- Pipeline automatisé dans `.github/workflows/test.yml`
|
||||||
|
- Tests exécutés avec Docker Compose
|
||||||
|
- Intégration continue configurée
|
||||||
|
|
||||||
|
### 3. ✅ Simulation d'erreur et correction
|
||||||
|
- Test simulé créé pour valider la détection automatique
|
||||||
|
- Erreur détectée par la CI (pipeline rouge)
|
||||||
|
- Test corrigé pour obtenir un pipeline vert
|
||||||
|
|
||||||
|
## 🏗️ Architecture des tests
|
||||||
|
|
||||||
|
```
|
||||||
|
dockezr/
|
||||||
|
├── .github/workflows/test.yml # Pipeline CI/CD
|
||||||
|
├── backend/
|
||||||
|
│ ├── test_api.py # Tests Pytest
|
||||||
|
│ ├── requirements-test.txt # Dépendances tests
|
||||||
|
│ └── Dockerfile.test # Image Docker pour tests
|
||||||
|
├── docker-compose.yml # Configuration avec service test
|
||||||
|
├── test.sh # Script de test (Linux/Mac)
|
||||||
|
└── test.bat # Script de test (Windows)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Tests implémentés
|
||||||
|
|
||||||
|
| Test | Description | Status |
|
||||||
|
|------|-------------|--------|
|
||||||
|
| `test_health_endpoint` | Vérifie `/health` retourne 200 | ✅ |
|
||||||
|
| `test_rooms_endpoint` | Vérifie `/rooms` retourne 200 | ✅ |
|
||||||
|
| `test_reservations_endpoint` | Vérifie `/reservations` retourne 200 | ✅ |
|
||||||
|
| `test_root_endpoint` | Vérifie `/` retourne 200 | ✅ |
|
||||||
|
| `test_room_by_id` | Test récupération salle par ID | ✅ |
|
||||||
|
| `test_room_not_found` | Test ID inexistant (404) | ✅ |
|
||||||
|
| `test_reservations_by_date` | Test réservations par date | ✅ |
|
||||||
|
| `test_simulated_error` | Test simulé (corrigé) | ✅ |
|
||||||
|
| `test_api_response_time` | Test performance API | ✅ |
|
||||||
|
|
||||||
|
## 🚀 Commandes pour lancer les tests
|
||||||
|
|
||||||
|
### Tests locaux avec Docker
|
||||||
|
```bash
|
||||||
|
# Démarrer les services
|
||||||
|
docker-compose up -d db backend
|
||||||
|
|
||||||
|
# Lancer les tests
|
||||||
|
docker-compose --profile test up --build --abort-on-container-exit test
|
||||||
|
|
||||||
|
# Ou utiliser le script
|
||||||
|
./test.sh # Linux/Mac
|
||||||
|
test.bat # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tests avec couverture
|
||||||
|
```bash
|
||||||
|
docker-compose run --rm test pytest test_api.py --cov=. --cov-report=html
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Résultats des tests
|
||||||
|
|
||||||
|
```
|
||||||
|
============================= test session starts ==============================
|
||||||
|
platform linux -- Python 3.11.14, pytest-7.4.3, pluggy-1.6.0
|
||||||
|
collected 9 items
|
||||||
|
|
||||||
|
test_api.py::TestAPI::test_health_endpoint PASSED [ 11%]
|
||||||
|
test_api.py::TestAPI::test_rooms_endpoint PASSED [ 22%]
|
||||||
|
test_api.py::TestAPI::test_reservations_endpoint PASSED [ 33%]
|
||||||
|
test_api.py::TestAPI::test_root_endpoint PASSED [ 44%]
|
||||||
|
test_api.py::TestAPI::test_room_by_id PASSED [ 55%]
|
||||||
|
test_api.py::TestAPI::test_room_not_found PASSED [ 66%]
|
||||||
|
test_api.py::TestAPI::test_reservations_by_date PASSED [ 77%]
|
||||||
|
test_api.py::test_simulated_error PASSED [ 88%]
|
||||||
|
test_api.py::test_api_response_time PASSED [100%]
|
||||||
|
|
||||||
|
============================== 9 passed in 0.06s ===============================
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Livrables
|
||||||
|
|
||||||
|
- ✅ **Fichier `test_api.py`** - Tests Pytest complets
|
||||||
|
- ✅ **Pipeline "vert"** - Tous les tests passent
|
||||||
|
- ✅ **Workflow GitHub Actions** - CI/CD automatisée
|
||||||
|
- ✅ **Configuration Docker** - Tests intégrés dans Docker
|
||||||
|
|
||||||
|
## 🔧 Configuration technique
|
||||||
|
|
||||||
|
### Variables d'environnement
|
||||||
|
- `API_BASE_URL`: URL de l'API (défaut: http://localhost:8000)
|
||||||
|
- `DATABASE_URL`: URL de la base de données PostgreSQL
|
||||||
|
|
||||||
|
### Dépendances de test
|
||||||
|
- `pytest==7.4.3` - Framework de test
|
||||||
|
- `requests==2.31.0` - Client HTTP
|
||||||
|
- `pytest-cov==4.1.0` - Couverture de code
|
||||||
|
- `pytest-timeout==2.1.0` - Timeout des tests
|
||||||
|
|
||||||
|
## 🎉 TP4 terminé avec succès !
|
||||||
|
|
||||||
|
Le système de tests automatisés est maintenant opérationnel et prêt pour la production.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Dockerfile pour les tests
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Définir le répertoire de travail
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copier les fichiers requirements
|
||||||
|
COPY requirements.txt requirements-test.txt ./
|
||||||
|
|
||||||
|
# Installer les dépendances
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r requirements-test.txt
|
||||||
|
|
||||||
|
# Copier le code source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Exposer le port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Commande par défaut pour les tests
|
||||||
|
CMD ["pytest", "test_api.py", "-v", "--tb=short"]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Requirements pour les tests
|
||||||
|
pytest==7.4.3
|
||||||
|
requests==2.31.0
|
||||||
|
pytest-cov==4.1.0
|
||||||
|
pytest-timeout==2.1.0
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000")
|
||||||
|
|
||||||
|
class TestAPI:
|
||||||
|
"""Tests pour l'API de réservation de salles Expernet"""
|
||||||
|
|
||||||
|
def test_health_endpoint(self):
|
||||||
|
"""Test que l'endpoint /health retourne 200"""
|
||||||
|
response = requests.get(f"{BASE_URL}/health")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()["status"] == "healthy"
|
||||||
|
|
||||||
|
def test_rooms_endpoint(self):
|
||||||
|
"""Test que l'endpoint /rooms retourne 200"""
|
||||||
|
response = requests.get(f"{BASE_URL}/rooms")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert isinstance(response.json(), list)
|
||||||
|
# Vérifier qu'il y a au moins une salle
|
||||||
|
rooms = response.json()
|
||||||
|
assert len(rooms) > 0
|
||||||
|
# Vérifier la structure d'une salle
|
||||||
|
if rooms:
|
||||||
|
room = rooms[0]
|
||||||
|
assert "id" in room
|
||||||
|
assert "name" in room
|
||||||
|
assert "capacity" in room
|
||||||
|
|
||||||
|
def test_reservations_endpoint(self):
|
||||||
|
"""Test que l'endpoint /reservations retourne 200"""
|
||||||
|
response = requests.get(f"{BASE_URL}/reservations")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert isinstance(response.json(), list)
|
||||||
|
|
||||||
|
def test_root_endpoint(self):
|
||||||
|
"""Test que l'endpoint racine retourne 200"""
|
||||||
|
response = requests.get(f"{BASE_URL}/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "message" in data
|
||||||
|
assert "status" in data
|
||||||
|
|
||||||
|
def test_room_by_id(self):
|
||||||
|
"""Test récupération d'une salle par ID"""
|
||||||
|
# D'abord récupérer la liste des salles
|
||||||
|
rooms_response = requests.get(f"{BASE_URL}/rooms")
|
||||||
|
assert rooms_response.status_code == 200
|
||||||
|
rooms = rooms_response.json()
|
||||||
|
|
||||||
|
if rooms:
|
||||||
|
room_id = rooms[0]["id"]
|
||||||
|
response = requests.get(f"{BASE_URL}/rooms/{room_id}")
|
||||||
|
assert response.status_code == 200
|
||||||
|
room = response.json()
|
||||||
|
assert "id" in room
|
||||||
|
assert "name" in room
|
||||||
|
assert "capacity" in room
|
||||||
|
assert room["id"] == room_id
|
||||||
|
|
||||||
|
def test_room_not_found(self):
|
||||||
|
"""Test qu'un ID de salle inexistant retourne 404"""
|
||||||
|
response = requests.get(f"{BASE_URL}/rooms/99999")
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
def test_reservations_by_date(self):
|
||||||
|
"""Test récupération des réservations par date"""
|
||||||
|
# Utiliser une date future
|
||||||
|
test_date = "2025-12-31"
|
||||||
|
response = requests.get(f"{BASE_URL}/reservations/date/{test_date}")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert isinstance(response.json(), list)
|
||||||
|
|
||||||
|
# Test pour simuler une erreur (maintenant corrigé)
|
||||||
|
def test_simulated_error():
|
||||||
|
"""Test qui était simulé pour valider la détection automatique - maintenant corrigé"""
|
||||||
|
# Test corrigé pour valider que la CI fonctionne
|
||||||
|
assert 1 == 1, "Test corrigé - la CI fonctionne correctement"
|
||||||
|
|
||||||
|
# Test de performance basique
|
||||||
|
def test_api_response_time():
|
||||||
|
"""Test que l'API répond dans un temps raisonnable"""
|
||||||
|
start_time = time.time()
|
||||||
|
response = requests.get(f"{BASE_URL}/health")
|
||||||
|
end_time = time.time()
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert (end_time - start_time) < 5.0 # Moins de 5 secondes
|
||||||
@@ -62,6 +62,30 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- dockezr_network
|
- dockezr_network
|
||||||
|
|
||||||
|
# Service de test
|
||||||
|
test:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile.test
|
||||||
|
container_name: dockezr_test
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql://user:password@db:5432/dockezr
|
||||||
|
API_BASE_URL: http://backend:8000
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
backend:
|
||||||
|
condition: service_started
|
||||||
|
networks:
|
||||||
|
- dockezr_network
|
||||||
|
command: >
|
||||||
|
sh -c "echo '⏳ Attente du backend...' &&
|
||||||
|
sleep 15 &&
|
||||||
|
echo '🧪 Lancement des tests...' &&
|
||||||
|
pytest test_api.py -v --tb=short"
|
||||||
|
profiles:
|
||||||
|
- test
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dockezr_network:
|
dockezr_network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Requirements pour les tests
|
||||||
|
pytest==7.4.3
|
||||||
|
requests==2.31.0
|
||||||
|
pytest-cov==4.1.0
|
||||||
|
pytest-timeout==2.1.0
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
echo TP4 - Tests automatises avec Docker
|
||||||
|
echo ======================================
|
||||||
|
|
||||||
|
echo Demarrage des tests...
|
||||||
|
|
||||||
|
echo Demarrage des services (DB + Backend)...
|
||||||
|
docker-compose up -d db backend
|
||||||
|
|
||||||
|
echo Attente des services (15 secondes)...
|
||||||
|
timeout /t 15 /nobreak > nul
|
||||||
|
|
||||||
|
echo Verification du backend...
|
||||||
|
curl -s http://localhost:8000/health > nul
|
||||||
|
if %errorlevel% equ 0 (
|
||||||
|
echo [OK] Backend operationnel
|
||||||
|
) else (
|
||||||
|
echo [ERREUR] Backend non accessible
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Lancement des tests...
|
||||||
|
docker-compose --profile test up --build --abort-on-container-exit test
|
||||||
|
|
||||||
|
echo Resultats des tests:
|
||||||
|
docker-compose --profile test logs test
|
||||||
|
|
||||||
|
echo Nettoyage...
|
||||||
|
docker-compose --profile test down -v
|
||||||
|
|
||||||
|
echo [OK] Tests termines !
|
||||||
|
echo TP4 termine avec succes !
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000")
|
||||||
|
|
||||||
|
class TestAPI:
|
||||||
|
"""Tests pour l'API de réservation de salles Expernet"""
|
||||||
|
|
||||||
|
def test_health_endpoint(self):
|
||||||
|
"""Test que l'endpoint /health retourne 200"""
|
||||||
|
response = requests.get(f"{BASE_URL}/health")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()["status"] == "healthy"
|
||||||
|
|
||||||
|
def test_rooms_endpoint(self):
|
||||||
|
"""Test que l'endpoint /rooms retourne 200"""
|
||||||
|
response = requests.get(f"{BASE_URL}/rooms")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert isinstance(response.json(), list)
|
||||||
|
# Vérifier qu'il y a au moins une salle
|
||||||
|
rooms = response.json()
|
||||||
|
assert len(rooms) > 0
|
||||||
|
# Vérifier la structure d'une salle
|
||||||
|
if rooms:
|
||||||
|
room = rooms[0]
|
||||||
|
assert "id" in room
|
||||||
|
assert "name" in room
|
||||||
|
assert "capacity" in room
|
||||||
|
|
||||||
|
def test_reservations_endpoint(self):
|
||||||
|
"""Test que l'endpoint /reservations retourne 200"""
|
||||||
|
response = requests.get(f"{BASE_URL}/reservations")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert isinstance(response.json(), list)
|
||||||
|
|
||||||
|
def test_root_endpoint(self):
|
||||||
|
"""Test que l'endpoint racine retourne 200"""
|
||||||
|
response = requests.get(f"{BASE_URL}/")
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "message" in data
|
||||||
|
assert "status" in data
|
||||||
|
|
||||||
|
def test_room_by_id(self):
|
||||||
|
"""Test récupération d'une salle par ID"""
|
||||||
|
# D'abord récupérer la liste des salles
|
||||||
|
rooms_response = requests.get(f"{BASE_URL}/rooms")
|
||||||
|
assert rooms_response.status_code == 200
|
||||||
|
rooms = rooms_response.json()
|
||||||
|
|
||||||
|
if rooms:
|
||||||
|
room_id = rooms[0]["id"]
|
||||||
|
response = requests.get(f"{BASE_URL}/rooms/{room_id}")
|
||||||
|
assert response.status_code == 200
|
||||||
|
room = response.json()
|
||||||
|
assert "id" in room
|
||||||
|
assert "name" in room
|
||||||
|
assert "capacity" in room
|
||||||
|
assert room["id"] == room_id
|
||||||
|
|
||||||
|
def test_room_not_found(self):
|
||||||
|
"""Test qu'un ID de salle inexistant retourne 404"""
|
||||||
|
response = requests.get(f"{BASE_URL}/rooms/99999")
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
def test_reservations_by_date(self):
|
||||||
|
"""Test récupération des réservations par date"""
|
||||||
|
# Utiliser une date future
|
||||||
|
test_date = "2025-12-31"
|
||||||
|
response = requests.get(f"{BASE_URL}/reservations/date/{test_date}")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert isinstance(response.json(), list)
|
||||||
|
|
||||||
|
# Test pour simuler une erreur (à corriger ensuite)
|
||||||
|
def test_simulated_error():
|
||||||
|
"""Test qui va échouer pour valider la détection automatique"""
|
||||||
|
# Cette ligne va causer une erreur intentionnellement
|
||||||
|
assert 1 == 2, "Erreur simulée pour tester la CI - à corriger après validation"
|
||||||
|
|
||||||
|
# Test de performance basique
|
||||||
|
def test_api_response_time():
|
||||||
|
"""Test que l'API répond dans un temps raisonnable"""
|
||||||
|
start_time = time.time()
|
||||||
|
response = requests.get(f"{BASE_URL}/health")
|
||||||
|
end_time = time.time()
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert (end_time - start_time) < 5.0 # Moins de 5 secondes
|
||||||
Reference in New Issue
Block a user