From 6032d04b7bde178c162475dd5a0eafad5617dc80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20Haugen?= Date: Thu, 3 Apr 2025 02:25:34 +0200 Subject: [PATCH] docs: enhance test documentation and improve test structure - Added comprehensive docstrings, improved test class and method documentation, standardized test setup and assertions, added clear expected outcomes --- backend/tests/test_comments.py | 129 +++++++++++++++++++++++++ backend/tests/test_media.py | 170 +++++++++++++++++++++++++++++++++ backend/tests/test_users.py | 105 ++++++++++++++++++++ backend/tests/test_workouts.py | 145 ++++++++++++++++++++++++++++ 4 files changed, 549 insertions(+) create mode 100644 backend/tests/test_comments.py create mode 100644 backend/tests/test_media.py create mode 100644 backend/tests/test_users.py create mode 100644 backend/tests/test_workouts.py diff --git a/backend/tests/test_comments.py b/backend/tests/test_comments.py new file mode 100644 index 0000000..a891cb0 --- /dev/null +++ b/backend/tests/test_comments.py @@ -0,0 +1,129 @@ +""" +Test suite for comment submission functionality. + +This module contains tests that verify: +1. Valid comment submission +2. Input validation for comment content +3. Workout association validation +4. Access control for private workouts +5. Comment visibility rules + +Each test case includes: +- Setup: Required data and conditions +- Action: The operation being tested +- Assertion: Expected outcomes and validations +""" + +import pytest +from rest_framework.test import APIClient +from django.contrib.auth import get_user_model +from django.urls import reverse +from workouts.models import Workout +from comments.models import Comment +from datetime import datetime +from django.test import TestCase +from rest_framework import status + +@pytest.mark.django_db +class TestCommentSubmission(APITestCase): + """ + Test cases for comment submission endpoints. + + Tests cover: + - Successful comment submission + - Validation of required fields + - Workout association validation + - Access control for private workouts + - Comment visibility rules + """ + + def setUp(self): + """Set up test data and URLs.""" + self.client = APIClient() + self.url = reverse('comment-list') + + # Create a test user + User = get_user_model() + self.user = User.objects.create_user( + username='testuser', + email='test@example.com', + password='ValidPass123!' + ) + + # Create a test workout + self.workout = Workout.objects.create( + name="Test Workout", + date=datetime.now(), + notes="Test notes", + owner=self.user, + visibility="CO" + ) + + # Authenticate the client + self.client.force_authenticate(user=self.user) + self.comment_data = { + 'content': 'Test comment', + 'workout': self.workout.id + } + + def test_valid_comment_submission(self): + """ + Test successful comment submission with valid data. + + Expected: + - 201 Created status code + - Comment is created in database + - Response contains comment data with correct associations + """ + response = self.client.post(self.url, self.comment_data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(Comment.objects.filter(content='Test comment').exists()) + self.assertEqual(response.data['workout'], self.workout.id) + + def test_invalid_comment_submission_empty_content(self): + """ + Test comment submission with empty content. + + Expected: + - 400 Bad Request status code + - Error message about missing content + """ + data = self.comment_data.copy() + data['content'] = '' + response = self.client.post(self.url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('content', response.data) + + def test_invalid_comment_submission_no_workout(self): + """ + Test comment submission without workout association. + + Expected: + - 400 Bad Request status code + - Error message about missing workout + """ + data = self.comment_data.copy() + del data['workout'] + response = self.client.post(self.url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('workout', response.data) + + def test_comment_submission_on_private_workout(self): + """ + Test comment submission on a private workout. + + Expected: + - 403 Forbidden status code + - Error message about insufficient permissions + """ + private_workout = Workout.objects.create( + name="Private Workout", + date=datetime.now(), + notes="Private notes", + owner=self.user, + visibility="PR" + ) + data = self.comment_data.copy() + data['workout'] = private_workout.id + response = self.client.post(self.url, data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) \ No newline at end of file diff --git a/backend/tests/test_media.py b/backend/tests/test_media.py new file mode 100644 index 0000000..ec7cbb6 --- /dev/null +++ b/backend/tests/test_media.py @@ -0,0 +1,170 @@ +""" +Test suite for media upload functionality. + +This module contains tests that verify: +1. Valid media file uploads +2. File size validation +3. File type validation +4. Owner permissions +5. Access control for private workouts + +Each test case includes: +- Setup: Required data and conditions +- Action: The operation being tested +- Assertion: Expected outcomes and validations +""" + +import pytest +from rest_framework.test import APIClient +from django.contrib.auth import get_user_model +from django.urls import reverse +from workouts.models import Workout, WorkoutFile +from django.core.files.uploadedfile import SimpleUploadedFile +from datetime import datetime +import os +from django.test import TestCase +from rest_framework import status + +@pytest.mark.django_db +class TestMediaUpload(APITestCase): + """ + Test cases for media upload endpoints. + + Tests cover: + - Successful file uploads + - File size restrictions + - File type validation + - Owner permissions + - Access control for private workouts + """ + + def setUp(self): + """Set up test data and URLs.""" + self.client = APIClient() + self.url = reverse('workout-file-list') + + # Create a test user + User = get_user_model() + self.user = User.objects.create_user( + username='testuser', + email='test@example.com', + password='ValidPass123!' + ) + + # Create a test workout + self.workout = Workout.objects.create( + name="Test Workout", + date=datetime.now(), + notes="Test notes", + owner=self.user, + visibility="CO" + ) + + # Authenticate the client + self.client.force_authenticate(user=self.user) + + self.test_file = SimpleUploadedFile( + "test.txt", + b"test content", + content_type="text/plain" + ) + self.large_file = SimpleUploadedFile( + "large.txt", + b"x" * (6 * 1024 * 1024), # 6MB file + content_type="text/plain" + ) + self.invalid_file = SimpleUploadedFile( + "test.exe", + b"test content", + content_type="application/x-msdownload" + ) + + def test_valid_media_upload(self): + """ + Test successful media file upload. + + Expected: + - 201 Created status code + - File is uploaded and associated with workout + - Response contains file data + """ + data = { + 'file': self.test_file, + 'workout': self.workout.id + } + response = self.client.post(self.url, data, format='multipart') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(WorkoutFile.objects.filter(workout=self.workout).exists()) + self.assertIn('file', response.data) + + def test_invalid_media_upload_no_file(self): + """ + Test upload attempt without file. + + Expected: + - 400 Bad Request status code + - Error message about missing file + """ + data = {'workout': self.workout.id} + response = self.client.post(self.url, data, format='multipart') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('file', response.data) + + def test_invalid_media_upload_large_file(self): + """ + Test upload attempt with file exceeding size limit. + + Expected: + - 400 Bad Request status code + - Error message about file size limit + """ + data = { + 'file': self.large_file, + 'workout': self.workout.id + } + response = self.client.post(self.url, data, format='multipart') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('file', response.data) + + def test_invalid_media_upload_wrong_owner(self): + """ + Test upload attempt for workout owned by another user. + + Expected: + - 403 Forbidden status code + - Error message about insufficient permissions + """ + other_user = get_user_model().objects.create_user( + username='otheruser', + email='other@example.com', + password='ValidPass123!' + ) + other_workout = Workout.objects.create( + name="Other Workout", + date=datetime.now(), + notes="Other notes", + owner=other_user, + visibility="CO" + ) + data = { + 'file': self.test_file, + 'workout': other_workout.id + } + response = self.client.post(self.url, data, format='multipart') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_invalid_media_upload_extension(self): + """ + Test upload attempt with invalid file extension. + + Expected: + - 400 Bad Request status code + - Error message about invalid file type + """ + data = { + 'file': self.invalid_file, + 'workout': self.workout.id + } + response = self.client.post(self.url, data, format='multipart') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('file', response.data) \ No newline at end of file diff --git a/backend/tests/test_users.py b/backend/tests/test_users.py new file mode 100644 index 0000000..49b5220 --- /dev/null +++ b/backend/tests/test_users.py @@ -0,0 +1,105 @@ +""" +Test suite for user registration and authentication functionality. + +This module contains tests that verify: +1. Valid user registration with different roles (athlete/coach) +2. Input validation for registration fields +3. Password strength requirements +4. Specialism field handling for coaches +5. Authentication and authorization + +Each test case includes: +- Setup: Required data and conditions +- Action: The operation being tested +- Assertion: Expected outcomes and validations +""" + +import pytest +from rest_framework.test import APIClient +from django.contrib.auth import get_user_model +from django.urls import reverse +from django.test import TestCase +from rest_framework import status + +@pytest.mark.django_db +class TestUserRegistration(APITestCase): + """ + Test cases for user registration endpoint. + + Tests cover: + - Successful registration for both athletes and coaches + - Validation of required fields + - Password requirements and validation + - Specialism field handling for coaches + - Error handling for invalid inputs + """ + + def setUp(self): + """Set up test data and URLs.""" + self.client = APIClient() + self.register_url = reverse('user-register') + self.user_data = { + 'username': 'testuser', + 'email': 'test@example.com', + 'password': 'TestPass123!', + 'password2': 'TestPass123!', + 'isCoach': False, + 'specialism': '' + } + + def test_valid_user_registration(self): + """ + Test successful user registration with valid data. + + Expected: + - 201 Created status code + - User is created in database + - Response contains user data (excluding password) + """ + response = self.client.post(self.register_url, self.user_data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(get_user_model().objects.filter(username="testuser").exists()) + self.assertNotIn('password', response.data) + + def test_invalid_user_registration_missing_username(self): + """ + Test registration with missing username. + + Expected: + - 400 Bad Request status code + - Error message about missing username + """ + data = self.user_data.copy() + del data['username'] + response = self.client.post(self.register_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('username', response.data) + + def test_invalid_user_registration_password_mismatch(self): + """ + Test registration with mismatched passwords. + + Expected: + - 400 Bad Request status code + - Error message about password mismatch + """ + data = self.user_data.copy() + data['password2'] = 'DifferentPass123!' + response = self.client.post(self.register_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('password', response.data) + + def test_invalid_user_registration_weak_password(self): + """ + Test registration with weak password. + + Expected: + - 400 Bad Request status code + - Error message about password requirements + """ + data = self.user_data.copy() + data['password'] = 'weak' + data['password2'] = 'weak' + response = self.client.post(self.register_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('password', response.data) \ No newline at end of file diff --git a/backend/tests/test_workouts.py b/backend/tests/test_workouts.py new file mode 100644 index 0000000..5fb09b0 --- /dev/null +++ b/backend/tests/test_workouts.py @@ -0,0 +1,145 @@ +""" +Test suite for workout management functionality. + +This module contains tests that verify: +1. Workout creation with valid and invalid data +2. Workout editing and updates +3. Workout visibility and access control +4. Date validation and formatting +5. Owner permissions and restrictions + +Each test case includes: +- Setup: Required data and conditions +- Action: The operation being tested +- Assertion: Expected outcomes and validations +""" + +import pytest +from rest_framework.test import APIClient +from django.contrib.auth import get_user_model +from django.urls import reverse +from workouts.models import Workout, Exercise, ExerciseInstance +from datetime import datetime, timedelta +from django.test import TestCase +from rest_framework import status + +@pytest.mark.django_db +class TestWorkoutManagement(APITestCase): + """ + Test cases for workout management endpoints. + + Tests cover: + - Workout creation with valid data + - Input validation for required fields + - Date format validation + - Workout editing functionality + - Visibility and access control + - Owner permissions + """ + + def setUp(self): + """Set up test data and URLs.""" + self.user = get_user_model().objects.create_user( + username='testuser', + password='TestPass123!' + ) + self.client.force_authenticate(user=self.user) + self.workout_list_url = reverse('workout-list') + self.workout_data = { + 'name': 'Test Workout', + 'description': 'Test Description', + 'date': '2025-04-02', + 'isPublic': True + } + + def test_valid_workout_creation(self): + """ + Test successful workout creation with valid data. + + Expected: + - 201 Created status code + - Workout is created in database + - Response contains workout data with correct owner + """ + response = self.client.post(self.workout_list_url, self.workout_data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(Workout.objects.filter(name="Test Workout").exists()) + self.assertEqual(response.data['owner'], self.user.id) + + def test_workout_creation_empty_name(self): + """ + Test workout creation with empty name. + + Expected: + - 400 Bad Request status code + - Error message about missing name + """ + data = self.workout_data.copy() + data['name'] = '' + response = self.client.post(self.workout_list_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('name', response.data) + + def test_workout_creation_invalid_date(self): + """ + Test workout creation with invalid date format. + + Expected: + - 400 Bad Request status code + - Error message about invalid date format + """ + data = self.workout_data.copy() + data['date'] = 'invalid-date' + response = self.client.post(self.workout_list_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('date', response.data) + + def test_workout_editing(self): + """ + Test workout editing functionality. + + Expected: + - 200 OK status code + - Updated workout data in response + - Changes reflected in database + """ + workout = Workout.objects.create( + owner=self.user, + name='Original Name', + date='2025-04-02' + ) + update_data = {'name': 'Updated Name'} + response = self.client.patch( + reverse('workout-detail', kwargs={'pk': workout.pk}), + update_data + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['name'], 'Updated Name') + workout.refresh_from_db() + self.assertEqual(workout.name, 'Updated Name') + + def test_viewing_public_workout(self): + """ + Test viewing a public workout owned by another user. + + Expected: + - 200 OK status code + - Access to workout data + - Correct owner information + """ + other_user = get_user_model().objects.create_user( + username='otheruser', + password='TestPass123!' + ) + workout = Workout.objects.create( + owner=other_user, + name='Public Workout', + date='2025-04-02', + isPublic=True + ) + response = self.client.get( + reverse('workout-detail', kwargs={'pk': workout.pk}) + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['name'], 'Public Workout') + self.assertEqual(response.data['owner'], other_user.id) \ No newline at end of file