From eeaba7ac062a3aa41dfaf9f56107d5f9a8662660 Mon Sep 17 00:00:00 2001 From: "Aurore \"Lugrim\" Poirier" Date: Sun, 8 Oct 2023 01:42:24 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A8=20Linting=20score=20is=20getting?= =?UTF-8?q?=20dangerously=20close=209?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed a few warnings, mostly by adding docstrings --- insalan/user/models.py | 4 ++ insalan/user/tests.py | 85 ++++++++++++++++++++++++++++++++++++++++++ insalan/user/views.py | 38 ++++++++++++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/insalan/user/models.py b/insalan/user/models.py index 7c8bbb8a..984725a2 100644 --- a/insalan/user/models.py +++ b/insalan/user/models.py @@ -47,6 +47,9 @@ def create_user( return user def create_superuser(self, email, username, password, **extra_fields): + """ + Check that all required fields are present and create a superuser + """ if password is None: raise TypeError(_("Les superutilisateur·rices requièrent un mot de passe")) user = self.create_user(email, username, password, **extra_fields) @@ -132,6 +135,7 @@ def send_email_confirmation(user_object: User): fail_silently=False, ) + @staticmethod def send_password_reset(user_object: User): """ Send a password reset token. diff --git a/insalan/user/tests.py b/insalan/user/tests.py index a814afc0..24e4cd21 100644 --- a/insalan/user/tests.py +++ b/insalan/user/tests.py @@ -10,7 +10,14 @@ class UserTestCase(TestCase): + """ + Tests of the User model + """ + def setUp(self): + """ + Create some base users to do operations on + """ u: User = User.objects.create_user( username="staffplayer", email="staff@insalan.fr", @@ -43,6 +50,9 @@ def setUp(self): ) def test_get_existing_full_user(self): + """ + Test getting all the fields of an already created user + """ u: User = User.objects.get(username="randomplayer") self.assertEquals(u.get_username(), "randomplayer") self.assertEquals(u.get_short_name(), "Random") @@ -54,6 +64,9 @@ def test_get_existing_full_user(self): self.assertFalse(u.is_staff) def test_get_existing_minimal_user(self): + """ + Test getting all the fields of an user created with only the required fields + """ u: User = User.objects.get(username="anotherplayer") self.assertEquals(u.get_username(), "anotherplayer") self.assertEquals(u.get_short_name(), "") @@ -65,14 +78,24 @@ def test_get_existing_minimal_user(self): self.assertFalse(u.is_staff) def test_get_non_existing_user(self): + """ + Test that getting an user which does not exist throws an `User.DoesNotExist` error + """ with self.assertRaises(User.DoesNotExist): User.objects.get(username="idontexist") class UserEndToEndTestCase(TestCase): + """ + Test cases of the API endpoints and workflows related to user model + """ + client: APIClient def setUp(self): + """ + Create a player to test getters + """ self.client = APIClient() User.objects.create_user( username="randomplayer", @@ -85,6 +108,10 @@ def setUp(self): ) def test_register_invalid_data(self): + """ + Test trying to register a few invalid users + """ + def send_invalid_data(data): request = self.client.post("/v1/user/register/", data, format="json") self.assertEquals(request.status_code, 400) @@ -95,7 +122,14 @@ def send_invalid_data(data): send_invalid_data({"username": "newuser", "email": "email@example.com"}) def test_register_valid_account(self): + """ + Test registering valid users + """ + def send_valid_data(data, check_fields=[]): + """ + Helper function that will request a register and check its output + """ request = self.client.post("/v1/user/register/", data, format="json") self.assertEquals(request.status_code, 201) @@ -144,6 +178,10 @@ def send_valid_data(data, check_fields=[]): ) def test_register_read_only_fields(self): + """ + Test that the read-only register fields are indeed read-only + """ + def send_valid_data(data, check_fields=[]): request = self.client.post("/v1/user/register/", data, format="json") @@ -197,6 +235,9 @@ def send_valid_data(data, check_fields=[]): ) def test_register_email_is_sent(self): + """ + Test that an email is sent upon registration + """ data = { "username": "ILoveMail", "password": "1111qwer!", @@ -212,6 +253,9 @@ def test_register_email_is_sent(self): self.assertEqual(mail.outbox[0].to, ["postman@example.com"]) def test_register_can_confirm_email(self): + """ + Test that an user can confirm their email with the link they received + """ data = { "username": "ILoveEmail", "password": "1111qwer!", @@ -241,6 +285,9 @@ def test_register_can_confirm_email(self): self.assertTrue(User.objects.get(username=data["username"]).email_active) def test_can_confirm_email_only_once(self): + """ + Test that an user can confirm their email only once + """ data = { "username": "ILoveEmail", "password": "1111qwer!", @@ -266,6 +313,9 @@ def test_can_confirm_email_only_once(self): self.assertEquals(request.status_code, 400) def test_confirmation_email_is_token_checked(self): + """ + Test that the token sent to an user for their email confirmation is really checked + """ data = { "username": "ILoveEmail", "password": "1111qwer!", @@ -292,6 +342,10 @@ def test_confirmation_email_is_token_checked(self): self.assertEquals(request.status_code, 400) def test_login_invalid_account(self): + """ + Try to login with invalid requests + """ + def send_valid_data(data): request = self.client.post("/v1/user/login/", data, format="json") @@ -309,6 +363,9 @@ def send_valid_data(data): ) def test_login_not_active_account(self): + """ + Test trying to login to an account which email is not already activated + """ User.objects.create_user( username="newplayer", email="test@test.com", password="1111qwer!" ) @@ -326,6 +383,9 @@ def send_valid_data(data): ) def test_login_account(self): + """ + Test that when everything is ok, an user is able to login + """ User.objects.create_user( username="newplayer", email="test@test.com", @@ -347,6 +407,10 @@ def send_valid_data(data): ) def can_resend_confirmation_email(self): + """ + Test that an user can request another confirmation email when + requesting the right route + """ data = { "username": "ILoveEmail", "password": "1111qwer!", @@ -373,6 +437,10 @@ def can_resend_confirmation_email(self): self.assertEqual(mail.outbox, 3) def cant_resend_confirmation_if_already_valid(self): + """ + Test that an user cannot resend a confirmation email if they already + confirmed their email + """ User.objects.create_user( username="newplayer", email="test@example.com", @@ -387,6 +455,10 @@ def cant_resend_confirmation_if_already_valid(self): self.assertEqual(request.status_code, 400) def cant_resend_confirmation_if_nonexisting_user(self): + """ + Test that we cannot resend a confirmation email for a non existing user + without crashing the server + """ request = self.client.post( "/v1/user/resend-email/", {"username": "IDontExistLol"}, format="json" ) @@ -394,11 +466,18 @@ def cant_resend_confirmation_if_nonexisting_user(self): self.assertEqual(request.status_code, 400) def dont_crash_resend_confirmation_if_empty(self): + """ + Test that server doesn't crash if ask to resend an email without any + valid data in request + """ request = self.client.post("/v1/user/resend-email/", {}, format="json") self.assertEqual(request.status_code, 400) def test_can_reset_password(self): + """ + Test that an user can reset their password (full workflow) + """ data = { "email": "randomplayer@example.com", } @@ -446,6 +525,9 @@ def test_can_reset_password(self): self.assertEquals(request.status_code, 404) def test_can_reset_password_only_once(self): + """ + Test that an user can reset their password only once with a token + """ data = { "email": "randomplayer@example.com", } @@ -486,6 +568,9 @@ def test_can_reset_password_only_once(self): self.assertEquals(request.status_code, 400) def test_password_reset_is_token_checked(self): + """ + Test that the password reset token is actually checked + """ data = { "email": "randomplayer@example.com", } diff --git a/insalan/user/views.py b/insalan/user/views.py index a93c13c7..0c0b60c8 100644 --- a/insalan/user/views.py +++ b/insalan/user/views.py @@ -60,22 +60,37 @@ def get(self, request): # TODO: change permission class PermissionViewSet(generics.ListCreateAPIView): + """ + Django's `Permission` ViewSet to be able to add them to the admin panel + """ + queryset = Permission.objects.all().order_by("name") serializer_class = PermissionSerializer permission_classes = [permissions.IsAdminUser] class GroupViewSet(generics.ListCreateAPIView): + """ + Django's `Group` ViewSet to be able to add them to the admin panel + """ + queryset = Group.objects.all().order_by("name") serializer_class = GroupSerializer permission_classes = [permissions.IsAdminUser] class EmailConfirmView(APIView): + """ + Email confirmation user API Endpoint + """ + permissions_classes = [permissions.AllowAny] authentication_classes = [SessionAuthentication] def get(self, request, user=None, token=None): + """ + If requested with valid parameters, will validate an user's email + """ error_text = _("Utilisateur ou jeton invalide (ou adresse déjà confirmée)") if user and token: @@ -100,10 +115,18 @@ def get(self, request, user=None, token=None): class AskForPasswordReset(APIView): + """ + Asking for a password reset API Endpoint + """ + permissions_classes = [permissions.AllowAny] authentication_classes = [SessionAuthentication] def post(self, request): + """ + If requested with valid parameters, will send a password reset email to + an user given their email address + """ try: user_object: User = User.objects.get(email=request.data["email"]) UserMailer.send_password_reset(user_object) @@ -114,10 +137,17 @@ def post(self, request): class ResetPassword(APIView): + """ + Password Reset API Endpoint + """ + permissions_classes = [permissions.AllowAny] authentication_classes = [SessionAuthentication] def post(self, request): + """ + If requested with valid parameters, will reset an user password + """ data = request.data if not ( "user" in data @@ -170,13 +200,16 @@ def post(self, request): class ResendEmailConfirmView(APIView): """ - API endpoint to re-send + API endpoint to re-send a confirmation email """ permissions_classes = [permissions.AllowAny] authentication_classes = [SessionAuthentication] def post(self, request): + """ + If the user is found, will send again a confirmation email + """ error_text = _("Impossible de renvoyer le courriel de confirmation") username = request.data.get("username") @@ -250,5 +283,8 @@ class UserLogout(APIView): authentication_classes = [] def post(self, request): + """ + Will logout an user. + """ logout(request) return Response(status=status.HTTP_200_OK)