Résolue

Problème de login avec Django et Pytest

# Django # Tests unitaires # Python

problème de login dans pytest

Bonsoir tout le monde, excellente année à vous toutes et tous.

Je lutte depuis des heures avec des tests de mon application sous django, sous pytest, qui nécessitent qu'un utilisateur créé au moyen d'une fixture soit connecté.
Le mot de passe est bien hashé, l'utilisateur est créé mais rien n'y fait, impossible de faire fonctionner les tests qui nécessitent le login!! Déjà au niveau du client créé le login ne fonctionne pas, voici ma fixture pour créer l'utilisateur et celle pour le client où déjà ça bloque:

@pytest.fixture
def user(db):
    return Patient.objects.create_user(
        email="[email protected]",
        password="oyeoye",
        nom="Dupont",
        prenom="Jean",
        adresse="123 Rue Exemple",
        genre="M",
    )

J'ai effectué des tests pour vérifier le hachage et le statut is_active, ils sont ok.

@pytest.fixture
def client(user):
    client = Client()
    logged_in = client.login(email=user.email, password="oyeoye")
    assert logged_in, "client not logged_in"
    return client

Merci pour votre aide.
Ali

Gabriel Trouvé

Mentor

Salut :)

Déjà, pour commencer tu peux faire du force login (imaginons une fixture user_1). Si tu ne test pas le login en soit, c'est bien mieux de faire comme ça.

import pytest
from django.test import Client
from django.urls import reverse


def test_stories_view(client: Client,  user_1):
    client.force_login(user_1)
    response = client.get(reverse("rpg:stories-list"))

Sinon pour ton cas précis essaye ça stp :

@pytest.fixture
def client(user):
    client = Client()
    logged_in = client.login(username=user.email, password="oyeoye")
    assert logged_in, "client not logged_in"
    return client

Hey Gabriel, merci pour ta réponse. Effectivement j'ai utilisé force_login pour pas mal de tests mais il subsiste un test de connexion malheureusement et donc j'ai besoin de ne pas "forcer" cette connexion.

J'avais également remplacé email par username car j'utilise un customuser basé sur email et password mais ça ne fonctionne pas, la connexion via le client échoue toujours. J'ai le bon backend d'authentification dans mes settings.
Voici le retour:

   @pytest.fixture
    def client(user):
        client = Client()
        logged_in = client.login(username=user.email, password="<password>")
>       assert logged_in, "client not logged_in"
E       AssertionError: client not logged_in
E       assert False

soignemoiwebsite/tests/test_vues.py:40: AssertionError
========================================================================================================= short test summary info =========================================================================================================
ERROR soignemoiwebsite/tests/test_vues.py::test_sejour_view - AssertionError: client not logged_in

et le test de connexion justement qui échoue (j'ai enlevé la partie assert logged_in dans la fixture du Client). Est-il correct?

 def test_login_user(client):
        url = reverse('soignemoiwebsite:login')
        response = client.post(url, {'username': '[email protected]', 'password': 'oyeoye'})
>       assert response.status_code == 302, "login did not redirect as expected"
E       AssertionError: login did not redirect as expected
E       assert 200 == 302
E        +  where 200 = <httpresponse "text="" charset='utf-8"' html;="" status_code="200,">.status_code

soignemoiwebsite/tests/test_vues.py:133: AssertionError
========================================================================================================= short test summary info =========================================================================================================
FAILED soignemoiwebsite/tests/test_vues.py::test_login_user - AssertionError: login did not redirect as expected

Merci pour ton aide.

PS: j'ai pu lire que CSRF pouvait aider, ou bien ajouter transactional_db dans certains cas mais ça va au-delà de mes compétences actuelles...

Gabriel Trouvé

Mentor

Tu n'utilises pas l'user dans ta fixture je pense :

def test_login_user(client, user): 
    url = reverse('soignemoiwebsite:login')
    response = client.post(url, {'username': user.email, 'password': 'oyeoye'})
    assert response.status_code == 302

Je pense que ça devrait aller là ?

non ça ne fonctionne toujours pas,
`def test_login_user(client, user):
url = reverse('soignemoiwebsite:login')
response = client.post(url, {'username': user.email, 'password': 'oyeoye'})

  assert response.status_code == 302, "login did not redirect as expected"

E AssertionError: login did not redirect as expected
E assert 200 == 302
E + where 200 = <httpresponse "text="" charset='utf-8"' html;="" status_code="200,">.status_code`</httpresponse>

En fait bien avant d'en arriver à ce test, dès la définition de la fixture Client on a le problème puisque la connexion n'est pas établie (assert logged_in, "client not logged_in)...

Bon Gabriel je crois avoir trouvé après d'innombrables tests: il y avait un problème de hashage du password dû au fait que dans mon modèle CustomUser, ma surcharge de la méthode save était mal implémentée : pour que tous mes mots de passe soient hashés quand ils sont définis en dehors de la méthode create_user (dans interface admin ou en appelant directement la classe Patient) j'ai implémenté un hashage mais de la mauvaise façon! Ceci provoquait un double hashage dans l'environnement de test et cela échouait.

# on surcharge save pour s'assurer du hachage du mot de passe en toute circonstance
    def save(self, *args, **kwargs):
        # Si le mot de passe n'est pas chiffré, on le chiffre.
        if self.pk is None or not self.password.startswith('pbkdf2_'):
            self.set_password(self.password)  # la méthode set_password est liée à AbstractBaseUser donc appelable ici.
        super().save(*args, **kwargs)

J'ai remplacé or par and et miracle tout fonctionne

Gabriel Trouvé

Mentor

Ah normalement tu n'as pas besoin de toucher au hash. Personellement mes users je ne touche jamais à ça.

Pourquoi tu voulais toucher au hash à la base ?

parce que je souhaitais que tous les mots de passe soient hashés, notamment quand je crée des utilisateurs depuis l'interface admin ou quand je crée un utilisateur en appelant sa classe ali = Patient(email=..., ...., password = 'password_en_clair)

Preneur dune meilleure facon de faire ;-)

Gabriel Trouvé

Mentor

ok il y a plus simple . Je te fais une reponse complète ce soir ou demain matin :)

Gabriel Trouvé

Mentor

Alors, imagine que l'on créé un CustomUser, je mets un exemple très simple et basique, avec son manager :

class CustomManager(BaseUserManager):
    def create_user(self, username, email, password, **kwargs):
        if not username:
            raise ValueError("Username must be set")
        if not email:
            raise ValueError("Email must be set")

        user = self.model(
            username=username, email=self.normalize_email(email), **kwargs
        )
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, username, email, password, **kwargs):
        kwargs.setdefault("is_staff", True)
        kwargs.setdefault("is_superuser", True)

        if kwargs.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")
        if kwargs.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True.")
        return self.create_user(username, email, password, **kwargs)


class CustomUser(AbstractUser):
    email = models.EmailField(unique=True)

    REQUIRED_FIELDS = ["email"]
    objects = CustomManager()

Là pas trop de customisation mais c'est pour l'example. C'est bien de se faire un manager custom en même temps que l'user custom, comme ça si tu rajoutes des champs dans le custom user, tu les gères dans ton manager.

Maintenant, pas besoin de toucher à save, car tu vas en réaliser utiliser create_user qui va s'occuper de hash le mot de passe :

user = CustomUser.objects.create_user(
    username="john_doe",
    email="[email protected]",
    password="secret_pass"
)

Et pour l'administration je te laisse regarder j'en parle ici :
https://youtu.be/yLy8EfalCxQ

J'espère t'avoir aidé :)

A bientôt !

salut Gabriel, merci pour ton aide, la vidéo sur la gestion admin va beaucoup m'aider, d'autant que je ne crois pas me rappeler que l'on aborde UserAdmin dans la formation.
bonne année encore!

Inscris-toi

(c'est gratuit !)

Inscris-toi

Tu dois créer un compte pour participer aux discussions.

Créer un compte

Rechercher sur le site

Formulaire de contact

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.