diff --git a/MisplaceAI/pytest.ini b/MisplaceAI/pytest.ini index 78635f9ce5516f17c472cc5b49367078718c0738..dd5b8c75d03b00e3cecd8fe7a80972d4c491f3a2 100644 --- a/MisplaceAI/pytest.ini +++ b/MisplaceAI/pytest.ini @@ -3,5 +3,6 @@ DJANGO_SETTINGS_MODULE = MisplaceAI.settings addopts = -v --ignore=MisplaceAI/models testpaths = rules/tests + user_dashboard/tests filterwarnings = ignore::django.utils.deprecation.RemovedInDjango41Warning diff --git a/MisplaceAI/user_dashboard/serializers.py b/MisplaceAI/user_dashboard/serializers.py index 81d06a74b81edf9ce641c9c177888b89c2c368e7..9e722ce3809178ac638cb84f8c71ea1da6db397b 100644 --- a/MisplaceAI/user_dashboard/serializers.py +++ b/MisplaceAI/user_dashboard/serializers.py @@ -8,6 +8,9 @@ from django.contrib.auth.models import User from rest_framework import serializers +from django.contrib.auth.password_validation import validate_password +from django.core.exceptions import ValidationError + class UserSerializer(serializers.ModelSerializer): """ @@ -42,6 +45,11 @@ class UserUpdateEmailSerializer(serializers.Serializer): """ email = serializers.EmailField() password = serializers.CharField(write_only=True) + + def validate_email(self, value): + if User.objects.filter(email=value).exists(): + raise serializers.ValidationError("This email is already in use.") + return value class UserUpdateUsernameSerializer(serializers.Serializer): """ @@ -50,6 +58,14 @@ class UserUpdateUsernameSerializer(serializers.Serializer): username = serializers.CharField(max_length=150) password = serializers.CharField(write_only=True) + def validate_username(self, value): + """ + Check that the username is at least 3 characters long. + """ + if len(value) < 3: + raise serializers.ValidationError("Username must be at least 3 characters long.") + return value + class UserUpdatePasswordSerializer(serializers.Serializer): """ Serializer for updating user password. @@ -57,3 +73,13 @@ class UserUpdatePasswordSerializer(serializers.Serializer): current_password = serializers.CharField(write_only=True) new_password = serializers.CharField(write_only=True) confirm_password = serializers.CharField(write_only=True) + + def validate_new_password(self, value): + """ + Validate the format of the new password using Django's built-in validators. + """ + try: + validate_password(value) + except ValidationError as e: + raise serializers.ValidationError(e.messages) + return value diff --git a/MisplaceAI/user_dashboard/tests/test_current_user_email.py b/MisplaceAI/user_dashboard/tests/test_current_user_email.py new file mode 100644 index 0000000000000000000000000000000000000000..15a9f657c1eaae6c72ffe5198a01e91538cf90f7 --- /dev/null +++ b/MisplaceAI/user_dashboard/tests/test_current_user_email.py @@ -0,0 +1,57 @@ +# MisplaceAI/user_dashboard/tests/test_current_user_email.py + +""" +Test 1: Ensure an authenticated user can retrieve their current email. +Test 2: Ensure an unauthenticated user cannot retrieve their current email. +Test 3: Ensure the email retrieval returns the correct email format. +Test 4: Ensure an authenticated user with a different email can retrieve their current email. +""" + +from rest_framework.test import APITestCase +from rest_framework import status +from django.contrib.auth.models import User +from django.urls import reverse + +class CurrentUserEmailViewTest(APITestCase): + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword', email='test@example.com') + self.user2 = User.objects.create_user(username='otheruser', password='otherpassword', email='other@example.com') + + def test_current_user_email_view_authenticated(self): + """ + Test 1: Ensure an authenticated user can retrieve their current email. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-current-email') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['email'], self.user.email) + + def test_current_user_email_view_unauthenticated(self): + """ + Test 2: Ensure an unauthenticated user cannot retrieve their current email. + """ + url = reverse('user-current-email') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + + def test_email_retrieval_correct_format(self): + """ + Test 3: Ensure the email retrieval returns the correct email format. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-current-email') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertRegex(response.data['email'], r"[^@]+@[^@]+\.[^@]+") + + def test_current_user_email_view_different_user(self): + """ + Test 4: Ensure an authenticated user with a different email can retrieve their current email. + """ + self.client.force_authenticate(user=self.user2) + url = reverse('user-current-email') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['email'], self.user2.email) diff --git a/MisplaceAI/user_dashboard/tests/test_current_user_username.py b/MisplaceAI/user_dashboard/tests/test_current_user_username.py new file mode 100644 index 0000000000000000000000000000000000000000..725c194b96a84e46749a2fc24e106e95b49a8b84 --- /dev/null +++ b/MisplaceAI/user_dashboard/tests/test_current_user_username.py @@ -0,0 +1,58 @@ +# MisplaceAI/user_dashboard/tests/test_current_user_username.py + +""" +Test 1: Ensure an authenticated user can retrieve their current username. +Test 2: Ensure an unauthenticated user cannot retrieve their current username. +Test 3: Ensure the username retrieval returns the correct username format. +Test 4: Ensure an authenticated user with a different username can retrieve their current username. +""" + +from rest_framework.test import APITestCase +from rest_framework import status +from django.contrib.auth.models import User +from django.urls import reverse + +class CurrentUserUsernameViewTest(APITestCase): + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword') + self.user2 = User.objects.create_user(username='otheruser', password='otherpassword') + self.client.force_authenticate(user=self.user) + + def test_current_user_username_view_authenticated(self): + """ + Test 1: Ensure an authenticated user can retrieve their current username. + """ + url = reverse('user-current-username') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['username'], self.user.username) + + def test_current_user_username_view_unauthenticated(self): + """ + Test 2: Ensure an unauthenticated user cannot retrieve their current username. + """ + self.client.logout() + url = reverse('user-current-username') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + + def test_username_retrieval_correct_format(self): + """ + Test 3: Ensure the username retrieval returns the correct username format. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-current-username') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertRegex(response.data['username'], r"^[a-zA-Z0-9_]+$") + + def test_current_user_username_view_different_user(self): + """ + Test 4: Ensure an authenticated user with a different username can retrieve their current username. + """ + self.client.force_authenticate(user=self.user2) + url = reverse('user-current-username') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['username'], self.user2.username) diff --git a/MisplaceAI/user_dashboard/tests/test_update_email.py b/MisplaceAI/user_dashboard/tests/test_update_email.py new file mode 100644 index 0000000000000000000000000000000000000000..784cb6e74527c8dfeb2aaaf8231b96ba91125ec9 --- /dev/null +++ b/MisplaceAI/user_dashboard/tests/test_update_email.py @@ -0,0 +1,112 @@ +# MisplaceAI/user_dashboard/tests/test_update_email.py + +""" +Test 1: Ensure an authenticated user can update their email. +Test 2: Ensure an unauthenticated user cannot update their email. +Test 3: Ensure email updates with the correct password. +Test 4: Ensure email updates fail with the incorrect password. +Test 5: Ensure invalid email format returns an error. +Test 6: Ensure updating to an already used email returns an error. +Test 7: Ensure an email update request without a password fails. +Test 8: Ensure an email update request with an empty email field fails. +Test 9: Ensure an email update request with an empty password field fails. +""" + +from rest_framework.test import APITestCase +from rest_framework import status +from django.contrib.auth.models import User +from django.urls import reverse + +class UpdateEmailViewTest(APITestCase): + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword', email='test@example.com') + self.other_user = User.objects.create_user(username='otheruser', password='otherpassword', email='other@example.com') + + def test_update_email_view_authenticated(self): + """ + Test 1: Ensure an authenticated user can update their email. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-email') + data = {'email': 'newemail@example.com', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_update_email_view_unauthenticated(self): + """ + Test 2: Ensure an unauthenticated user cannot update their email. + """ + url = reverse('user-update-email') + data = {'email': 'newemail@example.com', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_update_email_with_correct_password(self): + """ + Test 3: Ensure email updates with the correct password. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-email') + data = {'email': 'newemail@example.com', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_update_email_with_wrong_password(self): + """ + Test 4: Ensure email updates fail with the incorrect password. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-email') + data = {'email': 'newemail@example.com', 'password': 'wrongpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_email_with_invalid_email(self): + """ + Test 5: Ensure invalid email format returns an error. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-email') + data = {'email': 'invalid-email', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_email_to_existing_email(self): + """ + Test 6: Ensure updating to an already used email returns an error. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-email') + data = {'email': 'other@example.com', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_email_without_password(self): + """ + Test 7: Ensure an email update request without a password fails. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-email') + data = {'email': 'newemail@example.com', 'password': ''} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_email_with_empty_email_field(self): + """ + Test 8: Ensure an email update request with an empty email field fails. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-email') + data = {'email': '', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_email_with_empty_password_field(self): + """ + Test 9: Ensure an email update request with an empty password field fails. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-email') + data = {'email': 'newemail@example.com', 'password': ''} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/MisplaceAI/user_dashboard/tests/test_update_password.py b/MisplaceAI/user_dashboard/tests/test_update_password.py new file mode 100644 index 0000000000000000000000000000000000000000..c79b88d05150423565401932b52820cc86852d6d --- /dev/null +++ b/MisplaceAI/user_dashboard/tests/test_update_password.py @@ -0,0 +1,111 @@ +# MisplaceAI/user_dashboard/tests/test_update_password.py + + +""" +Test 1: Ensure an authenticated user can update their password. +Test 2: Ensure an unauthenticated user cannot update their password. +Test 3: Ensure password updates with the correct current password. +Test 4: Ensure password updates fail with the wrong current password. +Test 5: Ensure password updates fail when new passwords do not match. +Test 6: Ensure password updates fail with invalid new password format. +""" + + +from django.contrib.auth.models import User +from rest_framework import status +from rest_framework.reverse import reverse +from rest_framework.test import APITestCase + +class UpdatePasswordViewTest(APITestCase): + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword') + + def test_update_password_view_authenticated(self): + """ + Test 1: Ensure an authenticated user can update their password. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-password') + data = { + 'current_password': 'testpassword', + 'new_password': 'newpassword123', + 'confirm_password': 'newpassword123' + } + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_update_password_view_unauthenticated(self): + """ + Test 2: Ensure an unauthenticated user cannot update their password. + """ + url = reverse('user-update-password') + data = { + 'current_password': 'testpassword', + 'new_password': 'newpassword123', + 'confirm_password': 'newpassword123' + } + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + + def test_update_password_with_correct_current_password(self): + """ + Test 3: Ensure password updates with the correct current password. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-password') + data = { + 'current_password': 'testpassword', + 'new_password': 'newpassword123', + 'confirm_password': 'newpassword123' + } + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_update_password_with_wrong_current_password(self): + """ + Test 4: Ensure password updates fail with the wrong current password. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-password') + data = { + 'current_password': 'wrongpassword', + 'new_password': 'newpassword123', + 'confirm_password': 'newpassword123' + } + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('error', response.data) + self.assertEqual(response.data['error'], 'Current password is incorrect') + + def test_update_password_with_mismatched_new_passwords(self): + """ + Test 5: Ensure password updates fail when new passwords do not match. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-password') + data = { + 'current_password': 'testpassword', + 'new_password': 'newpassword123', + 'confirm_password': 'differentpassword123' + } + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('error', response.data) + self.assertEqual(response.data['error'], 'New passwords do not match') + + def test_update_password_with_invalid_new_password_format(self): + """ + Test 6: Ensure password updates fail with invalid new password format. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-password') + data = { + 'current_password': 'testpassword', + 'new_password': 'short', + 'confirm_password': 'short' + } + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('new_password', response.data) + self.assertIn('This password is too short. It must contain at least 8 characters.', response.data['new_password']) diff --git a/MisplaceAI/user_dashboard/tests/test_update_username.py b/MisplaceAI/user_dashboard/tests/test_update_username.py new file mode 100644 index 0000000000000000000000000000000000000000..600c872ae7b1f9f906d86a2592be3f75aeaf7d94 --- /dev/null +++ b/MisplaceAI/user_dashboard/tests/test_update_username.py @@ -0,0 +1,118 @@ +# MisplaceAI/user_dashboard/tests/test_update_username.py + +""" +Test 1: Ensure an authenticated user can update their username. +Test 2: Ensure an unauthenticated user cannot update their username. +Test 3: Ensure username updates fail with invalid data. +Test 4: Ensure username updates fail without providing a password. +Test 5: Ensure username updates fail with an incorrect password. +Test 6: Ensure username updates respect case sensitivity. +Test 7: Ensure username updates fail if the username is too long. +Test 8: Ensure username updates fail if the username is too short. +Test 9: Ensure a user cannot update their username to a username that already exists. +""" + +from rest_framework.test import APITestCase +from rest_framework import status +from django.contrib.auth.models import User +from django.urls import reverse + +class UpdateUsernameViewTest(APITestCase): + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword') + self.other_user = User.objects.create_user(username='otheruser', password='otherpassword') # For duplicate username test + + def test_update_username_view_authenticated(self): + """ + Test 1: Ensure an authenticated user can update their username. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-username') + data = {'username': 'newusername', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.user.refresh_from_db() + self.assertEqual(self.user.username, 'newusername') + + def test_update_username_view_unauthenticated(self): + """ + Test 2: Ensure an unauthenticated user cannot update their username. + """ + url = reverse('user-update-username') + data = {'username': 'newusername', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_update_username_with_invalid_data(self): + """ + Test 3: Ensure username updates fail with invalid data. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-username') + data = {'username': '', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_username_without_password(self): + """ + Test 4: Ensure username updates fail without providing a password. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-username') + data = {'username': 'newusername', 'password': ''} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_username_with_incorrect_password(self): + """ + Test 5: Ensure username updates fail with an incorrect password. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-username') + data = {'username': 'newusername', 'password': 'wrongpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_username_case_sensitivity(self): + """ + Test 6: Ensure username updates respect case sensitivity. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-username') + data = {'username': 'NewUsername', 'password': 'testpassword'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.user.refresh_from_db() + self.assertEqual(self.user.username, 'NewUsername') + + def test_update_username_too_long(self): + """ + Test 7: Ensure username updates fail if the username is too long. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-username') + data = {'username': 'a' * 151, 'password': 'testpassword'} # Assuming the max length is 150 + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_username_too_short(self): + """ + Test 8: Ensure username updates fail if the username is too short. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-username') + data = {'username': 'ab', 'password': 'testpassword'} # Assuming the min length is 3 + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_update_username_to_existing_username(self): + """ + Test 9: Ensure a user cannot update their username to a username that already exists. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update-username') + data = {'username': 'otheruser', 'password': 'testpassword'} # 'otheruser' already exists + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('error', response.data) + self.assertEqual(response.data['error'], 'Username already exists') diff --git a/MisplaceAI/user_dashboard/tests/test_user_dashboard.py b/MisplaceAI/user_dashboard/tests/test_user_dashboard.py new file mode 100644 index 0000000000000000000000000000000000000000..96c2500f39e74efa407e08ea0821fe5002436620 --- /dev/null +++ b/MisplaceAI/user_dashboard/tests/test_user_dashboard.py @@ -0,0 +1,79 @@ +# MisplaceAI/user_dashboard/tests/test_user_dashboard.py + +""" +Test 1: Ensure an authenticated user can retrieve their dashboard information. +Test 2: Ensure an unauthenticated user cannot retrieve dashboard information. +Test 3: Ensure an authenticated user can retrieve their full profile details. +Test 4: Ensure retrieving the dashboard information includes the user's email. +Test 5: Ensure retrieving the dashboard information includes the user's first name and last name. +""" + +from rest_framework.test import APITestCase +from rest_framework import status +from django.contrib.auth.models import User +from django.urls import reverse + +class UserDashboardViewTest(APITestCase): + def setUp(self): + self.user = User.objects.create_user( + username='testuser', + password='testpassword', + email='testuser@example.com', + first_name='Test', + last_name='User' + ) + + def test_user_dashboard_view_authenticated(self): + """ + Test 1: Ensure an authenticated user can retrieve their dashboard information. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-dashboard') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['username'], self.user.username) + + def test_user_dashboard_view_unauthenticated(self): + """ + Test 2: Ensure an unauthenticated user cannot retrieve dashboard information. + """ + url = reverse('user-dashboard') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_user_dashboard_view_full_profile(self): + """ + Test 3: Ensure an authenticated user can retrieve their full profile details. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-dashboard') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['username'], self.user.username) + self.assertEqual(response.data['email'], self.user.email) + self.assertEqual(response.data['first_name'], self.user.first_name) + self.assertEqual(response.data['last_name'], self.user.last_name) + + def test_user_dashboard_includes_email(self): + """ + Test 4: Ensure retrieving the dashboard information includes the user's email. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-dashboard') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('email', response.data) + self.assertEqual(response.data['email'], self.user.email) + + def test_user_dashboard_includes_name(self): + """ + Test 5: Ensure retrieving the dashboard information includes the user's first name and last name. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-dashboard') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('first_name', response.data) + self.assertIn('last_name', response.data) + self.assertEqual(response.data['first_name'], self.user.first_name) + self.assertEqual(response.data['last_name'], self.user.last_name) diff --git a/MisplaceAI/user_dashboard/tests/test_user_update.py b/MisplaceAI/user_dashboard/tests/test_user_update.py new file mode 100644 index 0000000000000000000000000000000000000000..4e7884647a0ed09b0b49386d37cb4001c52affb4 --- /dev/null +++ b/MisplaceAI/user_dashboard/tests/test_user_update.py @@ -0,0 +1,51 @@ +# MisplaceAI/user_dashboard/tests/test_user_update.py + +from rest_framework.test import APITestCase +from rest_framework import status +from django.contrib.auth.models import User +from django.urls import reverse + +class UserUpdateViewTest(APITestCase): + def setUp(self): + self.user = User.objects.create_user(username='testuser', password='testpassword') + + def test_user_update_view_authenticated(self): + """ + Test 1: Ensure an authenticated user can update their information. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update') + data = {'username': 'updateduser'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['username'], 'updateduser') + + def test_user_update_view_unauthenticated(self): + """ + Test 2: Ensure an unauthenticated user cannot update information. + """ + url = reverse('user-update') + data = {'username': 'updateduser'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_user_update_view_invalid_data(self): + """ + Test 3: Ensure invalid data returns appropriate errors. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update') + data = {'username': ''} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_user_update_view_partial_update(self): + """ + Test 4: Ensure partial updates work correctly for authenticated users. + """ + self.client.force_authenticate(user=self.user) + url = reverse('user-update') + data = {'first_name': 'Updated'} + response = self.client.patch(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['first_name'], 'Updated') diff --git a/MisplaceAI/user_dashboard/views.py b/MisplaceAI/user_dashboard/views.py index f89c3ae16c6ecbb21e89392b104eb2b27d65c2e0..395490b6cda6fa7bd40780f97a8c48a9ec3f068c 100644 --- a/MisplaceAI/user_dashboard/views.py +++ b/MisplaceAI/user_dashboard/views.py @@ -12,6 +12,10 @@ from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from django.contrib.auth import authenticate +from django.contrib.auth.password_validation import validate_password +from django.core.exceptions import ValidationError +from rest_framework.exceptions import NotAuthenticated + class UserDashboardView(generics.RetrieveAPIView): """ @@ -59,6 +63,8 @@ class UpdateEmailView(APIView): return Response({'message': 'Email updated successfully'}, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + class UpdateUsernameView(APIView): """ @@ -77,12 +83,19 @@ class UpdateUsernameView(APIView): if not user.check_password(password): return Response({'error': 'Password is incorrect'}, status=status.HTTP_400_BAD_REQUEST) + if User.objects.filter(username=new_username).exists(): + return Response({'error': 'Username already exists'}, status=status.HTTP_400_BAD_REQUEST) + user.username = new_username user.save() return Response({'message': 'Username updated successfully'}, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + + class CurrentUserEmailView(APIView): """ View for retrieving the current user's email address. @@ -102,6 +115,12 @@ class CurrentUserUsernameView(APIView): def get(self, request, *args, **kwargs): user = request.user return Response({'username': user.username}, status=status.HTTP_200_OK) + + + + + + class UpdatePasswordView(APIView): """ @@ -110,6 +129,10 @@ class UpdatePasswordView(APIView): permission_classes = [IsAuthenticated] def put(self, request, *args, **kwargs): + # Check if the user is authenticated + if not request.user.is_authenticated: + raise NotAuthenticated('Authentication credentials were not provided.') + user = request.user serializer = UserUpdatePasswordSerializer(data=request.data) @@ -124,8 +147,13 @@ class UpdatePasswordView(APIView): if new_password != confirm_password: return Response({'error': 'New passwords do not match'}, status=status.HTTP_400_BAD_REQUEST) + try: + validate_password(new_password, user) + except ValidationError as e: + return Response({'new_password': e.messages}, status=status.HTTP_400_BAD_REQUEST) + user.set_password(new_password) user.save() return Response({'message': 'Password updated successfully'}, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file