diff --git a/MisplaceAI/rules/__pycache__/views.cpython-310.pyc b/MisplaceAI/rules/__pycache__/views.cpython-310.pyc index 42d382c54f46045ba5c3e96a83735586c16ecf95..22744b11f4ec44052d2c2cd9e304390e2ea764c1 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/tests/test_items.py b/MisplaceAI/rules/tests/test_items.py index a16cab37f47cd58c23de999ae41840e4505dc74d..080f8b3a994b3177c3df9f2c5f145ec8c0f66077 100644 --- a/MisplaceAI/rules/tests/test_items.py +++ b/MisplaceAI/rules/tests/test_items.py @@ -11,113 +11,146 @@ Test 7: test_update_item_as_normal_user - Test that a normal user cannot update 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. +Test 11: test_create_duplicate_item_as_admin - Test that creating a duplicate item as admin returns an error. +Test 12: test_partial_update_item_as_admin - Test that an admin user can perform partial updates on an item. +Test 13: test_unauthenticated_access - Test that unauthenticated users cannot access any item endpoints. """ -from rest_framework.test import APITestCase + from rest_framework import status -from django.urls import reverse +from rest_framework.reverse import reverse +from rest_framework.test import APITestCase 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') + """ + Set up test data for the test cases. + """ + self.admin_user = User.objects.create_superuser(username='admin', password='password') + self.normal_user = User.objects.create_user(username='user', password='password') + self.item = Item.objects.create(name='Test Item') + + self.url = reverse('rules:admin_manage_item') + self.detail_url = lambda item_id: reverse('rules:admin_manage_item_detail', args=[item_id]) def test_create_item_as_admin(self): """ - Ensure admin users can create a new item. + Test 1: test_create_item_as_admin - Test that an admin user 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') + response = self.client.post(self.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. + Test 2: test_create_item_as_normal_user - Test that a normal user 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') + response = self.client.post(self.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. + Test 3: test_delete_item_as_admin - Test that an admin user 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) + response = self.client.delete(self.detail_url(self.item.id)) 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. + Test 4: test_delete_item_as_normal_user - Test that a normal user 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) + response = self.client.delete(self.detail_url(self.item.id)) 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. + Test 5: test_get_items_as_normal_user - Test that a normal user 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) + response = self.client.get(self.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. + Test 6: test_update_item_as_admin - Test that an admin user 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') + response = self.client.put(self.detail_url(self.item.id), 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. + Test 7: test_update_item_as_normal_user - Test that a normal user 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') + response = self.client.put(self.detail_url(self.item.id), data, format='json') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - + def test_create_item_with_invalid_data(self): + """ + Test 8: test_create_item_with_invalid_data - Test that creating an item with invalid data returns validation errors. + """ 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 + data = {'name': ''} + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_get_item_detail_as_admin(self): + """ + Test 9: test_get_item_detail_as_admin - Test that an admin user can retrieve the details of a specific item. + """ 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 + response = self.client.get(self.detail_url(self.item.id)) + self.assertEqual(response.status_code, status.HTTP_200_OK) def test_get_item_detail_as_normal_user(self): + """ + Test 10: test_get_item_detail_as_normal_user - Test that a normal user can retrieve the details of a specific item. + """ 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 + response = self.client.get(self.detail_url(self.item.id)) + self.assertEqual(response.status_code, status.HTTP_200_OK) - + def test_create_duplicate_item_as_admin(self): + """ + Test 11: test_create_duplicate_item_as_admin - Test that creating a duplicate item as admin returns an error. + """ + self.client.force_authenticate(user=self.admin_user) + data = {'name': 'Test Item'} + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_partial_update_item_as_admin(self): + """ + Test 12: test_partial_update_item_as_admin - Test that an admin user can perform partial updates on an item. + """ + self.client.force_authenticate(user=self.admin_user) + data = {'name': 'Partially Updated Item'} + response = self.client.patch(self.detail_url(self.item.id), data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + updated_item = Item.objects.get(id=self.item.id) + self.assertEqual(updated_item.name, 'Partially Updated Item') + + + def test_unauthenticated_access(self): + """ + Test 13: test_unauthenticated_access - Test that unauthenticated users cannot access any item endpoints. + """ + data = {'name': 'Unauthenticated Item'} + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + response = self.client.put(self.detail_url(self.item.id), data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + response = self.client.delete(self.detail_url(self.item.id)) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/MisplaceAI/rules/tests/test_locations.py b/MisplaceAI/rules/tests/test_locations.py index 5c1ee11a63cced3b4b30ac4f219f4149fe2c5714..32b210ea0a76fa643a3acd6f727b08ef2abff933 100644 --- a/MisplaceAI/rules/tests/test_locations.py +++ b/MisplaceAI/rules/tests/test_locations.py @@ -12,6 +12,8 @@ Test 7: test_update_location_as_normal_user - Test that a normal user cannot upd 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. +Test 11: test_partial_update_location_as_admin - Test that an admin user can perform partial updates on a location. +Test 12: test_partial_update_location_as_normal_user - Test that a normal user cannot perform partial updates on a location. """ @@ -27,68 +29,70 @@ class AdminManageLocationViewTest(APITestCase): """ def setUp(self): + """ + Set up the test environment. + """ 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') + self.url = reverse('rules:admin_manage_location') + self.detail_url = lambda location_id: reverse('rules:admin_manage_location_detail', args=[location_id]) def test_create_location_as_admin(self): """ - Ensure admin users can create a new location. + Test 1: 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') + response = self.client.post(self.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. + Test 2: 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') + response = self.client.post(self.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. + Test 3: 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]) + url = self.detail_url(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. + Test 4: 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]) + url = self.detail_url(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. + Test 5: 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) + response = self.client.get(self.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. + Test 6: 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]) + url = self.detail_url(self.location.id) data = {'name': 'Updated Location'} response = self.client.put(url, data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -96,28 +100,60 @@ class AdminManageLocationViewTest(APITestCase): def test_update_location_as_normal_user(self): """ - Ensure normal users cannot update an existing location. + Test 7: 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]) + url = self.detail_url(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): + """ + Test 8: Ensure creating a location with invalid data returns validation errors. + """ 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 + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_get_location_detail_as_admin(self): + """ + Test 9: Ensure admin users can retrieve the details of a specific location. + """ self.client.force_authenticate(user=self.admin_user) - url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + url = self.detail_url(self.location.id) response = self.client.get(url) - assert response.status_code == status.HTTP_200_OK + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['name'], 'Test Location') def test_get_location_detail_as_normal_user(self): + """ + Test 10: Ensure normal users can retrieve the details of a specific location. + """ self.client.force_authenticate(user=self.normal_user) - url = reverse('rules:admin_manage_location_detail', args=[self.location.id]) + url = self.detail_url(self.location.id) response = self.client.get(url) - assert response.status_code == status.HTTP_200_OK + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['name'], 'Test Location') + + def test_partial_update_location_as_admin(self): + """ + Test 11: Ensure admin users can perform partial updates on a location. + """ + self.client.force_authenticate(user=self.admin_user) + url = self.detail_url(self.location.id) + data = {'name': 'Partially Updated Location'} + response = self.client.patch(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(Location.objects.get(id=self.location.id).name, 'Partially Updated Location') + + def test_partial_update_location_as_normal_user(self): + """ + Test 12: Ensure normal users cannot perform partial updates on a location. + """ + self.client.force_authenticate(user=self.normal_user) + url = self.detail_url(self.location.id) + data = {'name': 'Partially Updated Location'} + response = self.client.patch(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/MisplaceAI/rules/views.py b/MisplaceAI/rules/views.py index c7857ab8fe978a9e5655c6ee14a9f711728dda86..7a514213f778de311db9710e4343e4f11f65a80f 100644 --- a/MisplaceAI/rules/views.py +++ b/MisplaceAI/rules/views.py @@ -22,12 +22,20 @@ class UserListView(APIView): 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. + """ # Only authenticated users can access this view permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): + """ + Retrieve a list of items for the authenticated user. + """ # Retrieve all items from the database, ordered by name items = Item.objects.all().order_by('name') # Serialize the item objects @@ -37,6 +45,9 @@ class AdminManageItemView(APIView): @method_decorator(admin_required) def post(self, request, *args, **kwargs): + """ + Create a new item. + """ # Deserialize the request data into an ItemSerializer serializer = ItemSerializer(data=request.data) # Check if the data is valid @@ -50,6 +61,9 @@ class AdminManageItemView(APIView): @method_decorator(admin_required) def put(self, request, item_id, *args, **kwargs): + """ + Update an existing item. + """ # Retrieve the existing item or return 404 if not found item = get_object_or_404(Item, id=item_id) # Deserialize the request data into an ItemSerializer @@ -62,9 +76,24 @@ class AdminManageItemView(APIView): return Response(serializer.data, status=status.HTTP_200_OK) # Return the errors with a 400 Bad Request status return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @method_decorator(admin_required) + def patch(self, request, item_id, *args, **kwargs): + """ + Partially update an existing item. + """ + item = get_object_or_404(Item, id=item_id) + serializer = ItemSerializer(item, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @method_decorator(admin_required) def delete(self, request, item_id, *args, **kwargs): + """ + Delete an existing item. + """ # Retrieve the existing item or return 404 if not found item = get_object_or_404(Item, id=item_id) # Delete the item from the database @@ -72,53 +101,71 @@ class AdminManageItemView(APIView): # 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 locations from the database, ordered by name - locations = Location.objects.all().order_by('name') - # Serialize the location objects - serializer = LocationSerializer(locations, many=True) - # Return the serialized data with a 200 OK status + def get(self, request, location_id=None, *args, **kwargs): + """ + Retrieve a list of locations or a specific location. + """ + if location_id: + # Retrieve a specific location + location = get_object_or_404(Location, id=location_id) + serializer = LocationSerializer(location) + else: + # Retrieve all locations + locations = Location.objects.all().order_by('name') + serializer = LocationSerializer(locations, many=True) return Response(serializer.data, status=status.HTTP_200_OK) @method_decorator(admin_required) def post(self, request, *args, **kwargs): - # Deserialize the request data into a LocationSerializer + """ + Create a new location. + """ serializer = LocationSerializer(data=request.data) - # Check if the data is valid if serializer.is_valid(): - # 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 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 or return 404 if not found + """ + Update an existing location. + """ location = get_object_or_404(Location, id=location_id) - # Deserialize the request data into a LocationSerializer serializer = LocationSerializer(location, data=request.data) - # Check if the data is valid if serializer.is_valid(): - # 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 the errors with a 400 Bad Request status + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @method_decorator(admin_required) + def patch(self, request, location_id, *args, **kwargs): + """ + Partially update an existing location. + """ + location = get_object_or_404(Location, id=location_id) + serializer = LocationSerializer(location, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) 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 or return 404 if not found + """ + Delete an existing location. + """ location = get_object_or_404(Location, id=location_id) - # 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)