Introduction
Une correction peut être annulée accidentellement par une modification ultérieure. Un test de non-régression de sécurité transforme une exigence importante en une requête répétable et un résultat attendu.
Vous compléterez un petit modèle Python pour un point d'accès DeskLite protégé. Vous vérifierez ensuite les tests eux-mêmes contre deux services locaux volontairement défectueux. Aucune installation de framework de test ni expérience préalable d'écriture de tests n'est nécessaire.
Interroger le point d'accès protégé
Dans cette étape, vous identifierez le comportement que les tests doivent protéger.
Le service correct préparé utilise le port local 8080. Son point d'accès /tickets renvoie le ticket d'origine pour un jeton valide, et 401 avec un objet d'erreur pour un jeton absent ou erroné. L'authentification et la réponse métier sont deux exigences distinctes : le statut 200 seul ne prouve pas que le bon ticket a été renvoyé.
cd /home/labex/project/ticket-tests
La valeur visible course_test_fixture_token de ce lab est un jeton de test public et délibérément fictif. Elle n'est acceptée que par ces services pédagogiques isolés et n'a aucun pouvoir d'authentification externe. Elle ne remplace pas les identifiants applicatifs privés enseignés précédemment.
Utilisez la même option d'en-tête de requête -H de curl que dans le lab sur les jetons :
curl -i -H 'Authorization: Bearer course_test_fixture_token' http://127.0.0.1:8080/tickets
La réponse attendue est HTTP 200 et {"tickets": [{"id": 1, "title": "Printer offline"}]}. -i inclut les en-têtes de la réponse HTTP, notamment son statut.
Omettez maintenant entièrement l'en-tête :
curl -i http://127.0.0.1:8080/tickets
La réponse attendue est HTTP 401 et {"error": "unauthorized"}. Ce rejet constitue le comportement requis, pas un exercice défectueux.
Lire le petit modèle de test
Dans cette étape, vous relierez chaque ligne d'un test à une partie d'une véritable requête.
cat test_security.py
Le fichier préparé contient :
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
from api import request charge un utilitaire de requête préparé. def test_valid_token(): définit une fonction de test nommée. Les lignes indentées appartiennent à cette fonction, comme dans la fonction de recherche du lab précédent.
request(path, headers) envoie une véritable requête HTTP et renvoie un dictionnaire Python : une association de noms à des valeurs. Les associations d'en-têtes utilisent aussi des accolades. {"Authorization": "Bearer ..."} envoie un en-tête, tandis que {} n'en envoie aucun. response["status"] lit l'entrée de statut ; response["body"] lit le corps JSON analysé.
Une assertion exprime une attente. En Python, == compare deux valeurs ; il diffère du simple = utilisé pour affecter une valeur. Un assert faux déclenche AssertionError, ce qui fait échouer le test. La dernière ligne compare l'objet métier attendu complet, y compris le titre du ticket.
L'utilitaire choisit un service préparé à partir de l'argument cible du programme de lancement. Vous modifierez les tests, pas l'utilitaire ni le service.
Repérer la lacune d'un test qui réussit
Dans cette étape, vous verrez pourquoi la réussite d'un test positif est insuffisante.
Exécutez le programme de lancement de tests préparé. Son argument good sélectionne le service correct :
python3 run_tests.py good
Le résultat attendu est test_valid_token avec "result": "PASS". Le programme collecte les fonctions dont les noms commencent par test_ et appelle chacune d'elles. Il renvoie le code de sortie 0 lorsque toutes réussissent, 1 en cas d'échec d'assertion et 2 en cas d'erreur de test ou d'exécution.
Deux autres services sont volontairement défectueux : missing-auth accepte les requêtes sans jeton ; wrong-auth accepte un jeton erroné. Leur réponse positive fonctionne toujours. Essayez les deux :
python3 run_tests.py missing-auth
python3 run_tests.py wrong-auth
Les deux réussissent encore l'unique test existant. Ce résultat révèle une lacune de couverture. Le test n'a jamais envoyé de requête devant être rejetée : il ne pouvait donc remarquer aucun des deux contournements.
Ajouter l'exigence concernant le jeton absent
Dans cette étape, vous ajouterez un test qui attend le rejet d'une véritable requête sans authentification.
Ouvrez le fichier de test :
nano test_security.py
Conservez le test positif et ajoutez la nouvelle fonction en dessous. Le fichier complet doit être :
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
def test_missing_token():
response = request("/tickets", {})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
L'association vide {} signifie qu'aucun en-tête de requête n'est envoyé. L'assertion de statut vérifie le rejet, tandis que l'assertion du corps garantit que la réponse d'erreur ne contient aucune donnée de ticket. Conservez l'indentation de quatre espaces. Enregistrez avec Ctrl+O, Enter, puis quittez avec Ctrl+X.
python3 run_tests.py good
Les deux tests doivent réussir. Utilisez maintenant le service volontairement défectueux qui accepte l'absence d'en-tête :
python3 run_tests.py missing-auth
Le test test_missing_token doit afficher "result": "FAIL" et "error": "AssertionError". Ce code de sortie intentionnel 1 est une preuve utile : le nouveau test détecte que le service accepte une requête qu'il devrait rejeter. Il diffère d'une erreur d'import ou d'une erreur de connexion au serveur.
Ajouter l'exigence concernant le jeton erroné
Dans cette étape, vous couvrirez le cas d'un identifiant présent mais invalide.
Un en-tête peut exister sans être valide. Tester uniquement un en-tête absent laisserait passer un service qui accepte n'importe quel jeton non vide. Ouvrez le même fichier et ajoutez la troisième fonction en conservant les deux premières :
nano test_security.py
Utilisez ce fichier complet :
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
def test_missing_token():
response = request("/tickets", {})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
def test_wrong_token():
response = request("/tickets", {"Authorization": "Bearer wrong_fixture_token"})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
wrong_fixture_token est délibérément refusé par le service correct. Enregistrez et quittez avec Ctrl+O, Enter, Ctrl+X.
python3 run_tests.py good
Les trois tests doivent réussir. Testez ensuite la régression concernant l'en-tête erroné :
python3 run_tests.py wrong-auth
Seul test_wrong_token doit échouer avec AssertionError. Ne remplacez pas 401 par 200 pour faire réussir ce service défectueux ; cela supprimerait l'exigence de sécurité.
Valider les deux détections de régression
Dans cette étape, vous vérifierez l'ensemble complet des tests contre les trois implémentations.
python3 run_tests.py good
python3 run_tests.py missing-auth
python3 run_tests.py wrong-auth
Lisez les résultats individuels. Le service correct doit réussir les trois tests. La régression concernant l'absence d'en-tête doit faire échouer uniquement test_missing_token, et celle concernant l'en-tête erroné doit faire échouer uniquement test_wrong_token. Le test métier avec jeton valide doit continuer à réussir partout.
Ces exécutions comparatives en échec sont intentionnelles. Une erreur de syntaxe, une erreur de connexion ou un échec inconditionnel ne démontre pas une détection. La vérification du cours confirme aussi que les tests ont envoyé les trois véritables requêtes HTTP à chaque service d'origine.
Ces comparaisons couvrent les deux régressions connues. Elles ne prouvent pas toutes les propriétés possibles d'authentification ou d'autorisation.
Terminer par une exécution réussie sur le service correct
Dans cette étape, vous terminerez avec une commande de test répétable pour l'implémentation prévue.
cat test_security.py
Confirmez que le test positif d'origine et les deux tests de rejet sont conservés. Leurs noms décrivent les trois exigences indépendantes.
python3 run_tests.py good
Les trois résultats doivent être PASS. Un futur contrôle de livraison pourra exécuter cette même commande sur le service prévu. Les services volontairement défectueux restent disponibles comme témoins pédagogiques ; ils ne constituent pas l'implémentation à livrer. Vous avez protégé un comportement réel au lieu de remettre une déclaration écrite sur la couverture des tests.
Résumé
Vous avez relié des exigences d'authentification explicites à trois véritables tests HTTP. L'implémentation correcte réussit ; chaque contournement préparé est détecté par l'assertion de rejet correspondante. Cette comparaison apporte la preuve que les tests protègent le comportement au lieu de simplement s'exécuter sans erreur.



