diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b907a10 --- /dev/null +++ b/.github/workflows/test.yml @@ -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 diff --git a/README_TP4.md b/README_TP4.md new file mode 100644 index 0000000..245e402 --- /dev/null +++ b/README_TP4.md @@ -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. diff --git a/backend/Dockerfile.test b/backend/Dockerfile.test new file mode 100644 index 0000000..e75089d --- /dev/null +++ b/backend/Dockerfile.test @@ -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"] diff --git a/backend/requirements-test.txt b/backend/requirements-test.txt new file mode 100644 index 0000000..9a043ef --- /dev/null +++ b/backend/requirements-test.txt @@ -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 diff --git a/backend/test_api.py b/backend/test_api.py new file mode 100644 index 0000000..b51b3ef --- /dev/null +++ b/backend/test_api.py @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 2519640..81b16bd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,6 +62,30 @@ services: networks: - 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: dockezr_network: driver: bridge diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..9a043ef --- /dev/null +++ b/requirements-test.txt @@ -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 diff --git a/test.bat b/test.bat new file mode 100644 index 0000000..238d079 --- /dev/null +++ b/test.bat @@ -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 ! diff --git a/test_api.py b/test_api.py new file mode 100644 index 0000000..4f0fb24 --- /dev/null +++ b/test_api.py @@ -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