99 Project ,
1010 ProjectState ,
1111 ProjectSubmission ,
12+ PeerReview ,
13+ PeerReviewState ,
14+ ReviewCriteria ,
15+ ReviewCriteriaTypes ,
16+ CriteriaResponse ,
1217)
1318
19+ from courses .projects import score_project , ProjectActionStatus
20+
1421
1522class PeerReviewBadgeTests (TestCase ):
1623 """Test cases for peer review badge color changes based on completion status"""
@@ -43,13 +50,37 @@ def setUp(self):
4350
4451 def test_peer_review_badge_red_when_not_completed (self ):
4552 """Test that the badge is red when peer reviews are not completed"""
46- # Create a submission with reviewed_enough_peers = False
47- ProjectSubmission .objects .create (
53+ # Create a submission
54+ submission = ProjectSubmission .objects .create (
4855 project = self .pr_project ,
4956 student = self .user ,
5057 enrollment = self .enrollment ,
5158 github_link = "https://github.com/test/repo" ,
52- reviewed_enough_peers = False
59+ )
60+
61+ # Create only 1 peer review (less than number_of_peers_to_evaluate=3)
62+ other_user = User .objects .create_user (
63+ username = "peer@test.com" ,
64+ email = "peer@test.com" ,
65+ password = "12345"
66+ )
67+ other_enrollment = Enrollment .objects .create (
68+ student = other_user ,
69+ course = self .course
70+ )
71+ other_submission = ProjectSubmission .objects .create (
72+ project = self .pr_project ,
73+ student = other_user ,
74+ enrollment = other_enrollment ,
75+ github_link = "https://github.com/peer/repo" ,
76+ )
77+ # Create a submitted peer review (only 1 out of required 3)
78+ PeerReview .objects .create (
79+ submission_under_evaluation = other_submission ,
80+ reviewer = submission ,
81+ optional = False ,
82+ state = PeerReviewState .SUBMITTED .value ,
83+ submitted_at = timezone .now ()
5384 )
5485
5586 self .client .login (username = "test@test.com" , password = "12345" )
@@ -70,14 +101,39 @@ def test_peer_review_badge_red_when_not_completed(self):
70101
71102 def test_peer_review_badge_green_when_completed (self ):
72103 """Test that the badge is green when peer reviews are completed"""
73- # Create a submission with reviewed_enough_peers = True
74- ProjectSubmission .objects .create (
104+ # Create a submission
105+ submission = ProjectSubmission .objects .create (
75106 project = self .pr_project ,
76107 student = self .user ,
77108 enrollment = self .enrollment ,
78109 github_link = "https://github.com/test/repo" ,
79- reviewed_enough_peers = True
80110 )
111+
112+ # Create 3 peer reviews (matching number_of_peers_to_evaluate default=3)
113+ for i in range (3 ):
114+ other_user = User .objects .create_user (
115+ username = f"peer{ i } @test.com" ,
116+ email = f"peer{ i } @test.com" ,
117+ password = "12345"
118+ )
119+ other_enrollment = Enrollment .objects .create (
120+ student = other_user ,
121+ course = self .course
122+ )
123+ other_submission = ProjectSubmission .objects .create (
124+ project = self .pr_project ,
125+ student = other_user ,
126+ enrollment = other_enrollment ,
127+ github_link = f"https://github.com/peer{ i } /repo" ,
128+ )
129+ # Create a submitted peer review (main user reviewing others)
130+ PeerReview .objects .create (
131+ submission_under_evaluation = other_submission ,
132+ reviewer = submission ,
133+ optional = False ,
134+ state = PeerReviewState .SUBMITTED .value ,
135+ submitted_at = timezone .now ()
136+ )
81137
82138 self .client .login (username = "test@test.com" , password = "12345" )
83139 response = self .client .get (
@@ -112,3 +168,191 @@ def test_peer_review_badge_secondary_when_not_submitted(self):
112168 # Badge should be secondary (bg-secondary) when not submitted
113169 self .assertEqual (project .badge_css_class , "bg-secondary" )
114170 self .assertEqual (project .badge_state_name , "Not submitted" )
171+
172+
173+ class PeerReviewBadgeEndToEndTests (TestCase ):
174+ """End-to-end test for peer review badge showing progression from red to green"""
175+
176+ def setUp (self ):
177+ self .client = Client ()
178+
179+ # Create main user
180+ self .user = User .objects .create_user (
181+ username = "main@test.com" ,
182+ email = "main@test.com" ,
183+ password = "12345"
184+ )
185+
186+ # Create course
187+ self .course = Course .objects .create (
188+ title = "Test Course" ,
189+ slug = "test-course" ,
190+ project_passing_score = 10 , # Set a passing score for project scoring
191+ )
192+
193+ # Create enrollment for main user
194+ self .enrollment = Enrollment .objects .create (
195+ student = self .user ,
196+ course = self .course
197+ )
198+
199+ # Create a project in peer review state with 3 required reviews
200+ self .project = Project .objects .create (
201+ course = self .course ,
202+ title = "Peer Review Project" ,
203+ slug = "pr-project" ,
204+ state = ProjectState .PEER_REVIEWING .value ,
205+ submission_due_date = timezone .now () - timezone .timedelta (days = 1 ),
206+ peer_review_due_date = timezone .now () + timezone .timedelta (days = 7 ),
207+ number_of_peers_to_evaluate = 3 , # Require 3 reviews
208+ points_for_peer_review = 1 ,
209+ )
210+
211+ # Create main user's submission
212+ self .main_submission = ProjectSubmission .objects .create (
213+ project = self .project ,
214+ student = self .user ,
215+ enrollment = self .enrollment ,
216+ github_link = "https://github.com/main/repo" ,
217+ commit_id = "main123" ,
218+ )
219+
220+ # Create 3 other students and their submissions for the main user to review
221+ self .other_submissions = []
222+ self .peer_reviews = []
223+
224+ for i in range (3 ):
225+ other_user = User .objects .create_user (
226+ username = f"student{ i } @test.com" ,
227+ email = f"student{ i } @test.com" ,
228+ password = "12345"
229+ )
230+
231+ other_enrollment = Enrollment .objects .create (
232+ student = other_user ,
233+ course = self .course
234+ )
235+
236+ other_submission = ProjectSubmission .objects .create (
237+ project = self .project ,
238+ student = other_user ,
239+ enrollment = other_enrollment ,
240+ github_link = f"https://github.com/student{ i } /repo" ,
241+ commit_id = f"commit{ i } " ,
242+ )
243+ self .other_submissions .append (other_submission )
244+
245+ # Create peer review assignment (main user reviews other students)
246+ peer_review = PeerReview .objects .create (
247+ submission_under_evaluation = other_submission ,
248+ reviewer = self .main_submission ,
249+ optional = False ,
250+ state = PeerReviewState .TO_REVIEW .value ,
251+ )
252+ self .peer_reviews .append (peer_review )
253+
254+ # Create reverse review (other student reviews main user)
255+ # This ensures main_submission is also in the submissions dict during scoring
256+ PeerReview .objects .create (
257+ submission_under_evaluation = self .main_submission ,
258+ reviewer = other_submission ,
259+ optional = False ,
260+ state = PeerReviewState .SUBMITTED .value ,
261+ submitted_at = timezone .now ()
262+ )
263+
264+ # Create review criteria
265+ self .criteria = ReviewCriteria .objects .create (
266+ course = self .course ,
267+ description = "Code Quality" ,
268+ review_criteria_type = ReviewCriteriaTypes .RADIO_BUTTONS .value ,
269+ options = [
270+ {"criteria" : "Poor" , "score" : 0 },
271+ {"criteria" : "Fair" , "score" : 1 },
272+ {"criteria" : "Good" , "score" : 2 },
273+ {"criteria" : "Excellent" , "score" : 3 },
274+ ]
275+ )
276+
277+ def submit_review (self , peer_review , score = "3" ):
278+ """Helper to submit a peer review"""
279+ CriteriaResponse .objects .create (
280+ review = peer_review ,
281+ criteria = self .criteria ,
282+ answer = score ,
283+ )
284+ peer_review .state = PeerReviewState .SUBMITTED .value
285+ peer_review .submitted_at = timezone .now ()
286+ peer_review .save ()
287+
288+ def get_badge_state (self ):
289+ """Helper to get current badge state from course view"""
290+ self .client .login (username = "main@test.com" , password = "12345" )
291+ response = self .client .get (
292+ reverse ("course" , kwargs = {"course_slug" : self .course .slug })
293+ )
294+ self .assertEqual (response .status_code , 200 )
295+
296+ projects = response .context ["projects" ]
297+ self .assertEqual (len (projects ), 1 )
298+ project = projects [0 ]
299+
300+ return project .badge_css_class , project .badge_state_name
301+
302+ def test_badge_progression_no_reviews_to_all_reviews (self ):
303+ """
304+ Test badge progression:
305+ 0 reviews -> red
306+ 1 review -> red
307+ 2 reviews -> red
308+ 3 reviews -> green (after scoring)
309+ """
310+ # Initial state: 0 reviews submitted, should be red
311+ badge_class , badge_name = self .get_badge_state ()
312+ self .assertEqual (badge_class , "bg-danger" ,
313+ "Badge should be red when no reviews are submitted" )
314+ self .assertEqual (badge_name , "Review" )
315+
316+ # Submit first review, still need 2 more
317+ self .submit_review (self .peer_reviews [0 ], "3" )
318+
319+ badge_class , badge_name = self .get_badge_state ()
320+ self .assertEqual (badge_class , "bg-danger" ,
321+ "Badge should be red after 1 review (need 3 total)" )
322+ self .assertEqual (badge_name , "Review" )
323+
324+ # Submit second review, still need 1 more
325+ self .submit_review (self .peer_reviews [1 ], "2" )
326+
327+ badge_class , badge_name = self .get_badge_state ()
328+ self .assertEqual (badge_class , "bg-danger" ,
329+ "Badge should be red after 2 reviews (need 3 total)" )
330+ self .assertEqual (badge_name , "Review" )
331+
332+ # Submit third review - all reviews complete
333+ self .submit_review (self .peer_reviews [2 ], "3" )
334+
335+ # Now badge should be green immediately (calculated on-the-fly)
336+ badge_class , badge_name = self .get_badge_state ()
337+ self .assertEqual (badge_class , "bg-success" ,
338+ "Badge should be green immediately after all 3 reviews are submitted" )
339+ self .assertEqual (badge_name , "Review completed" )
340+
341+ # Move peer review due date to the past to allow scoring
342+ self .project .peer_review_due_date = timezone .now () - timezone .timedelta (hours = 1 )
343+ self .project .save ()
344+
345+ # Run scoring to update reviewed_enough_peers field in database
346+ status , message = score_project (self .project )
347+ self .assertEqual (status , ProjectActionStatus .OK , f"Scoring should succeed. Got: { message } " )
348+
349+ # Refresh submission to get updated reviewed_enough_peers
350+ self .main_submission .refresh_from_db ()
351+ self .assertTrue (self .main_submission .reviewed_enough_peers ,
352+ "reviewed_enough_peers should be True after scoring" )
353+
354+ # After scoring, project state becomes COMPLETED
355+ self .project .refresh_from_db ()
356+ self .assertEqual (self .project .state , ProjectState .COMPLETED .value ,
357+ "Project should be in COMPLETED state after scoring" )
358+
0 commit comments