diff --git a/MisplaceAI/MisplaceAI/test_settings.py b/MisplaceAI/MisplaceAI/test_settings.py index f1f30e949b7458cc19b65950c1b000c0a7041634..6d795d3f1acecc93a3eaf436422e39487b5da640 100644 --- a/MisplaceAI/MisplaceAI/test_settings.py +++ b/MisplaceAI/MisplaceAI/test_settings.py @@ -1,3 +1,5 @@ +# MisplaceAi/MisplaceAI/test_settings.py + from .settings import * # Database configuration for testing diff --git a/MisplaceAI/authentication/__pycache__/views.cpython-310.pyc b/MisplaceAI/authentication/__pycache__/views.cpython-310.pyc index a09863931515576a4e71ad91083391430a224427..de51a8f509729894187b8ceed750ac3d4b022d24 100644 Binary files a/MisplaceAI/authentication/__pycache__/views.cpython-310.pyc and b/MisplaceAI/authentication/__pycache__/views.cpython-310.pyc differ diff --git a/MisplaceAI/authentication/views.py b/MisplaceAI/authentication/views.py index 554a474ec1c978e52622ea0f339211cbb002a540..f67e2f3860403cacd5092266858c46f4a9a7a808 100644 --- a/MisplaceAI/authentication/views.py +++ b/MisplaceAI/authentication/views.py @@ -13,11 +13,21 @@ from .serializers import RegisterSerializer, LoginSerializer from django.contrib.auth.models import User class RegisterView(generics.CreateAPIView): - """ - View for user registration. - """ - queryset = User.objects.all() # Queryset of all users - serializer_class = RegisterSerializer # Serializer class for user registration + queryset = User.objects.all() + serializer_class = RegisterSerializer + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + try: + serializer.is_valid(raise_exception=True) + user = serializer.save() + refresh = RefreshToken.for_user(user) + return Response({ + 'refresh': str(refresh), + 'access': str(refresh.access_token), + }, status=status.HTTP_201_CREATED) + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) class LoginView(APIView): """ diff --git a/MisplaceAI/rules/__pycache__/views.cpython-310.pyc b/MisplaceAI/rules/__pycache__/views.cpython-310.pyc index 1b6771d072a0121af1c0f8569ec94a78e46e978f..42d382c54f46045ba5c3e96a83735586c16ecf95 100644 Binary files a/MisplaceAI/rules/__pycache__/views.cpython-310.pyc and b/MisplaceAI/rules/__pycache__/views.cpython-310.pyc differ diff --git a/MisplaceAI/rules/decorators.py b/MisplaceAI/rules/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..11b799ac1cc4b7ce97a95720db956882d43f5fd9 --- /dev/null +++ b/MisplaceAI/rules/decorators.py @@ -0,0 +1,16 @@ +# misplaceAI/rules/decorators.py + +from functools import wraps +from rest_framework.response import Response +from rest_framework import status + +def admin_required(view_func): + """ + Custom decorator to ensure the user is an admin. + """ + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + if not request.user.is_authenticated or not request.user.is_superuser: + return Response({'detail': 'Permission denied.'}, status=status.HTTP_403_FORBIDDEN) + return view_func(request, *args, **kwargs) + return _wrapped_view diff --git a/MisplaceAI/rules/tests/test_items.py b/MisplaceAI/rules/tests/test_items.py new file mode 100644 index 0000000000000000000000000000000000000000..a16cab37f47cd58c23de999ae41840e4505dc74d --- /dev/null +++ b/MisplaceAI/rules/tests/test_items.py @@ -0,0 +1,123 @@ +# misplaceAI/rules/tests/test_items.py + +""" +Test 1: test_create_item_as_admin - Test that an admin user can create a new item. +Test 2: test_create_item_as_normal_user - Test that a normal user cannot create a new item. +Test 3: test_delete_item_as_admin - Test that an admin user can delete an existing item. +Test 4: test_delete_item_as_normal_user - Test that a normal user cannot delete an existing item. +Test 5: test_get_items_as_normal_user - Test that a normal user can retrieve the list of items. +Test 6: test_update_item_as_admin - Test that an admin user can update an existing item. +Test 7: test_update_item_as_normal_user - Test that a normal user cannot update an existing item. +Test 8: test_create_item_with_invalid_data - Test that creating an item with invalid data returns validation errors. +Test 9: test_get_item_detail_as_admin - Test that an admin user can retrieve the details of a specific item. +Test 10: test_get_item_detail_as_normal_user - Test that a normal user can retrieve the details of a specific item. +""" +from rest_framework.test import APITestCase +from rest_framework import status +from django.urls import reverse +from django.contrib.auth.models import User +from rules.models import Item + +class AdminManageItemViewTest(APITestCase): + """ + Test suite for the AdminManageItemView. + """ + + def setUp(self): + self.admin_user = User.objects.create_superuser(username='adminuser', password='adminpassword') + self.normal_user = User.objects.create_user(username='testuser', password='testpassword') + self.item = Item.objects.create(name='Test Item') + + def test_create_item_as_admin(self): + """ + Ensure admin users can create a new item. + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_item') + data = {'name': 'New Item'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Item.objects.count(), 2) + self.assertEqual(Item.objects.get(id=response.data['id']).name, 'New Item') + + def test_create_item_as_normal_user(self): + """ + Ensure normal users cannot create a new item. + """ + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_item') + data = {'name': 'New Item'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_item_as_admin(self): + """ + Ensure admin users can delete an existing item. + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Item.objects.count(), 0) + + def test_delete_item_as_normal_user(self): + """ + Ensure normal users cannot delete an existing item. + """ + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_get_items_as_normal_user(self): + """ + Ensure normal users can retrieve the list of items. + """ + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_item') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['name'], 'Test Item') + + def test_update_item_as_admin(self): + """ + Ensure admin users can update an existing item. + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + data = {'name': 'Updated Item'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(Item.objects.get(id=self.item.id).name, 'Updated Item') + + def test_update_item_as_normal_user(self): + """ + Ensure normal users cannot update an existing item. + """ + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + data = {'name': 'Updated Item'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_create_item_with_invalid_data(self): + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_item') + data = {'invalid_field': 'invalid_data'} + response = self.client.post(url, data, format='json') + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_get_item_detail_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + response = self.client.get(url) + assert response.status_code == status.HTTP_200_OK + + def test_get_item_detail_as_normal_user(self): + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) + response = self.client.get(url) + assert response.status_code == status.HTTP_200_OK + + diff --git a/MisplaceAI/rules/tests/test_locations.py b/MisplaceAI/rules/tests/test_locations.py new file mode 100644 index 0000000000000000000000000000000000000000..5c1ee11a63cced3b4b30ac4f219f4149fe2c5714 --- /dev/null +++ b/MisplaceAI/rules/tests/test_locations.py @@ -0,0 +1,123 @@ +# misplaceAI/rules/tests/test_locations.py + + +""" +Test 1: test_create_location_as_admin - Test that an admin user can create a new location. +Test 2: test_create_location_as_normal_user - Test that a normal user cannot create a new location. +Test 3: test_delete_location_as_admin - Test that an admin user can delete an existing location. +Test 4: test_delete_location_as_normal_user - Test that a normal user cannot delete an existing location. +Test 5: test_get_locations_as_normal_user - Test that a normal user can retrieve the list of locations. +Test 6: test_update_location_as_admin - Test that an admin user can update an existing location. +Test 7: test_update_location_as_normal_user - Test that a normal user cannot update an existing location. +Test 8: test_create_location_with_invalid_data - Test that creating a location with invalid data returns validation errors. +Test 9: test_get_location_detail_as_admin - Test that an admin user can retrieve the details of a specific location. +Test 10: test_get_location_detail_as_normal_user - Test that a normal user can retrieve the details of a specific location. +""" + + +from rest_framework.test import APITestCase +from rest_framework import status +from django.urls import reverse +from django.contrib.auth.models import User +from rules.models import Location + +class AdminManageLocationViewTest(APITestCase): + """ + Test suite for the AdminManageLocationView. + """ + + def setUp(self): + self.admin_user = User.objects.create_superuser(username='adminuser', password='adminpassword') + self.normal_user = User.objects.create_user(username='testuser', password='testpassword') + self.location = Location.objects.create(name='Test Location') + + def test_create_location_as_admin(self): + """ + Ensure admin users can create a new location. + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_location') + data = {'name': 'New Location'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Location.objects.count(), 2) + self.assertEqual(Location.objects.get(id=response.data['id']).name, 'New Location') + + def test_create_location_as_normal_user(self): + """ + Ensure normal users cannot create a new location. + """ + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_location') + data = {'name': 'New Location'} + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_location_as_admin(self): + """ + Ensure admin users can delete an existing location. + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Location.objects.count(), 0) + + def test_delete_location_as_normal_user(self): + """ + Ensure normal users cannot delete an existing location. + """ + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_get_locations_as_normal_user(self): + """ + Ensure normal users can retrieve the list of locations. + """ + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_location') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]['name'], 'Test Location') + + def test_update_location_as_admin(self): + """ + Ensure admin users can update an existing location. + """ + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + data = {'name': 'Updated Location'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(Location.objects.get(id=self.location.id).name, 'Updated Location') + + def test_update_location_as_normal_user(self): + """ + Ensure normal users cannot update an existing location. + """ + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + data = {'name': 'Updated Location'} + response = self.client.put(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_create_location_with_invalid_data(self): + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_location') + data = {'invalid_field': 'invalid_data'} + response = self.client.post(url, data, format='json') + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_get_location_detail_as_admin(self): + self.client.force_authenticate(user=self.admin_user) + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + response = self.client.get(url) + assert response.status_code == status.HTTP_200_OK + + def test_get_location_detail_as_normal_user(self): + self.client.force_authenticate(user=self.normal_user) + url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + response = self.client.get(url) + assert response.status_code == status.HTTP_200_OK diff --git a/MisplaceAI/rules/tests/test_permissions.py b/MisplaceAI/rules/tests/test_permissions.py deleted file mode 100644 index 8286aee61b6ca5f1e40f23407995599f8f59be68..0000000000000000000000000000000000000000 --- a/MisplaceAI/rules/tests/test_permissions.py +++ /dev/null @@ -1,145 +0,0 @@ -from rest_framework.test import APITestCase -from rest_framework import status -from django.urls import reverse -from django.contrib.auth.models import User -from rules.models import Item, Location - -class PermissionsTest(APITestCase): - """ - Test suite for permissions to ensure only authenticated users can access item and location endpoints. - """ - - def setUp(self): - # Create a normal user and an admin user - self.normal_user = User.objects.create_user(username='normaluser', password='testpassword') - self.admin_user = User.objects.create_superuser(username='adminuser', password='adminpassword') - - # Create an item and a location - self.item = Item.objects.create(name='Test Item') - self.location = Location.objects.create(name='Test Location') - - def test_normal_user_cannot_access_items(self): - """ - Ensure a normal user cannot access the item management endpoints. - """ - self.client.force_authenticate(user=self.normal_user) - - # Attempt to access item list - url = reverse('rules:admin_manage_item') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Attempt to create an item - data = {'name': 'New Item'} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Attempt to update an item - url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) - data = {'name': 'Updated Item'} - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Attempt to delete an item - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_normal_user_cannot_access_locations(self): - """ - Ensure a normal user cannot access the location management endpoints. - """ - self.client.force_authenticate(user=self.normal_user) - - # Attempt to access location list - url = reverse('rules:admin_manage_location') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Attempt to create a location - data = {'name': 'New Location'} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Attempt to update a location - url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) - data = {'name': 'Updated Location'} - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - # Attempt to delete a location - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_admin_user_can_access_items_and_locations(self): - """ - Ensure an admin user can access the item and location management endpoints. - """ - self.client.force_authenticate(user=self.admin_user) - - # Access item list - url = reverse('rules:admin_manage_item') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # Create an item - data = {'name': 'New Item'} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - # Update an item - url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) - data = {'name': 'Updated Item'} - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # Delete an item - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - # Access location list - url = reverse('rules:admin_manage_location') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # Create a location - data = {'name': 'New Location'} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - # Update a location - url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) - data = {'name': 'Updated Location'} - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # Delete a location - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - def test_normal_user_can_access_rules(self): - """ - Ensure a normal user can access the rules management endpoints. - """ - self.client.force_authenticate(user=self.normal_user) - - # Access rule list - url = reverse('rules:admin_manage_rule') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # Create a rule - data = {'item': self.item.id, 'locations': [self.location.id]} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - # Update a rule - url = reverse('rules:admin_manage_rule_detail', args=[self.item.id]) - new_item = Item.objects.create(name='New Item') - new_location = Location.objects.create(name='New Location') - data = {'item': new_item.id, 'locations': [new_location.id]} - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - # Delete a rule - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/MisplaceAI/rules/tests/test_rules.py b/MisplaceAI/rules/tests/test_rules.py new file mode 100644 index 0000000000000000000000000000000000000000..33cdab6a64bc91b05fa2b5528982052d9669157b --- /dev/null +++ b/MisplaceAI/rules/tests/test_rules.py @@ -0,0 +1,266 @@ +# misplaceAI/rules/tests/test_rules.py + +""" +Test 1: test_create_rule - Test that a rule can be created with valid data by the owner. +Test 2: test_create_rule_with_invalid_data - Test that creating a rule with invalid data fails. +Test 3: test_create_rule_without_association - Test that creating a rule without locations fails. +Test 4: test_delete_rule_as_non_owner - Test that a non-owner cannot delete a rule. +Test 5: test_delete_rule_as_owner - Test that the owner can delete their rule. +Test 6: test_get_rule_detail_as_non_owner - Test that a non-owner cannot access the details of a rule. +Test 7: test_get_rule_detail_as_owner - Test that the owner can access the details of their rule. +Test 8: test_get_rules_as_non_owner - Test that a non-owner cannot access the list of rules of the owner. +Test 9: test_get_rules_as_owner - Test that the owner can access the list of their rules. +Test 10: test_rule_association_with_multiple_locations - Test that a rule can be associated with multiple locations. +Test 11: test_update_rule_as_non_owner - Test that a non-owner cannot update a rule. +Test 12: test_update_rule_as_owner - Test that the owner can update their rule. +""" + +from rest_framework import status +from rest_framework.reverse import reverse +from rest_framework.test import APITestCase +from django.contrib.auth.models import User +from rules.models import Item, Location, Rule + +class TestAdminManageRuleView(APITestCase): + def setUp(self): + """ + Set up test data for the test cases. + """ + # Create users + self.owner = User.objects.create_user(username='owner', password='password') + self.non_owner = User.objects.create_user(username='non_owner', password='password') + + # Create an item + self.item = Item.objects.create(name='Test Item') + + # Create a location + self.location = Location.objects.create(name='Test Location') + + # Create a rule associated with the owner + self.rule = Rule.objects.create(user=self.owner, item=self.item) + self.rule.locations.set([self.location]) + + # Define URLs for rule management + self.url = reverse('rules:admin_manage_rule') + self.detail_url = lambda rule_id: reverse('rules:admin_manage_rule_detail', args=[rule_id]) + + def test_create_rule(self): + """ + Test 1: test_create_rule - Test that a rule can be created with valid data by the owner. + """ + # Authenticate as the owner + self.client.force_authenticate(user=self.owner) + + # Define the data for the new rule + data = { + 'item': self.item.id, + 'locations': [self.location.id] + } + + # Send a POST request to create the rule + response = self.client.post(self.url, data, format='json') + + # Assert that the rule is created successfully + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_create_rule_with_invalid_data(self): + """ + Test 2: test_create_rule_with_invalid_data - Test that creating a rule with invalid data fails. + """ + # Authenticate as the owner + self.client.force_authenticate(user=self.owner) + + # Define invalid data for the new rule + data = { + 'item': '', + 'locations': [] + } + + # Send a POST request with invalid data + response = self.client.post(self.url, data, format='json') + + # Assert that the request fails with a 400 Bad Request status + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_rule_without_association(self): + """ + Test 3: test_create_rule_without_association - Test that creating a rule without locations fails. + """ + # Authenticate as the owner + self.client.force_authenticate(user=self.owner) + + # Define data for the new rule without locations + data = { + 'item': self.item.id, + 'locations': [] + } + + # Send a POST request with incomplete data + response = self.client.post(self.url, data, format='json') + + # Assert that the request fails with a 400 Bad Request status + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_delete_rule_as_non_owner(self): + """ + Test 4: test_delete_rule_as_non_owner - Test that a non-owner cannot delete a rule. + """ + # Authenticate as the non-owner + self.client.force_authenticate(user=self.non_owner) + + # Send a DELETE request for the rule + response = self.client.delete(self.detail_url(self.rule.id)) + + # Assert that the request fails with a 403 Forbidden status + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_rule_as_owner(self): + """ + Test 5: test_delete_rule_as_owner - Test that the owner can delete their rule. + """ + # Authenticate as the owner + self.client.force_authenticate(user=self.owner) + + # Send a DELETE request for the rule + response = self.client.delete(self.detail_url(self.rule.id)) + + # Assert that the rule is deleted successfully with a 204 No Content status + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test_get_rule_detail_as_non_owner(self): + """ + Test 6: test_get_rule_detail_as_non_owner - Test that a non-owner cannot access the details of a rule. + """ + # Authenticate as the non-owner + self.client.force_authenticate(user=self.non_owner) + + # Send a GET request for the rule detail + response = self.client.get(self.detail_url(self.rule.id)) + + # Assert that the request fails with a 403 Forbidden status + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_get_rule_detail_as_owner(self): + """ + Test 7: test_get_rule_detail_as_owner - Test that the owner can access the details of their rule. + """ + # Authenticate as the owner + self.client.force_authenticate(user=self.owner) + + # Send a GET request for the rule detail + response = self.client.get(self.detail_url(self.rule.id)) + + # Assert that the request is successful with a 200 OK status + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Assert that the returned rule ID matches the rule's ID + self.assertEqual(response.data['id'], self.rule.id) + + def test_get_rules_as_non_owner(self): + """ + Test 8: test_get_rules_as_non_owner - Test that a non-owner cannot access the list of rules of the owner. + """ + # Authenticate as the non-owner + self.client.force_authenticate(user=self.non_owner) + + # Send a GET request for the list of rules + response = self.client.get(self.url) + + # Assert that the request is successful with a 200 OK status + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Assert that the non-owner sees an empty list + self.assertEqual(response.data, []) + + def test_get_rules_as_owner(self): + """ + Test 9: test_get_rules_as_owner - Test that the owner can access the list of their rules. + """ + # Authenticate as the owner + self.client.force_authenticate(user=self.owner) + + # Send a GET request for the list of rules + response = self.client.get(self.url) + + # Assert that the request is successful with a 200 OK status + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Assert that the returned list contains one rule + self.assertEqual(len(response.data), 1) + + # Assert that the returned rule ID matches the rule's ID + self.assertEqual(response.data[0]['id'], self.rule.id) + + def test_rule_association_with_multiple_locations(self): + """ + Test 10: test_rule_association_with_multiple_locations - Test that a rule can be associated with multiple locations. + """ + # Authenticate as the owner + self.client.force_authenticate(user=self.owner) + + # Create a new location + new_location = Location.objects.create(name='New Location') + + # Define the data for updating the rule with multiple locations + data = { + 'item': self.item.id, + 'locations': [self.location.id, new_location.id] + } + + # Send a PUT request to update the rule + response = self.client.put(self.detail_url(self.rule.id), data, format='json') + + # Assert that the request is successful with a 200 OK status + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Assert that the rule is associated with two locations + self.assertEqual(len(response.data['locations']), 2) + + def test_update_rule_as_non_owner(self): + """ + Test 11: test_update_rule_as_non_owner - Test that a non-owner cannot update a rule. + """ + # Authenticate as the non-owner + self.client.force_authenticate(user=self.non_owner) + + # Create a new item and location + new_item = Item.objects.create(name='New Item') + new_location = Location.objects.create(name='New Location') + + # Define the data for updating the rule + data = { + 'item': new_item.id, + 'locations': [new_location.id] + } + + # Send a PUT request to update the rule + response = self.client.put(self.detail_url(self.rule.id), data, format='json') + + # Assert that the request fails with a 403 Forbidden status + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_update_rule_as_owner(self): + """ + Test 12: test_update_rule_as_owner - Test that the owner can update their rule. + """ + # Authenticate as the owner + self.client.force_authenticate(user=self.owner) + + # Create a new item and location + new_item = Item.objects.create(name='New Item') + new_location = Location.objects.create(name='New Location') + + # Define the data for updating the rule + data = { + 'item': new_item.id, + 'locations': [new_location.id] + } + + # Send a PUT request to update the rule + response = self.client.put(self.detail_url(self.rule.id), data, format='json') + + # Assert that the request is successful with a 200 OK status + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Assert that the returned rule ID matches the rule's ID + self.assertEqual(response.data['id'], self.rule.id) diff --git a/MisplaceAI/rules/tests/test_views.py b/MisplaceAI/rules/tests/test_views.py deleted file mode 100644 index e501fe619eed1abea98f7fc6422c3b531c011c92..0000000000000000000000000000000000000000 --- a/MisplaceAI/rules/tests/test_views.py +++ /dev/null @@ -1,182 +0,0 @@ -from rest_framework.test import APITestCase -from rest_framework import status -from django.urls import reverse -from django.contrib.auth.models import User -from rules.models import Location, Item, Rule - -class UserListViewTest(APITestCase): - """ - Test suite for the UserListView. - """ - - def setUp(self): - # Create a user - self.user = User.objects.create_user(username='testuser', password='testpassword') - self.client.force_authenticate(user=self.user) - - def test_list_users(self): - """ - Ensure we can list all users. - """ - url = reverse('rules:user-list') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]['username'], 'testuser') - -class AdminManageItemViewTest(APITestCase): - """ - Test suite for the AdminManageItemView. - """ - - def setUp(self): - self.user = User.objects.create_user(username='testuser', password='testpassword') - self.client.force_authenticate(user=self.user) - self.item = Item.objects.create(name='Test Item') - - def test_get_items(self): - """ - Ensure we can retrieve the list of items. - """ - url = reverse('rules:admin_manage_item') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]['name'], 'Test Item') - - def test_create_item(self): - """ - Ensure we can create a new item. - """ - url = reverse('rules:admin_manage_item') - data = {'name': 'New Item'} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(Item.objects.count(), 2) - self.assertEqual(Item.objects.get(id=response.data['id']).name, 'New Item') - - def test_update_item(self): - """ - Ensure we can update an existing item. - """ - url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) - data = {'name': 'Updated Item'} - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(Item.objects.get(id=self.item.id).name, 'Updated Item') - - def test_delete_item(self): - """ - Ensure we can delete an existing item. - """ - url = reverse('rules:admin_manage_item_detail', args=[self.item.id]) - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(Item.objects.count(), 0) - -class AdminManageLocationViewTest(APITestCase): - """ - Test suite for the AdminManageLocationView. - """ - - def setUp(self): - self.user = User.objects.create_user(username='testuser', password='testpassword') - self.client.force_authenticate(user=self.user) - self.location = Location.objects.create(name='Test Location') - - def test_get_locations(self): - """ - Ensure we can retrieve the list of locations. - """ - url = reverse('rules:admin_manage_location') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]['name'], 'Test Location') - - def test_create_location(self): - """ - Ensure we can create a new location. - """ - url = reverse('rules:admin_manage_location') - data = {'name': 'New Location'} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(Location.objects.count(), 2) - self.assertEqual(Location.objects.get(id=response.data['id']).name, 'New Location') - - def test_update_location(self): - """ - Ensure we can update an existing location. - """ - url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) - data = {'name': 'Updated Location'} - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(Location.objects.get(id=self.location.id).name, 'Updated Location') - - def test_delete_location(self): - """ - Ensure we can delete an existing location. - """ - url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(Location.objects.count(), 0) - -class AdminManageRuleViewTest(APITestCase): - """ - Test suite for the AdminManageRuleView. - """ - - def setUp(self): - self.user = User.objects.create_user(username='testuser', password='testpassword') - self.client.force_authenticate(user=self.user) - self.item = Item.objects.create(name='Test Item') - self.location = Location.objects.create(name='Test Location') - self.rule = Rule.objects.create(user=self.user, item=self.item) - self.rule.locations.add(self.location) - - def test_get_rules(self): - """ - Ensure we can retrieve the list of rules. - """ - url = reverse('rules:admin_manage_rule') - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]['item']['id'], self.item.id) - - def test_create_rule(self): - """ - Ensure we can create a new rule. - """ - url = reverse('rules:admin_manage_rule') - data = {'item': self.item.id, 'locations': [self.location.id]} - response = self.client.post(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(Rule.objects.count(), 2) - self.assertEqual(Rule.objects.get(id=response.data['id']).item.id, self.item.id) - - def test_update_rule(self): - """ - Ensure we can update an existing rule. - """ - url = reverse('rules:admin_manage_rule_detail', args=[self.rule.id]) - new_item = Item.objects.create(name='New Item') - new_location = Location.objects.create(name='New Location') - data = {'item': new_item.id, 'locations': [new_location.id]} - response = self.client.put(url, data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - updated_rule = Rule.objects.get(id=self.rule.id) - self.assertEqual(updated_rule.item.id, new_item.id) - self.assertEqual(updated_rule.locations.first().id, new_location.id) - - def test_delete_rule(self): - """ - Ensure we can delete an existing rule. - """ - url = reverse('rules:admin_manage_rule_detail', args=[self.rule.id]) - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(Rule.objects.count(), 0) \ No newline at end of file diff --git a/MisplaceAI/rules/views.py b/MisplaceAI/rules/views.py index 94223ea7f9925e8aee8dc750d3577f4de2ae1ea1..c7857ab8fe978a9e5655c6ee14a9f711728dda86 100644 --- a/MisplaceAI/rules/views.py +++ b/MisplaceAI/rules/views.py @@ -1,213 +1,252 @@ # misplaceAI/rules/views.py -# This file defines the views for the rules app. -# Views handle the logic for different endpoints, providing appropriate responses -# based on the request and the business logic. This includes managing users, items, locations, and rules. - +from django.utils.decorators import method_decorator from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status -from rest_framework.permissions import IsAuthenticated, IsAdminUser -from django.contrib.auth.models import User -from .models import Location, Item, Rule -from .serializers import LocationSerializer, ItemSerializer, RuleSerializer, UserSerializer +from rest_framework.permissions import IsAuthenticated from django.shortcuts import get_object_or_404 -from rest_framework.decorators import permission_classes +from django.contrib.auth.models import User +from .models import Item, Location, Rule +from .serializers import ItemSerializer, LocationSerializer, UserSerializer, RuleSerializer +from .decorators import admin_required - class UserListView(APIView): - """ - View to list all users. Only accessible by authenticated users. - """ + # Only authenticated users can access this view permission_classes = [IsAuthenticated] + def get(self, request, *args, **kwargs): - # Retrieve all user objects from the database + # Retrieve all users from the database users = User.objects.all() - # Serialize the user objects into JSON format + # Serialize the user objects serializer = UserSerializer(users, many=True) # Return the serialized data with a 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) - - class AdminManageItemView(APIView): - """ - View to manage items. Only accessible by authenticated users. - """ - @permission_classes([IsAuthenticated]) + # Only authenticated users can access this view + permission_classes = [IsAuthenticated] + def get(self, request, *args, **kwargs): - # Retrieve all item objects from the database, ordered by name + # Retrieve all items from the database, ordered by name items = Item.objects.all().order_by('name') - # Serialize the item objects into JSON format + # Serialize the item objects serializer = ItemSerializer(items, many=True) # Return the serialized data with a 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) - - @permission_classes([IsAdminUser]) + + @method_decorator(admin_required) def post(self, request, *args, **kwargs): - # Deserialize the incoming data into an ItemSerializer + # Deserialize the request data into an ItemSerializer serializer = ItemSerializer(data=request.data) - # Validate the data + # Check if the data is valid if serializer.is_valid(): - # Save the new item object to the database + # Save the new item to the database serializer.save() # Return the serialized data with a 201 Created status return Response(serializer.data, status=status.HTTP_201_CREATED) - # Return validation errors with a 400 Bad Request status + # Return the errors with a 400 Bad Request status return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @permission_classes([IsAdminUser]) + @method_decorator(admin_required) def put(self, request, item_id, *args, **kwargs): - # Retrieve the existing item object from the database + # Retrieve the existing item or return 404 if not found item = get_object_or_404(Item, id=item_id) - # Deserialize the incoming data into an ItemSerializer + # Deserialize the request data into an ItemSerializer serializer = ItemSerializer(item, data=request.data) - # Validate the data + # Check if the data is valid if serializer.is_valid(): - # Update the existing item object in the database + # Save the updated item to the database serializer.save() # Return the serialized data with a 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) - # Return validation errors with a 400 Bad Request status + # Return the errors with a 400 Bad Request status return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - @permission_classes([IsAdminUser]) + + @method_decorator(admin_required) def delete(self, request, item_id, *args, **kwargs): - # Retrieve the existing item object from the database + # Retrieve the existing item or return 404 if not found item = get_object_or_404(Item, id=item_id) - # Delete the item object from the database + # Delete the item from the database item.delete() # Return a 204 No Content status to indicate successful deletion return Response(status=status.HTTP_204_NO_CONTENT) class AdminManageLocationView(APIView): - """ - View to manage locations. Only accessible by authenticated users. - """ + # Only authenticated users can access this view permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): - # Retrieve all location objects from the database, ordered by name + # Retrieve all locations from the database, ordered by name locations = Location.objects.all().order_by('name') - # Serialize the location objects into JSON format + # Serialize the location objects serializer = LocationSerializer(locations, many=True) # Return the serialized data with a 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) + @method_decorator(admin_required) def post(self, request, *args, **kwargs): - # Deserialize the incoming data into a LocationSerializer + # Deserialize the request data into a LocationSerializer serializer = LocationSerializer(data=request.data) - # Validate the data + # Check if the data is valid if serializer.is_valid(): - # Save the new location object to the database + # Save the new location to the database serializer.save() # Return the serialized data with a 201 Created status return Response(serializer.data, status=status.HTTP_201_CREATED) - # Return validation errors with a 400 Bad Request status + # Return the errors with a 400 Bad Request status return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + @method_decorator(admin_required) def put(self, request, location_id, *args, **kwargs): - # Retrieve the existing location object from the database + # Retrieve the existing location or return 404 if not found location = get_object_or_404(Location, id=location_id) - # Deserialize the incoming data into a LocationSerializer + # Deserialize the request data into a LocationSerializer serializer = LocationSerializer(location, data=request.data) - # Validate the data + # Check if the data is valid if serializer.is_valid(): - # Update the existing location object in the database + # Save the updated location to the database serializer.save() # Return the serialized data with a 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) - # Return validation errors with a 400 Bad Request status + # Return the errors with a 400 Bad Request status return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + @method_decorator(admin_required) def delete(self, request, location_id, *args, **kwargs): - # Retrieve the existing location object from the database + # Retrieve the existing location or return 404 if not found location = get_object_or_404(Location, id=location_id) - # Delete the location object from the database + # Delete the location from the database location.delete() # Return a 204 No Content status to indicate successful deletion return Response(status=status.HTTP_204_NO_CONTENT) -# misplaceAI/rules/views.py + + + + + class AdminManageRuleView(APIView): """ View to manage rules. Only accessible by authenticated users. """ - permission_classes = [IsAuthenticated] - - def get(self, request, *args, **kwargs): - # Filter rules based on the authenticated user - rules = Rule.objects.filter(user=request.user).order_by('id') - # Serialize the rule objects into JSON format - serializer = RuleSerializer(rules, many=True) - # Return the serialized data with a 200 OK status + permission_classes = [IsAuthenticated] # Only authenticated users can access this view + + def get(self, request, rule_id=None, *args, **kwargs): + """ + Retrieve a list of rules for the authenticated user or a specific rule. + """ + # Check if a specific rule ID is provided + if rule_id: + # Retrieve the rule or return 404 if not found + rule = get_object_or_404(Rule, id=rule_id) + # Ensure the rule belongs to the authenticated user + if rule.user != request.user: + # Return 403 Forbidden if the user does not own the rule + return Response(status=status.HTTP_403_FORBIDDEN) + # Serialize the rule object + serializer = RuleSerializer(rule) + else: + # Retrieve all rules for the authenticated user + rules = Rule.objects.filter(user=request.user).order_by('id') + # Serialize the list of rule objects + serializer = RuleSerializer(rules, many=True) + # Return the serialized data with 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request, *args, **kwargs): - # Extract the item ID and location IDs from the request data + """ + Create a new rule associated with the authenticated user. + """ + # Get the data from the request data = request.data + # Get the item ID from the data item_id = data.get('item') + # Get the list of location IDs from the data location_ids = data.get('locations', []) + # Check if both item ID and location IDs are provided + if not item_id or not location_ids: + # Return 400 Bad Request if not + return Response({'error': 'Item and locations are required.'}, status=status.HTTP_400_BAD_REQUEST) + # Get the authenticated user user = request.user - # Retrieve the item object from the database + # Retrieve the item object or return 404 if not found item = get_object_or_404(Item, id=item_id) - # Retrieve the location objects from the database + # Retrieve the location objects for the provided IDs locations = Location.objects.filter(id__in=location_ids) # Check if the locations exist if not locations.exists(): - # Return an error if one or more locations are invalid + # Return 400 Bad Request if not return Response({'error': 'One or more locations are invalid.'}, status=status.HTTP_400_BAD_REQUEST) # Create a new rule object rule = Rule(user=user, item=item) # Save the rule object to the database rule.save() - # Set the locations for the rule + # Associate the locations with the rule rule.locations.set(locations) - # Save the rule object to the database + # Save the rule object again to update the associations rule.save() - # Serialize the rule object into JSON format + # Serialize the rule object serializer = RuleSerializer(rule) - # Return the serialized data with a 201 Created status + # Return the serialized data with 201 Created status return Response(serializer.data, status=status.HTTP_201_CREATED) def put(self, request, rule_id, *args, **kwargs): - # Retrieve the existing rule object from the database + """ + Update an existing rule for the authenticated user. + """ + # Retrieve the existing rule object from the database and ensure it belongs to the user rule = get_object_or_404(Rule, id=rule_id) - # Extract the item ID and location IDs from the request data + # Ensure the rule belongs to the authenticated user + if rule.user != request.user: + # Return 403 Forbidden if the user does not own the rule + return Response(status=status.HTTP_403_FORBIDDEN) + + # Get the data from the request data = request.data + # Get the item ID from the data item_id = data.get('item') + # Get the list of location IDs from the data location_ids = data.get('locations', []) - # Retrieve the item object from the database + # Retrieve the item object or return 404 if not found item = get_object_or_404(Item, id=item_id) - # Retrieve the location objects from the database + # Retrieve the location objects for the provided IDs locations = Location.objects.filter(id__in=location_ids) # Check if the locations exist if not locations.exists(): - # Return an error if one or more locations are invalid + # Return 400 Bad Request if not return Response({'error': 'One or more locations are invalid.'}, status=status.HTTP_400_BAD_REQUEST) - # Update the rule object with the new item and locations + # Update the item associated with the rule rule.item = item + # Update the locations associated with the rule rule.locations.set(locations) # Save the updated rule object to the database rule.save() - # Serialize the rule object into JSON format + # Serialize the rule object serializer = RuleSerializer(rule) - # Return the serialized data with a 200 OK status + # Return the serialized data with 200 OK status return Response(serializer.data, status=status.HTTP_200_OK) def delete(self, request, rule_id, *args, **kwargs): - # Retrieve the existing rule object from the database + """ + Delete an existing rule for the authenticated user. + """ + # Retrieve the rule object or return 404 if not found rule = get_object_or_404(Rule, id=rule_id) + # Ensure the rule belongs to the authenticated user + if rule.user != request.user: + # Return 403 Forbidden if the user does not own the rule + return Response(status=status.HTTP_403_FORBIDDEN) # Delete the rule object from the database rule.delete() - # Return a 204 No Content status to indicate successful deletion + # Return 204 No Content status to indicate successful deletion return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/frontend/src/services/auth.js b/frontend/src/services/auth.js index b9ea689b3e08c99f24d6676f44a0d081e12f2810..866bbdca7170ff96b101f1964e07472c233b6da8 100644 --- a/frontend/src/services/auth.js +++ b/frontend/src/services/auth.js @@ -13,11 +13,26 @@ export const login = async (credentials) => { // Register function for new users export const register = async (userData) => { - const data = await obtainToken('/api/auth/register/', userData); - localStorage.setItem('isAuthenticated', true); - return data; + try { + const data = await obtainToken('/api/auth/register/', userData); + localStorage.setItem('isAuthenticated', true); + return data; + } catch (error) { + if (error.response) { + // The request was made and the server responded with a status code outside of the range of 2xx + console.error('Registration failed:', error.response.data); + } else if (error.request) { + // The request was made but no response was received + console.error('No response received:', error.request); + } else { + // Something happened in setting up the request that triggered an error + console.error('Error in registration request setup:', error.message); + } + throw error; + } }; + // Admin login function export const adminLogin = async (credentials) => { const data = await obtainToken('/api/auth/admin/login/', credentials);