diff --git a/backend/secfit/.coverage b/backend/secfit/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..be523e4bd8ea6d310eeb53ecd9866d8966e65080 Binary files /dev/null and b/backend/secfit/.coverage differ diff --git a/backend/secfit/requirements.txt b/backend/secfit/requirements.txt index 2f243ad9ad47a8d9d12dcdb3ae80316719788194..d3da257234e9819009e5d6c0538bc7373dcc508b 100644 --- a/backend/secfit/requirements.txt +++ b/backend/secfit/requirements.txt @@ -31,4 +31,5 @@ toml==0.10.1 urllib3==1.25.10 whitenoise==5.2.0 wrapt==1.12.1 -datetime \ No newline at end of file +datetime +coverage==5.5 \ No newline at end of file diff --git a/backend/secfit/secfit/django_heroku.py b/backend/secfit/secfit/django_heroku.py index 7f60ede9a2a2273cbc6afaf36bbb142339d69039..76218707447386e4c495d22bcb41d50b35681a4b 100644 --- a/backend/secfit/secfit/django_heroku.py +++ b/backend/secfit/secfit/django_heroku.py @@ -34,7 +34,8 @@ def settings(config, *, db_colors=False, databases=True, test_runner=False, stat # logger.info('Adding $DATABASE_URL to default DATABASE Django setting.') # Configure Django for DATABASE_URL environment variable. - config['DATABASES']['default'] = dj_database_url.config(conn_max_age=conn_max_age, ssl_require=True) + config['DATABASES']['default'] = dj_database_url.config( + conn_max_age=conn_max_age, ssl_require=True) # logger.info('Adding $DATABASE_URL to TEST default DATABASE Django setting.') @@ -65,7 +66,8 @@ def settings(config, *, db_colors=False, databases=True, test_runner=False, stat config['MIDDLEWARE_CLASSES'] = tuple( ['whitenoise.middleware.WhiteNoiseMiddleware'] + list(config['MIDDLEWARE_CLASSES'])) except KeyError: - config['MIDDLEWARE'] = tuple(['whitenoise.middleware.WhiteNoiseMiddleware'] + list(config['MIDDLEWARE'])) + config['MIDDLEWARE'] = tuple( + ['whitenoise.middleware.WhiteNoiseMiddleware'] + list(config['MIDDLEWARE'])) # Enable GZip. config['STATICFILES_STORAGE'] = 'whitenoise.storage.CompressedManifestStaticFilesStorage' diff --git a/backend/secfit/secfit/settings.py b/backend/secfit/secfit/settings.py index 8e71e6104c1976d5336a343e0c5e557c1f557f7b..4f3cb5891b89ce158b5250ef15fd9674c9c6efd8 100644 --- a/backend/secfit/secfit/settings.py +++ b/backend/secfit/secfit/settings.py @@ -64,6 +64,7 @@ INSTALLED_APPS = [ "comments.apps.CommentsConfig", "suggested_workouts.apps.SuggestedWorkoutsConfig", "corsheaders", + "django_heroku" ] @@ -147,6 +148,11 @@ REST_FRAMEWORK = { 'rest_framework.authentication.SessionAuthentication', ), } +AUTH_PASSWORD_VALIDATORS = [{ + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'OPTIONS': { + 'min_length': 8, + }}] AUTH_USER_MODEL = "users.User" diff --git a/backend/secfit/users/tests.py b/backend/secfit/users/tests.py index 7ce503c2dd97ba78597f6ff6e4393132753573f6..3696cebc68a2b871e0601118d8ee3356522d0c85 100644 --- a/backend/secfit/users/tests.py +++ b/backend/secfit/users/tests.py @@ -1,3 +1,162 @@ -from django.test import TestCase +from django.contrib.auth import get_user_model, password_validation +# from django.test import TestCase +from users.serializers import UserSerializer +from rest_framework.test import APIRequestFactory, APITestCase +from rest_framework.request import Request +from random import choice +from string import ascii_uppercase +from users.models import User +from django import forms +from rest_framework import serializers +from rest_framework.exceptions import ValidationError -# Create your tests here. + +class UserSerializerTestCase(APITestCase): + # Set up test instance of a user and serialized data of that user + def setUp(self): + self.user_attributes = { + "id": 1, + "email": "fake@email.com", + "username": "fake_user", + "phone_number": "92345678", + "country": "Norway", + "city": "Trondheim", + "street_address": "Lade Alle", + } + factory = APIRequestFactory() + request = factory.get('/') + self.test_user = get_user_model()(**self.user_attributes) + self.test_user.set_password("password") + self.serialized_user = UserSerializer( + self.test_user, context={'request': Request(request)}) + + self.serializer_data = { + "id": self.user_attributes["id"], + "email": self.user_attributes["email"], + "username": self.user_attributes["username"], + "password": 'password', + "password1": 'password', + "athletes": [], + "phone_number": self.user_attributes["phone_number"], + "country": self.user_attributes["country"], + "city": self.user_attributes["city"], + "street_address": self.user_attributes["street_address"], + "coach": "", + "workouts": [], + "coach_files": [], + "athlete_files": [], + } + self.new_serializer_data = { + "email": 'email@fake.com', + "username": 'faker', + "athletes": [], + "password": 'fuck_django', + "password1": 'fuck_django', + "phone_number": '12345678', + "country": 'Norge', + "city": 'Oslo', + "street_address": 'Mora di', + "workouts": [], + "coach_files": [], + "athlete_files": [], } + + # Test that the serializer return the expecte fields for a given user instance + + def test_contains_expected_fields(self): + serialized_data = self.serialized_user.data + self.assertEqual(set(serialized_data.keys()), set([ + "url", + "id", + "email", + "username", + "athletes", + "phone_number", + "country", + "city", + "street_address", + "coach", + "workouts", + "coach_files", + "athlete_files", + ])) + # Testing if serialized data matched the retrieved instance in the database + + def test_corresponding_id_field(self): + serialized_data = self.serialized_user.data + self.assertEqual(serialized_data[ + "id" + ], self.user_attributes['id']) + + def test_corresponding_email_field(self): + serialized_data = self.serialized_user.data + self.assertEqual(serialized_data[ + "email" + ], self.user_attributes['email']) + + def test_corresponding_username_field(self): + serialized_data = self.serialized_user.data + self.assertEqual(serialized_data[ + "username" + ], self.user_attributes['username']) + + def test_corresponding_phone_number_field(self): + serialized_data = self.serialized_user.data + self.assertEqual(serialized_data[ + "phone_number" + ], self.user_attributes['phone_number']) + + def test_corresponding_country_field(self): + serialized_data = self.serialized_user.data + self.assertEqual(serialized_data[ + "country" + ], self.user_attributes['country']) + + def test_corresponding_city_field(self): + serialized_data = self.serialized_user.data + self.assertEqual(serialized_data[ + "country" + ], self.user_attributes['country']) + + def test_corresponding_street_address_field(self): + serialized_data = self.serialized_user.data + self.assertEqual(serialized_data[ + "street_address" + ], self.user_attributes['street_address']) + + def test_create_user(self): + # Sjekker at jeg får serialisert til OrderedDict, kompleks datatype som kan bruker for å lage instans + new_serializer = UserSerializer(data=self.new_serializer_data) + self.assertTrue(new_serializer.is_valid()) + # Lage bruker + new_serializer.save() + # Sjekker at brukeren faktisk ble laget med brukernavner, 'faker' + self.assertEquals(get_user_model().objects.get( + username=self.new_serializer_data['username']).username, self.new_serializer_data['username']) + # Sjekk at resten av feltene til instansen faktisk er lik de du definerte i serializer sin data + self.assertEquals(get_user_model().objects.get( + username=self.new_serializer_data['username']).email, self.new_serializer_data['email']) + self.assertEquals(get_user_model().objects.get( + username=self.new_serializer_data['username']).street_address, self.new_serializer_data['street_address']) + self.assertEquals(get_user_model().objects.get( + username=self.new_serializer_data['username']).phone_number, self.new_serializer_data['phone_number']) + self.assertEquals(get_user_model().objects.get( + username=self.new_serializer_data['username']).country, self.new_serializer_data['country']) + self.assertEquals(get_user_model().objects.get( + username=self.new_serializer_data['username']).city, self.new_serializer_data['city']) + user_password = get_user_model().objects.get(username='faker').password + # Sjekker om plaintekst passordet matcher med den krypterte i databasen + self.assertTrue(self.new_serializer_data['password'], user_password) + + def test_validate_password(self): + with self.assertRaises(serializers.ValidationError): + UserSerializer(self.new_serializer_data).validate_password( + 'short') + + def test_valid_pasword(self): + self.new_serializer_data['password'] = '12345678910' + self.new_serializer_data['password1'] = '12345678910' + self.data = {'password': '12345678910', 'password1': '12345678910'} + user_ser = UserSerializer(instance=None, data=self.data) + # Returns the password as the value + self.assertEquals(user_ser.validate_password( + '12345678910'), self.data['password']) diff --git a/backend/secfit/workouts/permissions.py b/backend/secfit/workouts/permissions.py index 4039b9ce7bd3b54fe089c93d31157ed6e0887d2c..8ff7fcb535422ad572efe3f09351ff9b383c6224 100644 --- a/backend/secfit/workouts/permissions.py +++ b/backend/secfit/workouts/permissions.py @@ -33,9 +33,10 @@ class IsCoachAndVisibleToCoach(permissions.BasePermission): """Checks whether the requesting user is the existing object's owner's coach and whether the object (workout) has a visibility of Public or Coach. """ + # Fixed bug where the function did not check for the visibility level def has_object_permission(self, request, view, obj): - return obj.owner.coach == request.user + return obj.owner.coach == request.user and (obj.visibility == 'PU' or obj.visibility == 'CO') class IsCoachOfWorkoutAndVisibleToCoach(permissions.BasePermission): @@ -44,7 +45,10 @@ class IsCoachOfWorkoutAndVisibleToCoach(permissions.BasePermission): """ def has_object_permission(self, request, view, obj): - return obj.workout.owner.coach == request.user + # Fixed bug where the function did not check for the visibility level + return obj.workout.owner.coach == request.user and ( + obj.workout.visibility == "PU" or obj.workout.visibility == "CO" + ) class IsPublic(permissions.BasePermission): diff --git a/backend/secfit/workouts/tests.py b/backend/secfit/workouts/tests.py index 7fbbf7847f5b0f201d408d4017cc865d614e2615..7b274711fa889795d0e369a8bda3aac7d86ea0a1 100644 --- a/backend/secfit/workouts/tests.py +++ b/backend/secfit/workouts/tests.py @@ -1,6 +1,259 @@ -""" -Tests for the workouts application. -""" -from django.test import TestCase +from django.contrib.auth import get_user_model +from django.test import RequestFactory, TestCase +from workouts.permissions import IsOwner, IsOwnerOfWorkout, IsCoachAndVisibleToCoach, IsCoachOfWorkoutAndVisibleToCoach, IsPublic, IsWorkoutPublic, IsReadOnly +from django.utils import timezone +from workouts.models import Workout, ExerciseInstance, Exercise +from rest_framework.test import APIRequestFactory, APITestCase -# Create your tests here. + +class WorkoutPermissionsTestCases(TestCase): + def setUp(self): + self.owner = get_user_model()(id=1, username='bitch', email='email@email.com', phone_number='92134654', + country='Norway', city='Paradise city', street_address='Hemmelig' + ) + self.owner.save() + self.user = get_user_model()(id=2, username='balle', email='email@fake.com', phone_number='92134654', + country='Norway', city='Hmm', street_address='Hemmelig' + ) + self.user.save() + self.factory = APIRequestFactory() + self.workout = Workout.objects.create(id=1, name='Ballesnerkel', date=timezone.now(), notes='Hva vil du?', + owner=self.owner, visibility='PU' + ) + self.workout.save() + # Creating an object that has a workout instance. This object is an ExerciseInstance whichi needs an Exercise + self.exercise = Exercise.objects.create( + name="dummy_exercise", description='Dummy description', unit='rep') + self.exercise.save() + self.exercise_instance = ExerciseInstance.objects.create( + workout=self.workout, suggested_workout=None, exercise=self.exercise, sets=2, number=2) + self.exercise_instance.save() + self.request = self.factory.delete('/') + + """ + Testing IsOwner + """ + + def test_ownership_workout(self): + self.request = self.factory.delete('/') + self.request.user = self.owner + permission = IsOwner.has_object_permission( + self, request=self.request, view=None, obj=self.workout) + self.assertTrue(permission) + self.request.user = self.user + self.assertFalse(IsOwner.has_object_permission( + self, request=self.request, view=None, obj=self.workout)) + + """ + Testing IsOwnerOfWorkout + """ + + def test_is_owner_of_workout(self): + """ + First testing has_permission + """ + # Make fake request + self.request = self.factory.delete('/') + # Fake post request + fake_request_data = { + "workout": "http://127.0.0.1:8000/api/workouts/1/"} + # Fake method for request + self.request.method = 'POST' + # Fake data + self.request.data = fake_request_data + # Setting initialized user who is the owner for the workout which is going to be retrieved + self.request.user = self.owner + permission_class = IsOwnerOfWorkout + # Check has permission is working + self.assertTrue(permission_class.has_permission( + self, request=self.request, view=None)) + # Check for a user who is not owner of workout + self.request.user = self.user + self.assertFalse(permission_class.has_permission( + self, request=self.request, view=None)) + # Now check for the case where there exist no workout for the id + fake_request_data_no_workout = {} + self.request.data = fake_request_data_no_workout + self.assertFalse(permission_class.has_permission( + self, request=self.request, view=None)) + + # Should always return True for has_permission when the method is not a POST + self.request.method = 'GET' + self.assertTrue(permission_class.has_permission( + self, request=self.request, view=None)) + # Check for the case where request.user is owner + self.request.user = self.owner + self.assertTrue(permission_class.has_permission( + self, request=self.request, view=None)) + """ + Test has_object_permission + """ + self.assertTrue(permission_class.has_object_permission( + self, self.request, view=None, obj=self.exercise_instance)) + # Test for where the requested user is not the workout for the exercise instance + self.request.user = self.user + self.assertFalse(permission_class.has_object_permission( + self, self.request, view=None, obj=self.exercise_instance)) + + """ + Testing IsCoachAndVisibleToCoach + """ + + def test_is_coach_and_visible_to_coach(self): + # Make a coach to the owner of workout defined in setUp + self.coach_of_owner = get_user_model()(id=3, username='coach_of_owner', email='email@owner.com', phone_number='98154654', + country='England', city='London', street_address='...' + ) + self.coach_of_owner.save() + self.owner.coach = self.coach_of_owner + self.owner.save() + print(self.owner.coach) + self.request.user = self.coach_of_owner + permission_class = IsCoachAndVisibleToCoach + self.assertTrue(IsCoachAndVisibleToCoach.has_object_permission( + self, request=self.request, view=None, obj=self.workout)) + # Changing the visibility to coach to see if it still works + self.workout.visibility = 'CO' + self.workout.save() + self.assertTrue(IsCoachAndVisibleToCoach.has_object_permission( + self, request=self.request, view=None, obj=self.workout)) + # Changing request.user to someoner who is not the owner's coach + self.request.user = self.user + self.assertFalse(IsCoachAndVisibleToCoach.has_object_permission( + self, request=self.request, view=None, obj=self.workout)) + # Check if you get the same result when visibility is set to public and requested user is still not coach of owner + self.workout.visibility = 'PU' + self.workout.save() + self.assertFalse(self.assertFalse(IsCoachAndVisibleToCoach.has_object_permission( + self, request=self.request, view=None, obj=self.workout))) + # Now, check if the function returns false when visibility is set to private + # for both cases where requested user is coach or not coach of owner + self.workout.visibility = 'PR' + self.workout.save() + self.assertFalse(IsCoachAndVisibleToCoach.has_object_permission( + self, request=self.request, + view=None, obj=self.workout)) + # Changing requested user back to coach. Should still return false + self.request.user = self.coach_of_owner + self.assertFalse(IsCoachAndVisibleToCoach.has_object_permission(self, request=self.request, + view=None, obj=self.workout)) + + # This test fails. Had to fix the fault in the permission class + + """ + This one test if the function, IsCoachOfWorkoutAndVisibleToCoach + """ + + def test_coach_of_workout_and_visible_to_coach(self): + """ + Testing for the exercise_instance instead of the workout directly + """ + permission_class = IsCoachOfWorkoutAndVisibleToCoach + # Make a coach to the owner of workout defined in setUp + self.coach_of_owner = get_user_model()(id=4, username='coach_of_owner2', email='email@owner.com', phone_number='98154654', + country='England', city='London', street_address='...' + ) + self.coach_of_owner.save() + self.owner.coach = self.coach_of_owner + self.owner.save() + # Check if false when requesting user is not the owner's coach + self.request.user = self.user + self.assertFalse(permission_class.has_object_permission( + self, request=self.request, view=None, obj=self.exercise_instance)) + + self.request.user = self.coach_of_owner + self.assertTrue(permission_class.has_object_permission( + self, request=self.request, view=None, obj=self.exercise_instance)) + # Changing the visibility to coach to see if it still works + self.workout.visibility = 'CO' + self.workout.save() + self.assertTrue(permission_class.has_object_permission( + self, request=self.request, view=None, obj=self.exercise_instance)) + # Changing request.user to someoner who is not the owner's coach + self.request.user = self.user + self.assertFalse(permission_class.has_object_permission( + self, request=self.request, view=None, obj=self.exercise_instance)) + # Check if you get the same result when visibility is set to public and requested user is still not coach of owner + self.workout.visibility = 'PU' + self.workout.save() + self.assertFalse(self.assertFalse(permission_class.has_object_permission( + self, request=self.request, view=None, obj=self.exercise_instance))) + # Now, check if the function returns false when visibility is set to private + # for both cases where requested user is coach or not coach of owner + self.workout.visibility = 'PR' + self.workout.save() + self.assertFalse(permission_class.has_object_permission( + self, request=self.request, + view=None, obj=self.exercise_instance)) + # Changing requested user back to coach. Should still return false + self.request.user = self.coach_of_owner + self.assertFalse(permission_class.has_object_permission(self, request=self.request, + view=None, obj=self.exercise_instance)) + + # This test fails. Had to fix the fault in the permission class + """ + Testing IsPublic + """ + + def test_is_public(self): + permission_class = IsPublic + self.workout.visibility = 'PU' + self.assertTrue(permission_class.has_object_permission( + self, request=self.request, view=None, obj=self.workout)) + """ + Other visibility levels should return false + """ + self.workout.visibility = 'CO' + self.assertFalse(permission_class.has_object_permission( + self, request=self.request, view=None, obj=self.workout)) + self.workout.visibility = 'PR' + self.assertFalse(permission_class.has_object_permission( + self, request=self.request, view=None, obj=self.workout)) + """ + Testing IsWorkoutPublic using exercise_instance as the object which has a relation to a workout + """ + + def test_is_workout_public(self): + permission_class = IsWorkoutPublic + self.workout.visibility = 'PU' + self.assertTrue(permission_class.has_object_permission( + self, request=None, view=None, obj=self.exercise_instance)) + self.workout.visibility = 'CO' + self.assertFalse(permission_class.has_object_permission( + self, request=None, view=None, obj=self.exercise_instance)) + self.workout.visibility = 'PR' + self.assertFalse(permission_class.has_object_permission( + self, request=None, view=None, obj=self.exercise_instance)) + + """ + Testing IsReadOnly + """ + + def test_is_read_only(self): + permission_class = IsReadOnly + """ + Testing if false when unsafe methods are provided + """ + self.request.method = 'POST' + self.assertFalse(permission_class.has_object_permission( + self, request=self.request, view=None, obj=None)) + self.request.method = 'PUT' + self.assertFalse(permission_class.has_object_permission( + self, request=self.request, view=None, obj=None)) + self.request.method = 'DELETE' + self.assertFalse(permission_class.has_object_permission( + self, request=self.request, view=None, obj=None)) + """ + Testing if safe methods return true + """ + self.request.method = 'HEAD' + self.assertTrue(permission_class.has_object_permission( + self, request=self.request, view=None, obj=None)) + + self.request.method = 'GET' + self.assertTrue(permission_class.has_object_permission( + self, request=self.request, view=None, obj=None)) + + self.request.method = 'OPTIONS' + self.assertTrue(permission_class.has_object_permission( + self, request=self.request, view=None, obj=None))