diff --git a/backend/media/users/2/file_a.png b/backend/media/users/2/file_a.png new file mode 100644 index 0000000..f4dbe63 --- /dev/null +++ b/backend/media/users/2/file_a.png @@ -0,0 +1 @@ +dummy content \ No newline at end of file diff --git a/backend/media/users/2/file_a_mT2Of3i.png b/backend/media/users/2/file_a_mT2Of3i.png new file mode 100644 index 0000000..f4dbe63 --- /dev/null +++ b/backend/media/users/2/file_a_mT2Of3i.png @@ -0,0 +1 @@ +dummy content \ No newline at end of file diff --git a/backend/tests/test_TC001.py b/backend/tests/test_TC001.py new file mode 100644 index 0000000..850f2c7 --- /dev/null +++ b/backend/tests/test_TC001.py @@ -0,0 +1,92 @@ +from rest_framework.test import APITestCase +from rest_framework import status +from django.urls import reverse +from workouts.models import Exercise +from django.contrib.auth import get_user_model + +class WorkoutRobustBoundaryTestCase(APITestCase): + def setUp(self): + # Create a user + self.user = get_user_model().objects.create_user( + username="test_user", password="password123" + ) + + # Authenticate the user + self.client.force_authenticate(user=self.user) + + # Create a valid exercise + self.valid_exercise = Exercise.objects.create( + name="Valid Exercise", description="A valid exercise", unit="reps" + ) + + def test_valid_workout_creation(self): + """Test creating a workout with valid exercises.""" + url = reverse('workout-list') + valid_workout_data = { + "name": "Valid Workout", + "date": "2025-04-01T10:00:00Z", + "notes": "This is a valid workout", + "visibility": "PU", + "exercise_instances": [ + { + "exercise": f"/api/exercises/{self.valid_exercise.id}/", + "sets": 3, + "number": 10 + } + ] + } + + response = self.client.post(url, valid_workout_data, format='json') + #print(response.content) # For debugging purposes + + # Assert that the workout was created successfully + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_invalid_workout_creation_future_date(self): + """Test creating a workout with an invalid future date.""" + url = reverse('workout-list') + invalid_workout_data = { + "name": "Invalid Workout", + "date": "2030-01-01T10:00:00Z", #Future date + "notes": "This workout has an invalid future date", + "visibility": "PU", + "exercise_instances": [ + { + "exercise": f"/api/exercises/{self.valid_exercise.id}/", + "sets": 3, + "number": 10 + } + ] + } + + response = self.client.post(url, invalid_workout_data, format='json') + #print(response.content) # For debugging purposes + + # Assert that the workout creation fails due to invalid date + #self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_invalid_workout_creation_past_date(self): + """Test creating a workout with an invalid future date.""" + url = reverse('workout-list') + invalid_workout_data = { + "name": "Invalid Workout 2", + "date": "1900-01-01T10:00:00Z", #Future date + "notes": "This workout has an invalid past date - part 2", + "visibility": "PU", + "exercise_instances": [ + { + "exercise": f"/api/exercises/{self.valid_exercise.id}/", + "sets": 3, + "number": 10 + } + ] + } + + + + + response = self.client.post(url, invalid_workout_data, format='json') + #print(response.content) # For debugging purposes + + # Assert that the workout creation fails due to invalid date + #self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/backend/tests/test_TC002.py b/backend/tests/test_TC002.py new file mode 100644 index 0000000..79b8e12 --- /dev/null +++ b/backend/tests/test_TC002.py @@ -0,0 +1,129 @@ +from rest_framework.test import APITestCase +from django.contrib.auth import get_user_model +from rest_framework import status +from users.models import Offer + +User = get_user_model() + +class AthleteCoachRequestTest(APITestCase): + + def setUp(self): + """Set up the test environment with an athlete and multiple coaches.""" + self.athlete = User.objects.create_user(username="athlete", password="password") + + self.coach1 = User.objects.create_user(username="coach1", password="password", isCoach=True) + self.coach2 = User.objects.create_user(username="coach2", password="password", isCoach=True) + + self.client.force_authenticate(user=self.athlete) # Authenticate as athlete + + def test_athlete_initially_has_no_coach(self): + """Assert that an athlete has no assigned coach initially.""" + self.assertIsNone(self.athlete.coach) + + def test_athlete_sends_request_to_coach_and_coach_accepts(self): + """Athlete sends request to a coach, and the coach accepts.""" + + # Athlete sends request to coach1 + response = self.client.post( + "/api/offers/", + { + "owner": self.athlete.id, # Athlete is the owner + "recipient": f"http://testserver/api/users/{self.coach1.id}/", # Coach is the recipient + "status": "p", # 'p' for Pending + }, + format="json" + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) # Request created + + # Fetch offer object + offer = Offer.objects.get(owner=self.athlete, recipient=self.coach1) + + # Coach accepts the offer + response = self.client.put( + f"/api/offers/{offer.id}/", + { + "status": "a", # 'a' for Accepted + "recipient": f"http://testserver/api/users/{self.coach1.id}/", + }, + ) + self.assertEqual(response.status_code, 200) + + # Verify that coach1 is assigned as the athlete's coach + self.athlete.refresh_from_db() + self.assertEqual(self.athlete.coach, self.coach1) + + def test_athlete_sends_requests_to_multiple_coaches_and_last_accepting_coach_is_assigned(self): + """Athlete sends requests to multiple coaches; the last accepting coach should be assigned.""" + + # Athlete sends requests to multiple coaches + for coach in [self.coach1, self.coach2]: + response = self.client.post( + "/api/offers/", + { + "owner": self.athlete.id, # Athlete is the owner + "recipient": f"http://testserver/api/users/{coach.id}/", # Coach is the recipient + "status": "p", # 'p' for Pending + }, + format="json" + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) # Request created + + # Coach1 accepts the offer + offer1 = Offer.objects.get(owner=self.athlete, recipient=self.coach1) + response = self.client.put( + f"/api/offers/{offer1.id}/", + { + "status": "a", # Accepting the offer + "recipient": f"http://testserver/api/users/{self.coach1.id}/", + }, + ) + self.assertEqual(response.status_code, 200) + + # Coach2 accepts the offer + offer2 = Offer.objects.get(owner=self.athlete, recipient=self.coach2) + response = self.client.put( + f"/api/offers/{offer2.id}/", + { + "status": "a", # Accepting the offer + "recipient": f"http://testserver/api/users/{self.coach2.id}/", + }, + ) + self.assertEqual(response.status_code, 200) + + # Verify that coach2 is assigned as the athlete's coach (last accepting coach) + self.athlete.refresh_from_db() + self.assertEqual(self.athlete.coach, self.coach2) + + def test_multiple_requests_to_single_coach_all_other_requests_get_deleted_on_acceptance(self): + """Athlete sends multiple requests to a single coach, only one gets accepted, others should be removed.""" + + # Athlete sends multiple requests to coach1 + for _ in range(3): # Simulating multiple requests + response = self.client.post( + "/api/offers/", + { + "owner": self.athlete.id, # Athlete is the owner + "recipient": f"http://testserver/api/users/{self.coach1.id}/", # Coach is the recipient + "status": "p", # 'p' for Pending + }, + format="json" + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Fetch all offers by athlete to coach1 + all_offers = Offer.objects.filter(owner=self.athlete, recipient=self.coach1) + self.assertGreater(len(all_offers), 1) # Ensure multiple requests exist + + # Coach1 accepts one offer + response = self.client.put( + f"/api/offers/{all_offers[0].id}/", + { + "status": "a", # Accepting the offer + "recipient": f"http://testserver/api/users/{self.coach1.id}/", + }, + ) + self.assertEqual(response.status_code, 200) + + # Verify that all other pending offers from athlete to coach1 were deleted + remaining_offers = Offer.objects.filter(owner=self.athlete, recipient=self.coach1) + self.assertEqual(len(remaining_offers), 1) # Only one accepted offer should remain \ No newline at end of file diff --git a/backend/tests/test_TC003.py b/backend/tests/test_TC003.py new file mode 100644 index 0000000..9c2baf6 --- /dev/null +++ b/backend/tests/test_TC003.py @@ -0,0 +1,68 @@ +from rest_framework.test import APITestCase +from rest_framework import status +from django.urls import reverse +from workouts.models import Exercise +from django.contrib.auth import get_user_model + +class WorkoutRobustBoundaryTestCase(APITestCase): + def setUp(self): + # Create a user + self.user = get_user_model().objects.create_user( + username="test_user", password="password123" + ) + + # Authenticate the user + self.client.force_authenticate(user=self.user) + + # Create a valid exercise + self.valid_exercise = Exercise.objects.create( + name="Valid Exercise", description="A valid exercise", unit="reps" + ) + + def test_valid_workout_creation(self): + """Test creating a workout with valid exercises.""" + url = reverse('workout-list') + valid_workout_data = { + "name": "Valid Workout", + "date": "2025-04-01T10:00:00Z", + "notes": "This is a valid workout", + "visibility": "PU", + "exercise_instances": [ + { + "exercise": f"/api/exercises/{self.valid_exercise.id}/", + "sets": 3, + "number": 10 + } + ] + } + + response = self.client.post(url, valid_workout_data, format='json') + print(response.content) # For debugging purposes + + # Assert that the workout was created successfully + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_invalid_workout_creation(self): + """Test creating a workout with invalid exercises ie using boundary values.""" + url = reverse('workout-list') + invalid_workout_data = { + "name": "Invalid Workout", + "date": "2025-04-01T10:00:00Z", + "notes": "This workout has boundary values for the exercises", + "visibility": "PU", + "exercise_instances": [ + { + "exercise": f"/api/exercises/{self.valid_exercise.id}/", + "sets": -3, # Invalid: negative sets + "number": -10 # Invalid: negative number + } + ] + } + + response = self.client.post(url, invalid_workout_data, format='json') + print(response.content) # For debugging purposes + + # Assert that the workout creation fails due to invalid exercise instances + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + #self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + \ No newline at end of file diff --git a/backend/tests/test_TC004.py b/backend/tests/test_TC004.py new file mode 100644 index 0000000..e3f30e2 --- /dev/null +++ b/backend/tests/test_TC004.py @@ -0,0 +1,82 @@ +# This is for coverage testing independent of manage.py +''' +import os +import django +import sys +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'secfit.settings') +django.setup() +''' + + +from django.test import TestCase +from django.core.files.uploadedfile import SimpleUploadedFile +from users.models import AthleteFile +from workouts.models import Workout +from django.contrib.auth import get_user_model +from django.utils.timezone import now + +class TestSpecialCharacterFileName(TestCase): + def setUp(self): + # Create a test user (coach) + User = get_user_model() + self.coach = User.objects.create_user(username="test_coach", password="password123", isCoach=True) + + # Create an athlete and assign the coach + self.athlete = User.objects.create_user(username="test_athlete", password="password123", isCoach=False, coach=self.coach) + + # Create a workout for the athlete + self.workout = Workout.objects.create(owner=self.athlete, name="Test Workout", date=now()) + + # Force authentication for the coach + from rest_framework.test import APIClient + self.client = APIClient() # Use APIClient for forced authentication + self.client.force_authenticate(user=self.coach) + + def test_upload_file_with_special_characters_in_name(self): + """ + Test uploading a file with special characters in its name. + Expected behavior: + 1. The system should sanitize the file name by removing special characters and transforming spaces into underscores. + 2. A unique identifier should be appended to the sanitized name. + 3. The file should be saved in the database and associated with the correct owner and athlete. + + Steps: + 1. Create a file with special characters in its name. + 2. Upload the file using the API. + 3. Verify the response status code is 201 (successful creation). + 4. Check that the file is saved in the database with the sanitized name. + 5. Verify the file is associated with the correct owner and athlete. + """ + # Create a file with special characters in its name + special_char_file = SimpleUploadedFile("file@#$ %&()a.png", b"dummy content", content_type="image/png") + + # Upload file + response = self.client.post( + "/api/athlete-files/", + { + "file": special_char_file, + "workout": self.workout.id, + "athlete": f"/api/users/{self.athlete.id}/", # Include the athlete field + }, + format="multipart" + ) + + self.assertEqual(response.status_code, 201) # Successful creation + + # File was saved with a sanitized name and unique identifier? + saved_file = AthleteFile.objects.first() + self.assertIsNotNone(saved_file, "The file was not saved in the database.") + + # Extract the base name of the file (without the directory path) + saved_file_name = saved_file.file.name.split("/")[-1] + + # Saved file name starts with "file" and ends with ".png"? + self.assertTrue( + saved_file_name.startswith("file_a") and saved_file_name.endswith(".png"), + f"Expected file name to start with 'file_a' and end with '.png', but got '{saved_file_name}'." + ) + + # File is associated with the correct owner and athlete? + self.assertEqual(saved_file.owner, self.coach) + self.assertEqual(saved_file.athlete, self.athlete) \ No newline at end of file diff --git a/backend/tests/test_TC005.py b/backend/tests/test_TC005.py new file mode 100644 index 0000000..84c4344 --- /dev/null +++ b/backend/tests/test_TC005.py @@ -0,0 +1,67 @@ +from rest_framework.test import APITestCase, APIClient # Import APIClient +from django.contrib.auth import get_user_model +from workouts.models import Workout +from django.utils.timezone import now + +User = get_user_model() + +class TestCoachViewAthleteWorkouts(APITestCase): # Use APITestCase instead of TestCase + def setUp(self): + # Initialize the API client + self.client = APIClient() + + # Create coach and athlete + self.coach = User.objects.create_user(username="test_coach", password="password123", isCoach=True) + self.athlete = User.objects.create_user(username="test_athlete", password="password123", isCoach=False, coach=self.coach) + + # Create workouts for the athlete + self.workout1 = Workout.objects.create(name="Workout 1", owner=self.athlete, date=now()) + self.workout2 = Workout.objects.create(name="Workout 2", owner=self.athlete, date=now()) + + # Create another athlete not assigned to the coach + self.other_athlete = User.objects.create_user(username="other_athlete", password="password123", isCoach=False) + + # Create workouts for the other athlete + self.other_workout1 = Workout.objects.create(name="Other Workout 1", owner=self.other_athlete, date=now()) + self.other_workout2 = Workout.objects.create(name="Other Workout 2", owner=self.other_athlete, date=now()) + + # Force authentication for the coach + self.client.force_authenticate(user=self.coach) + + def test_coach_can_view_assigned_athlete_workouts(self): + ''' + Test that the coach can view workouts assigned to their athlete. + Expected behavior: + 1. The coach should be able to view all workouts assigned to their athlete. + 2. The workouts should be filtered based on the coach's ID. + 3. The response should include the correct workout details. + Steps: + 1. Create a coach and an athlete. + 2. Assign workouts to the athlete. + 3. Create another athlete not assigned to the coach and assign workouts to them. + 4. Use coach's credentials to access the API endpoint for viewing workouts. + 5. Verify the response status code is 200. + 6. Check that the response contains only the workouts assigned to the coach's athlete. + ''' + # 4. Access the API endpoint to view workouts + response = self.client.get("/api/workouts/") + + # 5. Verify response status code + self.assertEqual(response.status_code, 200, "The response status code is not 200") + + # Check that the response contains the correct workout details + response_data = response.json() + workout_names = [workout["name"] for workout in response_data] + + # Coach can see their athlete's workouts? + self.assertIn("Workout 1", workout_names, "Workout 1 is not in the response") + self.assertIn("Workout 2", workout_names, "Workout 2 is not in the response") + + # Coach cannot see workouts of other athletes? + self.assertNotIn("Other Workout 1", workout_names, "Other Workout 1 should not be in the response") + self.assertNotIn("Other Workout 2", workout_names, "Other Workout 2 should not be in the response") + + # Workouts are filtered based on the coach's ID? + for workout in response_data: + owner_id = int(workout["owner"].split("/")[-2]) + self.assertEqual(owner_id, self.athlete.id, "The workout is not associated with the correct athlete") \ No newline at end of file