Skip to content

Commit 3ed6c11

Browse files
committed
Tests and checks
1 parent 9a0c6c0 commit 3ed6c11

3 files changed

Lines changed: 404 additions & 0 deletions

File tree

backend/reviews/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,9 @@ def review_recap_compute_analysis_view(self, request, review_session_id):
445445
if not review_session.user_can_review(request.user):
446446
raise PermissionDenied()
447447

448+
if not review_session.can_see_shortlist_screen:
449+
raise PermissionDenied()
450+
448451
conference = review_session.conference
449452
accepted_submissions = self._get_accepted_submissions(conference)
450453
force_recompute = request.GET.get("recompute") == "1"
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import json
2+
3+
import pytest
4+
from django.contrib.admin import AdminSite
5+
from django.core.exceptions import PermissionDenied
6+
7+
from conferences.tests.factories import ConferenceFactory
8+
from reviews.admin import ReviewSessionAdmin
9+
from reviews.models import ReviewSession
10+
from reviews.tests.factories import (
11+
AvailableScoreOptionFactory,
12+
ReviewSessionFactory,
13+
)
14+
from submissions.models import Submission
15+
from submissions.tests.factories import SubmissionFactory
16+
from users.tests.factories import UserFactory
17+
18+
pytestmark = pytest.mark.django_db
19+
20+
21+
def _create_recap_setup(*, session_status=ReviewSession.Status.COMPLETED):
22+
"""Create a review session with accepted submissions for recap tests."""
23+
user = UserFactory(is_staff=True, is_superuser=True)
24+
conference = ConferenceFactory()
25+
26+
review_session = ReviewSessionFactory(
27+
conference=conference,
28+
session_type=ReviewSession.SessionType.PROPOSALS,
29+
status=session_status,
30+
)
31+
AvailableScoreOptionFactory(review_session=review_session, numeric_value=0)
32+
AvailableScoreOptionFactory(review_session=review_session, numeric_value=1)
33+
34+
submission_1 = SubmissionFactory(
35+
conference=conference, status=Submission.STATUS.accepted
36+
)
37+
submission_2 = SubmissionFactory(
38+
conference=conference, status=Submission.STATUS.accepted
39+
)
40+
41+
return user, conference, review_session, [submission_1, submission_2]
42+
43+
44+
# --- review_recap_view tests ---
45+
46+
47+
def test_recap_view_returns_200(rf):
48+
user, conference, review_session, submissions = _create_recap_setup()
49+
50+
request = rf.get("/")
51+
request.user = user
52+
53+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
54+
response = admin.review_recap_view(request, review_session.id)
55+
56+
assert response.status_code == 200
57+
58+
59+
def test_recap_view_context_has_stats(rf):
60+
user, conference, review_session, submissions = _create_recap_setup()
61+
62+
request = rf.get("/")
63+
request.user = user
64+
65+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
66+
response = admin.review_recap_view(request, review_session.id)
67+
68+
assert "total_accepted" in response.context_data
69+
assert "stats_by_type" in response.context_data
70+
assert "submissions_data" in response.context_data
71+
assert "compute_analysis_url" in response.context_data
72+
assert response.context_data["total_accepted"] == 2
73+
74+
75+
def test_recap_view_does_not_call_ml_functions(rf, mocker):
76+
mock_similar = mocker.patch("reviews.admin.compute_similar_talks")
77+
mock_clusters = mocker.patch("reviews.admin.compute_topic_clusters")
78+
79+
user, conference, review_session, submissions = _create_recap_setup()
80+
81+
request = rf.get("/")
82+
request.user = user
83+
84+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
85+
admin.review_recap_view(request, review_session.id)
86+
87+
mock_similar.assert_not_called()
88+
mock_clusters.assert_not_called()
89+
90+
91+
def test_recap_view_permission_denied_for_non_reviewer(rf):
92+
user = UserFactory(is_staff=True, is_superuser=False)
93+
conference = ConferenceFactory()
94+
review_session = ReviewSessionFactory(
95+
conference=conference,
96+
session_type=ReviewSession.SessionType.PROPOSALS,
97+
)
98+
99+
request = rf.get("/")
100+
request.user = user
101+
102+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
103+
104+
with pytest.raises(PermissionDenied):
105+
admin.review_recap_view(request, review_session.id)
106+
107+
108+
def test_recap_view_redirects_when_shortlist_not_visible(rf, mocker):
109+
mocker.patch("reviews.admin.messages")
110+
111+
user, conference, review_session, submissions = _create_recap_setup(
112+
session_status=ReviewSession.Status.DRAFT,
113+
)
114+
115+
# Grants sessions need COMPLETED to see shortlist; DRAFT won't work
116+
review_session.session_type = ReviewSession.SessionType.GRANTS
117+
review_session.save()
118+
119+
request = rf.get("/")
120+
request.user = user
121+
122+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
123+
response = admin.review_recap_view(request, review_session.id)
124+
125+
assert response.status_code == 302
126+
127+
128+
# --- review_recap_compute_analysis_view tests ---
129+
130+
131+
def test_compute_analysis_view_returns_json(rf, mocker):
132+
mocker.patch(
133+
"reviews.admin.compute_similar_talks",
134+
return_value={},
135+
)
136+
mocker.patch(
137+
"reviews.admin.compute_topic_clusters",
138+
return_value={"topics": [], "outliers": [], "submission_topics": {}},
139+
)
140+
141+
user, conference, review_session, submissions = _create_recap_setup()
142+
143+
request = rf.get("/")
144+
request.user = user
145+
146+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
147+
response = admin.review_recap_compute_analysis_view(request, review_session.id)
148+
149+
assert response.status_code == 200
150+
assert response["Content-Type"] == "application/json"
151+
152+
data = json.loads(response.content)
153+
assert "submissions_list" in data
154+
assert "topic_clusters" in data
155+
156+
157+
def test_compute_analysis_view_passes_recompute_flag(rf, mocker):
158+
mock_similar = mocker.patch(
159+
"reviews.admin.compute_similar_talks",
160+
return_value={},
161+
)
162+
mock_clusters = mocker.patch(
163+
"reviews.admin.compute_topic_clusters",
164+
return_value={"topics": [], "outliers": [], "submission_topics": {}},
165+
)
166+
167+
user, conference, review_session, submissions = _create_recap_setup()
168+
169+
request = rf.get("/?recompute=1")
170+
request.user = user
171+
172+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
173+
admin.review_recap_compute_analysis_view(request, review_session.id)
174+
175+
_, kwargs = mock_similar.call_args
176+
assert kwargs["force_recompute"] is True
177+
178+
_, kwargs = mock_clusters.call_args
179+
assert kwargs["force_recompute"] is True
180+
181+
182+
def test_compute_analysis_view_no_recompute_by_default(rf, mocker):
183+
mock_similar = mocker.patch(
184+
"reviews.admin.compute_similar_talks",
185+
return_value={},
186+
)
187+
mock_clusters = mocker.patch(
188+
"reviews.admin.compute_topic_clusters",
189+
return_value={"topics": [], "outliers": [], "submission_topics": {}},
190+
)
191+
192+
user, conference, review_session, submissions = _create_recap_setup()
193+
194+
request = rf.get("/")
195+
request.user = user
196+
197+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
198+
admin.review_recap_compute_analysis_view(request, review_session.id)
199+
200+
_, kwargs = mock_similar.call_args
201+
assert kwargs["force_recompute"] is False
202+
203+
_, kwargs = mock_clusters.call_args
204+
assert kwargs["force_recompute"] is False
205+
206+
207+
def test_compute_analysis_view_permission_denied_for_non_reviewer(rf):
208+
user = UserFactory(is_staff=True, is_superuser=False)
209+
conference = ConferenceFactory()
210+
review_session = ReviewSessionFactory(
211+
conference=conference,
212+
session_type=ReviewSession.SessionType.PROPOSALS,
213+
)
214+
215+
request = rf.get("/")
216+
request.user = user
217+
218+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
219+
220+
with pytest.raises(PermissionDenied):
221+
admin.review_recap_compute_analysis_view(request, review_session.id)
222+
223+
224+
def test_compute_analysis_view_permission_denied_when_shortlist_not_visible(rf):
225+
user = UserFactory(is_staff=True, is_superuser=True)
226+
conference = ConferenceFactory()
227+
review_session = ReviewSessionFactory(
228+
conference=conference,
229+
session_type=ReviewSession.SessionType.GRANTS,
230+
status=ReviewSession.Status.DRAFT,
231+
)
232+
233+
request = rf.get("/")
234+
request.user = user
235+
236+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
237+
238+
with pytest.raises(PermissionDenied):
239+
admin.review_recap_compute_analysis_view(request, review_session.id)
240+
241+
242+
def test_compute_analysis_view_submissions_list_sorted_by_similarity(rf, mocker):
243+
user, conference, review_session, submissions = _create_recap_setup()
244+
sub1, sub2 = submissions
245+
246+
mocker.patch(
247+
"reviews.admin.compute_similar_talks",
248+
return_value={
249+
sub1.id: [{"id": sub2.id, "title": "Sub2", "similarity": 50.0}],
250+
sub2.id: [{"id": sub1.id, "title": "Sub1", "similarity": 90.0}],
251+
},
252+
)
253+
mocker.patch(
254+
"reviews.admin.compute_topic_clusters",
255+
return_value={"topics": [], "outliers": [], "submission_topics": {}},
256+
)
257+
258+
request = rf.get("/")
259+
request.user = user
260+
261+
admin = ReviewSessionAdmin(ReviewSession, AdminSite())
262+
response = admin.review_recap_compute_analysis_view(request, review_session.id)
263+
data = json.loads(response.content)
264+
265+
# sub2 has highest similarity (90%) so should be first
266+
assert data["submissions_list"][0]["id"] == sub2.id
267+
assert data["submissions_list"][1]["id"] == sub1.id

0 commit comments

Comments
 (0)