Skip to content

Commit e013332

Browse files
committed
wrote tests for user.py and fixed bugs, passing all 10
1 parent 9b95742 commit e013332

File tree

6 files changed

+242
-33
lines changed

6 files changed

+242
-33
lines changed

csm_web/frontend/src/utils/queries/base.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ export const useProfiles = (): UseQueryResult<Profile[], Error> => {
4141
*/
4242
export const useUserInfo = (): UseQueryResult<RawUserInfo, Error> => {
4343
const queryResult = useQuery<RawUserInfo, Error>(
44-
["userinfo"],
44+
["user"],
4545
async () => {
46-
const response = await fetchNormalized("/userinfo");
46+
const response = await fetchNormalized("/user");
4747
if (response.ok) {
4848
return await response.json();
4949
} else {

csm_web/scheduler/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class User(AbstractUser):
5353

5454
pronouns = models.CharField(max_length=20, default="", blank=True)
5555
pronunciation = models.CharField(max_length=50, default="", blank=True)
56+
# xTODO: configure to use the Django Settings bucket backend
5657
# profile_pic = models.ImageField(upload_to='profiles/', null=True, blank=True)
5758
# if profile picture is implemented
5859
bio = models.CharField(max_length=500, default="", blank=True)
Lines changed: 205 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1-
import pytest
1+
import json
22

3+
import pytest
4+
from django.urls import reverse
5+
from scheduler.factories import (
6+
CoordinatorFactory,
7+
CourseFactory,
8+
MentorFactory,
9+
SectionFactory,
10+
StudentFactory,
11+
UserFactory,
12+
)
313
from scheduler.models import User
414

515

616
@pytest.mark.django_db
717
def test_create_user():
18+
"""
19+
Test that a user can be created.
20+
"""
821
email = "test@berkeley.edu"
922
username = "test"
1023
user, created = User.objects.get_or_create(email=email, username=username)
@@ -13,3 +26,194 @@ def test_create_user():
1326
assert user.username == username
1427
assert User.objects.count() == 1
1528
assert User.objects.get(email=email).username == username
29+
30+
31+
# avoid pylint warning redefining name in outer scope
32+
@pytest.fixture(name="setup_permissions")
33+
def fixture_setup_permissions():
34+
"""
35+
Setup users, courses, and sections for testing permissions
36+
"""
37+
student_user = UserFactory(username="student_user")
38+
other_student_user = UserFactory(username="other_student_user")
39+
mentor_user = UserFactory(username="mentor_user")
40+
other_mentor_user = UserFactory(username="other_mentor_user")
41+
coordinator_user = UserFactory(username="coordinator_user")
42+
43+
# Create courses
44+
course_a = CourseFactory(name="course_a")
45+
course_b = CourseFactory(name="course_b")
46+
47+
# Assign mentors to courses
48+
mentor_a = MentorFactory(user=mentor_user, course=course_a)
49+
mentor_b = MentorFactory(user=other_mentor_user, course=course_b)
50+
coordinator = CoordinatorFactory(user=coordinator_user, course=course_a)
51+
52+
# Create sections associated with the correct course via the mentor
53+
section_a1 = SectionFactory(mentor=mentor_a)
54+
section_b1 = SectionFactory(mentor=mentor_b)
55+
56+
# Ensure students are enrolled in sections that match their course
57+
student_a1 = StudentFactory(user=student_user, section=section_a1, course=course_a)
58+
other_student_a1 = StudentFactory(
59+
user=other_student_user, section=section_a1, course=course_a
60+
)
61+
62+
return {
63+
"student_user": student_user,
64+
"other_student_user": other_student_user,
65+
"mentor_user": mentor_user,
66+
"other_mentor_user": other_mentor_user,
67+
"coordinator_user": coordinator,
68+
"course_a": course_a,
69+
"course_b": course_b,
70+
"section_a1": section_a1,
71+
"section_b1": section_b1,
72+
"student_a1": student_a1,
73+
"other_student_a1": other_student_a1,
74+
}
75+
76+
77+
###############
78+
# Student tests
79+
###############
80+
81+
82+
@pytest.mark.django_db
83+
def test_student_view_own_profile(client, setup_permissions):
84+
"""
85+
Test that a student can view their own profile.
86+
"""
87+
student_user = setup_permissions["student_user"]
88+
client.force_login(student_user)
89+
90+
response = client.get(reverse("user_retrieve", kwargs={"pk": student_user.pk}))
91+
assert response.status_code == 200
92+
assert response.data["email"] == student_user.email
93+
94+
95+
@pytest.mark.django_db
96+
def test_student_view_other_student_in_same_section(client, setup_permissions):
97+
"""
98+
Test that a student can view another student in the same section.
99+
"""
100+
student = setup_permissions["student_user"]
101+
other_student = setup_permissions["other_student_user"]
102+
client.force_login(student)
103+
response = client.get(reverse("user_retrieve", kwargs={"pk": other_student.pk}))
104+
assert response.status_code == 200
105+
assert response.data["email"] == other_student.email
106+
107+
108+
@pytest.mark.django_db
109+
def test_student_view_mentors(client, setup_permissions):
110+
"""
111+
Test that a student can view a mentor's profile.
112+
"""
113+
student = setup_permissions["student_user"]
114+
mentor = setup_permissions["mentor_user"]
115+
client.force_login(student)
116+
response = client.get(reverse("user_retrieve", kwargs={"pk": mentor.pk}))
117+
assert response.status_code == 200
118+
assert response.data["email"] == mentor.email
119+
120+
121+
@pytest.mark.django_db
122+
def test_student_edit_own_profile(client, setup_permissions):
123+
"""
124+
Test that a student can edit their own profile.
125+
"""
126+
student = setup_permissions["student_user"]
127+
client.force_login(student)
128+
edit_url = reverse("user_update", kwargs={"pk": student.pk})
129+
response = client.put(
130+
edit_url,
131+
data=json.dumps({"first_name": "NewName"}),
132+
content_type="application/json",
133+
)
134+
assert response.status_code == 200
135+
student.refresh_from_db()
136+
assert student.first_name == "NewName"
137+
138+
139+
##############
140+
# Mentor tests
141+
##############
142+
@pytest.mark.django_db
143+
def test_mentor_view_own_profile(client, setup_permissions):
144+
"""
145+
Test that a mentor can view their own profile.
146+
"""
147+
mentor_user = setup_permissions["mentor_user"]
148+
client.force_login(mentor_user)
149+
150+
response = client.get(reverse("user_retrieve", kwargs={"pk": mentor_user.pk}))
151+
assert response.status_code == 200
152+
assert response.data["email"] == mentor_user.email
153+
154+
155+
@pytest.mark.django_db
156+
def test_mentor_view_students_in_course(client, setup_permissions):
157+
"""
158+
Test that a mentor can view student profiles in the course they teach.
159+
"""
160+
mentor_user = setup_permissions["mentor_user"]
161+
student_user = setup_permissions["student_user"]
162+
client.force_login(mentor_user)
163+
164+
response = client.get(reverse("user_retrieve", kwargs={"pk": student_user.pk}))
165+
assert response.status_code == 200
166+
assert response.data["email"] == student_user.email
167+
168+
169+
@pytest.mark.django_db
170+
def test_mentor_cannot_edit_other_profiles(client, setup_permissions):
171+
"""
172+
Test that a mentor cannot edit another student's or mentor's profile.
173+
"""
174+
mentor_user = setup_permissions["mentor_user"]
175+
other_student_user = setup_permissions["other_student_user"]
176+
client.force_login(mentor_user)
177+
response = client.put(
178+
reverse("user_update", kwargs={"pk": other_student_user.pk}),
179+
data=json.dumps({"first_name": "new_username"}),
180+
content_type="application/json",
181+
)
182+
assert response.status_code == 403
183+
184+
185+
###################
186+
# Coordinator tests
187+
###################
188+
189+
190+
@pytest.mark.django_db
191+
def test_coordinator_view_all_profiles_in_course(client, setup_permissions):
192+
"""
193+
Test that a coordinator can view all profiles in the course they coordinate.
194+
"""
195+
coordinator_user = setup_permissions["coordinator_user"]
196+
student_user = setup_permissions["student_user"]
197+
client.force_login(coordinator_user)
198+
199+
response = client.get(reverse("user_retrieve", kwargs={"pk": student_user.pk}))
200+
assert response.status_code == 200
201+
202+
203+
@pytest.mark.django_db
204+
def test_coordinator_edit_all_profiles_in_course(client, setup_permissions):
205+
"""
206+
Test that a coordinator can edit all profiles in the course they coordinate.
207+
"""
208+
coordinator_user = setup_permissions["coordinator_user"]
209+
student_user = setup_permissions["student_user"]
210+
client.force_login(coordinator_user)
211+
212+
response = client.put(
213+
reverse("user_update", kwargs={"pk": student_user.pk}),
214+
data=json.dumps({"first_name": "new_student_name"}),
215+
content_type="application/json",
216+
)
217+
assert response.status_code == 200
218+
student_user.refresh_from_db()
219+
assert student_user.first_name == "new_student_name"

csm_web/scheduler/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
urlpatterns = router.urls
1616

1717
urlpatterns += [
18-
path("user_info/", views.user_info, name="user_info"),
18+
path("user/", views.user_info, name="user"),
1919
path("user/<int:pk>/", views.user_retrieve, name="user_retrieve"),
2020
path("user/<int:pk>/update/", views.user_update, name="user_update"),
2121
path("matcher/active/", views.matcher.active),

csm_web/scheduler/views/user.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ def has_permission(request_user, target_user):
3737
if request_user == target_user:
3838
return True
3939

40+
# if the target user is a mentor, return True
41+
if Mentor.objects.filter(user=target_user).exists():
42+
return True
43+
4044
# if requestor is a student, get all the sections they are in
4145
# if the target user is a student in any of those sections, return True
4246
if Student.objects.filter(user=request_user).exists():
@@ -51,14 +55,14 @@ def has_permission(request_user, target_user):
5155
return True
5256

5357
# if the target user is a mentor in any of the courses the student is in, return True
54-
if Mentor.objects.filter(user=target_user).exists():
55-
target_user_courses = Mentor.objects.filter(user=target_user).values_list(
56-
"course", flat=True
57-
)
58-
if Student.objects.filter(
59-
user=request_user, course__in=target_user_courses
60-
).exists():
61-
return True
58+
# if Mentor.objects.filter(user=target_user).exists():
59+
# target_user_courses = Mentor.objects.filter(user=target_user).values_list(
60+
# "course", flat=True
61+
# )
62+
# if Student.objects.filter(
63+
# user=request_user, course__in=target_user_courses
64+
# ).exists():
65+
# return True
6266

6367
# if requestor is a mentor, get all the courses they mentor
6468
# if the target user is a student or mentor in any of those courses, return True
@@ -69,8 +73,8 @@ def has_permission(request_user, target_user):
6973

7074
if Student.objects.filter(user=target_user, course__in=mentor_courses).exists():
7175
return True
72-
if Mentor.objects.filter(user=target_user, course__in=mentor_courses).exists():
73-
return True
76+
# if Mentor.objects.filter(user=target_user, course__in=mentor_courses).exists():
77+
# return True
7478

7579
# if requestor is a coordinator, get all the courses they coordinate
7680
# if the target user is a student or mentor in any of those courses, return True
@@ -82,10 +86,10 @@ def has_permission(request_user, target_user):
8286
user=target_user, course__in=coordinator_courses
8387
).exists():
8488
return True
85-
if Mentor.objects.filter(
86-
user=target_user, course__in=coordinator_courses
87-
).exists():
88-
return True
89+
# if Mentor.objects.filter(
90+
# user=target_user, course__in=coordinator_courses
91+
# ).exists():
92+
# return True
8993
if Coordinator.objects.filter(
9094
user=target_user, course__in=coordinator_courses
9195
).exists():
@@ -122,17 +126,17 @@ def user_update(request, pk):
122126
except User.DoesNotExist:
123127
return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
124128

125-
if not request.user == user:
126-
raise PermissionDenied("You do not have permission to edit this profile")
127-
128-
coordinator_courses = Coordinator.objects.filter(user=request.user).values_list(
129-
"course", flat=True
130-
)
131-
132-
if not Coordinator.objects.filter(
133-
user=request.user, course_in=coordinator_courses
134-
).exists():
135-
raise PermissionDenied("You do not have permission to edit this profile")
129+
if request.user == user:
130+
pass
131+
else:
132+
# Check if the user is a coordinator and has permission
133+
coordinator_courses = Coordinator.objects.filter(user=request.user).values_list(
134+
"course", flat=True
135+
)
136+
if not Coordinator.objects.filter(
137+
user=request.user, course__in=coordinator_courses
138+
).exists():
139+
raise PermissionDenied("You do not have permission to edit this profile")
136140

137141
serializer = UserSerializer(user, data=request.data, partial=True)
138142
if serializer.is_valid():

cypress/e2e/course/restricted-courses.cy.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe("whitelisted courses", () => {
7272
cy.login();
7373

7474
cy.intercept("/api/courses").as("get-courses");
75-
cy.intercept("/api/userinfo").as("get-userinfo");
75+
cy.intercept("/api/user").as("get-user");
7676

7777
cy.visit("/");
7878
cy.wait("@get-courses");
@@ -82,7 +82,7 @@ describe("whitelisted courses", () => {
8282

8383
// view courses
8484
cy.contains(".primary-btn", /add course/i).click();
85-
cy.wait("@get-userinfo");
85+
cy.wait("@get-user");
8686
cy.contains(".page-title", /which course/i).should("be.visible");
8787

8888
// should have two buttons at the top
@@ -126,7 +126,7 @@ describe("whitelisted courses", () => {
126126
cy.login();
127127

128128
cy.intercept("/api/courses").as("get-courses");
129-
cy.intercept("/api/userinfo").as("get-userinfo");
129+
cy.intercept("/api/user").as("get-user");
130130

131131
cy.visit("/");
132132
cy.wait("@get-courses");
@@ -136,7 +136,7 @@ describe("whitelisted courses", () => {
136136

137137
// view courses
138138
cy.contains(".primary-btn", /add course/i).click();
139-
cy.wait("@get-userinfo");
139+
cy.wait("@get-user");
140140
cy.contains(".page-title", /which course/i).should("be.visible");
141141

142142
// should have two buttons at the top

0 commit comments

Comments
 (0)