From 9f11b40117295def55fe4da0cefa1850d4a9a3f9 Mon Sep 17 00:00:00 2001 From: a2-imeri <Alfret2.imeri@live.uwe.ac.uk> Date: Wed, 10 Jul 2024 10:25:26 +0100 Subject: [PATCH] Add Tests for Rules --- MisplaceAI/MisplaceAI/test_settings.py | 2 + .../__pycache__/views.cpython-310.pyc | Bin 2744 -> 3121 bytes MisplaceAI/authentication/views.py | 20 +- .../rules/__pycache__/views.cpython-310.pyc | Bin 5272 -> 5607 bytes MisplaceAI/rules/decorators.py | 16 ++ MisplaceAI/rules/tests/test_items.py | 123 ++++++++ MisplaceAI/rules/tests/test_locations.py | 123 ++++++++ MisplaceAI/rules/tests/test_permissions.py | 145 ---------- MisplaceAI/rules/tests/test_rules.py | 266 ++++++++++++++++++ MisplaceAI/rules/tests/test_views.py | 182 ------------ MisplaceAI/rules/views.py | 203 +++++++------ frontend/src/services/auth.js | 21 +- 12 files changed, 684 insertions(+), 417 deletions(-) create mode 100644 MisplaceAI/rules/decorators.py create mode 100644 MisplaceAI/rules/tests/test_items.py create mode 100644 MisplaceAI/rules/tests/test_locations.py delete mode 100644 MisplaceAI/rules/tests/test_permissions.py create mode 100644 MisplaceAI/rules/tests/test_rules.py delete mode 100644 MisplaceAI/rules/tests/test_views.py diff --git a/MisplaceAI/MisplaceAI/test_settings.py b/MisplaceAI/MisplaceAI/test_settings.py index f1f30e9..6d795d3 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 GIT binary patch delta 1205 zcmdlXx>15JpO=@5fq{WR&Y~~Pm472&5@Wp*0|P@ULlk2QLljdga~8`2)`bjFY^m%~ z94U+`Of3vioGHw~44N#rc!E;XGmA@7i^4Kf%afTv#y~L#0|NudSbqivhT;we28J4j zEQT7!EXEYZUdCFMD)}0gET$Bu6y{z=Mur-e1<VT>YFSHI7O>W^E{tcaWh-GTVXt9p zW(1L09L<a=EOVGjIJ3A?SbLem8G;#D7)rP+of#Qw*%=wC1WVXbK<e3>88aD6IJ0;l zs@NDJ8B!Q(+3VPYp>#Q8IfEwq<Z5Qw`jt$#*o#uric*U+Zm}gMC#Mz{uVgG@VPIgm z#hO}FlwYLDdy6kUwIsecwJ0+&C$lQGsE7|F%aK_eUzV7YnR1JzII%3Xh#f4LmR}TK zTAW%`#0ugv7nc;>;(?eNUy`4lng=miz#}9iAl}HpFkZ+x$kj2#)#VnaYejNuK}lwQ z-sFB3F(yrp$;(*`xP?HbFs80#C{mkj#-!+Si!He*HL)aB7!;gx3=9l>j7*F?j2w(y zj7*Gdj2w(C|5%tg{_?RXF>*98{jU;6ju<_N-pMB!)hGXCG%+mFU|?V<QbQ%wL0ldN z1_pkR6`<(jVBlh8VdP;d;+b5?Dl@s8>7zjrF9QQZND)ZMEe?=(^AdAYZ*deP78jT2 z7p1IZDl!5YV+<lh85kHQ?`M`aC=vm21sNC^s)RlBz(J>wT$GxUnpcvUm{Y9DUj#}h z;J_^s2WgULaf>OE1Zk225z?Tr;D&~rLA<|r5hwswvKPsNWEDV!B8UK`+9G8TO9e!L zf)L4*AphNBDabD_QJMUa#m-F=q(%!wXoJjWkB?8uPmYf-vH}TV3mZcQ1_qF2pV=4~ z7&sVM7<rhB3?~<|$}$>E?q|KKga{g>KrmroV9;bK0=q?cavPfh8#pjTC$C}4jshho zu#byCIj6`JWV0EF03|6%0Dw|c5j+&YiKxf|qy!WMMV25I!mS`Lg57U7xscsa!x~i< ztPZTij)8#z>`hyc3OlGDez5Q5069*Rqlj<vUJg^6B3Y0$$RbTvq_B1b1s5wMQLBPf zfDHx{VADY+hfg-*)Wz)p2L=X)V31a@0~{u|b3T&?1#c0^Wkn!aq=<u<Jh_KUomEgk HN<<O>+OPRG delta 865 zcmdleu|t$EpO=@5fq{Wxxm-uu6yA+|NsN(l3=9mZ3{i|J3{gxej8V*~ELp4z*cLKG zv8QrGai%Z@GiWm3;t5Jk&nzxUEegv_Ew57LQczG(05KKP@{1Hoi&Kjfiognr5=%1k z^Yp;-leaLdv)^Kmk59=@o_v$pjB_PJkthQLLy_oYe`ZCoA|3_?27U$xhGGr|1_llW z7Dg^+4#v%PEc}d<{h4IcZ*hRk%}dNpy~R<GSX^A5UzD<vsYn)N0?5s`SW}CN@{1<d zvP!$IWGrG~U|`T>y2W0UnpTuroN<dSF*!N4xVTE#Gp{T$Co@GMxhORyHLoNyF{fCQ zzX%ixMN$k547WHki{rs6CZAw+bGpTmmR}SPviTNsaY@lF9*8mVCHdK@dAGPdLP7%K zjSLLp{k?CAfH@`x2JudgF7ZLGfuXL!AuHJ@i!d8VDS=!G@hpfX2C|5yAiuaoYH}8v zof9uekPk$F0-#6$#4-U92rXb1NMDfz0|Nty@tF-8PCU#-5|bTRWSEM?C-bmh^^jy> zU<fIa2I+@~PLVt)JXngr4pC-cV2A?wGchGMGw+r_W^sICX-NhsG?EibQd5c)L3$M? zYjR{qfx@In1;l3p5vm|U4Mebm2n`Uy3L-Q?gcgVZg?y1Vh@}G}bU_5z^<V<z@*<Gm zOeVkMaMUnGl|?8qXJBA307;sG2y>YKN;&s(fLy1^Q6x3lip$jD7N=`Pa%ur6nH7QT z(_}5u1L@EQ5mumZW`)ERV=6SYfh6Fr@SVJxOBc5*EEyOWJV6@3uCScM{Y)Md{6!$w W6@es?%!IgoGAoZdE4zS{h$H~b!@LOq diff --git a/MisplaceAI/authentication/views.py b/MisplaceAI/authentication/views.py index 554a474..f67e2f3 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 GIT binary patch literal 5607 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 bF(|k3F!3-Vpa`P`BM+khlK_u^p@<m(KXRYA delta 2790 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 diff --git a/MisplaceAI/rules/decorators.py b/MisplaceAI/rules/decorators.py new file mode 100644 index 0000000..11b799a --- /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 0000000..a16cab3 --- /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 0000000..5c1ee11 --- /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 8286aee..0000000 --- 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 0000000..33cdab6 --- /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 e501fe6..0000000 --- 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 94223ea..c7857ab 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 b9ea689..866bbdc 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); -- GitLab