Skip to content

Commit 0049d40

Browse files
authored
Merge pull request #74 from UNLV-CS472-672/user_tests
User tests
2 parents 008a9fe + 7a302c7 commit 0049d40

File tree

4 files changed

+159
-42
lines changed

4 files changed

+159
-42
lines changed

backend/apps/users/tests.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,96 @@
1+
from django.contrib.auth.models import User
12
from django.test import TestCase
3+
from django.urls import reverse
4+
from rest_framework.test import APIClient
5+
from rest_framework import status
6+
from .models import Profile
7+
from rest_framework_simplejwt.tokens import RefreshToken
28

3-
# Create your tests here.
9+
class UserAPITestCase(TestCase):
10+
def setUp(self):
11+
"""Set up test data and API client."""
12+
self.client = APIClient()
13+
self.user = User.objects.create_user(
14+
username="testuser",
15+
password="testpassword",
16+
first_name="Test",
17+
last_name="User",
18+
email="test@example.com"
19+
)
20+
self.profile = Profile.objects.create(user=self.user, role=Profile.STUDENT)
21+
self.login_url = "/users/login/"
22+
self.register_url = "/users/register/"
23+
self.csrf_token_url = "/users/csrf/"
24+
self.delete_user_url = "/users/delete_user/"
25+
self.logout_url = "/users/logout/"
26+
27+
# Obtain JWT tokens
28+
response = self.client.post(self.login_url, {"username": "testuser", "password": "testpassword"})
29+
self.assertEqual(response.status_code, status.HTTP_200_OK)
30+
self.refresh_token = response.json().get("refreshToken")
31+
self.access_token = response.json().get("accessToken")
32+
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {self.access_token}")
33+
34+
def test_csrf_token_view(self):
35+
"""Test CSRF token endpoint."""
36+
response = self.client.get(self.csrf_token_url)
37+
self.assertEqual(response.status_code, status.HTTP_200_OK)
38+
self.assertIn("csrfToken", response.json())
39+
40+
def test_login_view_success(self):
41+
"""Test successful login."""
42+
response = self.client.post(self.login_url, {
43+
"username": "testuser",
44+
"password": "testpassword"
45+
})
46+
self.assertEqual(response.status_code, status.HTTP_200_OK)
47+
self.assertIn("accessToken", response.json())
48+
49+
def test_login_view_failure(self):
50+
"""Test failed login with incorrect credentials."""
51+
response = self.client.post(self.login_url, {
52+
"username": "wronguser",
53+
"password": "wrongpassword"
54+
})
55+
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
56+
self.assertIn("error", response.json())
57+
58+
def test_register_profile(self):
59+
"""Test user registration."""
60+
response = self.client.post(self.register_url, {
61+
"country": "USA",
62+
"displayName": "newuser",
63+
"email": "newuser@example.com",
64+
"firstName": "New",
65+
"lastName": "User",
66+
"password": "newpassword",
67+
"role": Profile.STUDENT
68+
})
69+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
70+
self.assertTrue(User.objects.filter(username="newuser").exists())
71+
self.assertTrue(Profile.objects.filter(user__username="newuser").exists())
72+
self.assertEqual(str(Profile.objects.filter(user__username="newuser")[0]), "newuser")
73+
74+
def test_delete_user_success(self):
75+
"""Test deleting an existing user."""
76+
response = self.client.post(self.delete_user_url, {"username": "testuser"})
77+
self.assertEqual(response.status_code, status.HTTP_200_OK)
78+
self.assertFalse(User.objects.filter(username="testuser").exists())
79+
80+
def test_delete_user_not_found(self):
81+
"""Test deleting a non-existent user."""
82+
response = self.client.post(self.delete_user_url, {"username": "nonexistent"})
83+
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
84+
self.assertIn("User not found!", response.content.decode())
85+
86+
def test_logout_view(self):
87+
"""Test logout functionality."""
88+
response = self.client.post(self.logout_url, {"refresh_token": self.refresh_token})
89+
if(response.status_code==status.HTTP_400_BAD_REQUEST):
90+
print(response.data["error"])
91+
self.assertEqual(response.status_code, status.HTTP_200_OK)
92+
self.assertIn("message", response.json())
93+
94+
# as per Allison's recommendation
95+
def tearDown(self):
96+
self.user.delete()

backend/apps/users/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
urlpatterns = [
44
path("login/", login_view, name="login"),
55
path("logout/", logout_view, name="logout"),
6+
path("register/", register_profile, name="register"),
7+
path("delete_user/", delete_user, name="delete"),
68
path("csrf/", csrf_token_view, name="csrf"),
79
]

backend/apps/users/views.py

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,88 @@
1-
from django.shortcuts import render, redirect
1+
# https://chatgpt.com/share/67df13f7-a784-8005-8349-637a36bf1765
2+
23
from django.contrib.auth.models import User
3-
from django.contrib.auth import authenticate, login, logout
4+
from django.contrib.auth import authenticate
45
from django.http import JsonResponse, HttpResponse
56
from django.middleware.csrf import get_token
67
from .models import Profile
8+
from rest_framework import status
9+
from rest_framework.response import Response
10+
from rest_framework.decorators import api_view, permission_classes
11+
from rest_framework.permissions import AllowAny
12+
from rest_framework_simplejwt.tokens import RefreshToken
713

814
login_template = "login.html"
915
register_profile_template = "register.html"
1016
delete_user_template = "delete_user.html"
1117

18+
@api_view(['GET'])
1219
def csrf_token_view(request):
1320
return JsonResponse({"csrfToken": get_token(request)})
1421

22+
# Login View
23+
@api_view(['POST'])
24+
@permission_classes([AllowAny])
1525
def login_view(request):
16-
user = request.user if request.user.is_authenticated else None
17-
error = None
26+
username = request.data['username']
27+
password = request.data['password']
28+
user = authenticate(username=username, password=password)
1829

19-
if request.method == "POST":
20-
username = request.POST.get("username")
21-
password = request.POST.get("password")
22-
23-
user = authenticate(request, username=username, password=password)
2430
if user:
25-
login(request, user)
26-
else:
27-
return render(request, "login.html", {"error": "Invalid username or password"})
28-
29-
return render(request, "login.html", {"user": user, "error": error})
31+
refresh = RefreshToken.for_user(user)
32+
return Response({
33+
'refreshToken': str(refresh),
34+
'accessToken': str(refresh.access_token),
35+
'username': user.username
36+
}, status=status.HTTP_200_OK)
37+
# https://stackoverflow.com/questions/7064374/proper-http-headers-for-login-success-fail-responses
38+
return Response({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)
3039

40+
# Logout View
41+
@api_view(['POST'])
3142
def logout_view(request):
32-
logout(request)
33-
return redirect("login")
43+
try:
44+
refresh_token = request.data["refresh_token"]
45+
if refresh_token:
46+
token = RefreshToken(refresh_token)
47+
token.blacklist()
48+
return Response({"message": "Logout successful"}, status=status.HTTP_200_OK)
49+
except Exception as e:
50+
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
3451

35-
def register_profile(request):
36-
if request.method == "POST":
37-
username = request.POST["username"]
38-
password = request.POST["password"]
39-
first_name = request.POST["first_name"]
40-
last_name = request.POST["last_name"]
41-
email = request.POST["email"]
42-
role = request.POST["role"] # Get the selected role
43-
# Create user
44-
user = User.objects.create_user(
45-
username=username,
46-
password=password,
47-
first_name=first_name,
48-
last_name=last_name,
49-
email=email,
50-
)
51-
# Create associated Profile
52-
Profile.objects.create(user=user, role=role)
53-
return HttpResponse(f"User {user.username} created successfully with role {role}!")
5452

55-
return render(request, register_profile_template)
53+
@api_view(['POST'])
54+
@permission_classes([AllowAny])
55+
def register_profile(request):
56+
country = request.data["country"]
57+
username = request.data["displayName"]
58+
email = request.data["email"]
59+
first_name = request.data["firstName"]
60+
last_name = request.data["lastName"]
61+
password = request.data["password"]
62+
role = request.data["role"] # Get the selected role
63+
# Create user
64+
user = User.objects.create_user(
65+
username=username,
66+
password=password,
67+
first_name=first_name,
68+
last_name=last_name,
69+
email=email,
70+
)
71+
# Create associated Profile
72+
Profile.objects.create(user=user, role=role)
73+
return Response({"message": "User registered successfully"}, status=status.HTTP_201_CREATED)
5674

75+
@api_view(['POST'])
5776
def delete_user(request):
5877
if request.method == "POST":
59-
username = request.POST["username"]
78+
username = request.data["username"]
6079
try:
6180
user = User.objects.get(username=username)
6281
user.delete()
63-
return HttpResponse(f"User {username} deleted successfully!")
82+
return Response(f"User {username} deleted successfully!", status=status.HTTP_200_OK)
6483
except User.DoesNotExist:
65-
return HttpResponse("User not found!")
66-
return render(request, delete_user_template)
67-
84+
# https://stackoverflow.com/questions/17884469/what-is-the-http-response-code-for-failed-http-delete-operation
85+
return Response("User not found!", status=status.HTTP_404_NOT_FOUND)
86+
# should never reach here, but still just in case
87+
return Response("FATAL: Undefined functionality, please contact system administrator",
88+
status=status.HTTP_404_NOT_FOUND)

backend/backend/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"apps.uploads", # our uploads app
7373
"apps.search", # out search app
7474
"rest_framework", # rest framework
75+
'rest_framework_simplejwt.token_blacklist', # for logout functionality
7576
"channels", # Django channels
7677
"django_celery_results", # get celery results in Django admin
7778
"django_celery_beat", # celery beat

0 commit comments

Comments
 (0)