Skip to content

Commit bb76448

Browse files
committed
feat: add validations to enhance messaging services
- Add validation to re-queue pulled messages - Add validation to mark duplicate messages as failed - Implement integrity checks to prevent redundant processing
1 parent ca6ed02 commit bb76448

5 files changed

Lines changed: 117 additions & 73 deletions

File tree

submissions/api.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
TOP_SUBMISSIONS_CACHE_TIMEOUT = 300
5151

5252

53-
# pylint: disable=unused-argument
5453
def create_external_grader_detail(student_item_dict,
5554
answer,
5655
queue_name: str,
@@ -95,7 +94,7 @@ def create_external_grader_detail(student_item_dict,
9594
queue_name=queue_name,
9695
grader_file_name=grader_file_name,
9796
points_possible=points_possible,
98-
97+
queue_key=external_grader_additional_data.get('queue_key'),
9998
)
10099

101100
files_dict = external_grader_additional_data.get('files')

submissions/tests/test_api.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ def test_get_or_create_student_item_race_condition__item_not_created(self):
869869
api._get_or_create_student_item(STUDENT_ITEM) # pylint: disable=protected-access
870870

871871
def test_create_external_grader_detail(self):
872-
external_grader_detail_data = {'queue_name': 'test_queue'}
872+
external_grader_detail_data = {'queue_name': 'test_queue', 'queue_key': 'test_queue'}
873873
external_grader_detail = api.create_external_grader_detail(STUDENT_ITEM, ANSWER_ONE,
874874
**external_grader_detail_data)
875875

@@ -880,11 +880,11 @@ def test_create_external_grader_detail(self):
880880
self.assertEqual(external_grader_detail.queue_name, 'test_queue')
881881

882882
def test_create_multiple_external_grader_detail(self):
883-
external_grader_detail_data1 = {'queue_name': 'test_queue'}
883+
external_grader_detail_data1 = {'queue_name': 'test_queue', 'queue_key': 'test_queue'}
884884
external_grader_detail1 = api.create_external_grader_detail(STUDENT_ITEM, ANSWER_ONE,
885885
**external_grader_detail_data1)
886886

887-
external_grader_detail_data2 = {'queue_name': 'test_queue'}
887+
external_grader_detail_data2 = {'queue_name': 'test_queue', 'queue_key': 'test_queue'}
888888
external_grader_detail2 = api.create_external_grader_detail(SECOND_STUDENT_ITEM, ANSWER_ONE,
889889
**external_grader_detail_data2)
890890

@@ -910,10 +910,15 @@ def test_create_external_grader_detail_directly_database_error(self):
910910
api.create_external_grader_detail(STUDENT_ITEM, ANSWER_ONE, queue_name="test_queue")
911911

912912
def test_create_multiple_submission_external_grader_details(self):
913-
external_grader_detail1 = api.create_external_grader_detail(STUDENT_ITEM, ANSWER_ONE, queue_name="shared_queue")
913+
external_grader_detail1 = api.create_external_grader_detail(STUDENT_ITEM,
914+
ANSWER_ONE,
915+
queue_name="shared_queue",
916+
queue_key="test_queue")
914917

915-
external_grader_detail2 = api.create_external_grader_detail(SECOND_STUDENT_ITEM, ANSWER_TWO,
916-
queue_name="shared_queue")
918+
external_grader_detail2 = api.create_external_grader_detail(SECOND_STUDENT_ITEM,
919+
ANSWER_TWO,
920+
queue_name="shared_queue",
921+
queue_key="test_queue")
917922

918923
submission1 = Submission.objects.get(uuid=external_grader_detail1.submission.uuid)
919924
self.assertEqual(external_grader_detail1.queue_name, 'shared_queue')
@@ -926,7 +931,7 @@ def test_create_multiple_submission_external_grader_details(self):
926931
self.assertNotEqual(external_grader_detail1.submission.uuid, external_grader_detail2.submission.uuid)
927932

928933
def test_create_external_grader_detail_with_files(self):
929-
"""Test creating a queue record with file handling."""
934+
"""Test creating a external grader with file handling."""
930935

931936
test_file = SimpleUploadedFile(
932937
"test.txt",
@@ -936,6 +941,7 @@ def test_create_external_grader_detail_with_files(self):
936941

937942
event_data = {
938943
'queue_name': 'test_queue',
944+
'queue_key': 'test_queue',
939945
'files': {'test.txt': test_file}
940946
}
941947

@@ -947,7 +953,7 @@ def test_create_external_grader_detail_with_files(self):
947953
self.assertEqual(submission_file.original_filename, 'test.txt')
948954

949955
def test_create_external_grader_detail_with_multiple_files(self):
950-
"""Test creating a queue record with multiple files."""
956+
"""Test creating a external grader with multiple files."""
951957
test_files = {
952958
'test1.txt': SimpleUploadedFile(
953959
"test1.txt",
@@ -963,6 +969,7 @@ def test_create_external_grader_detail_with_multiple_files(self):
963969

964970
external_grader_detail = {
965971
'queue_name': 'test_queue',
972+
'queue_key': 'test_queue',
966973
'files': test_files
967974
}
968975
external_grader_detail = api.create_external_grader_detail(STUDENT_ITEM, ANSWER_ONE, **external_grader_detail)
@@ -972,7 +979,10 @@ def test_create_external_grader_detail_with_multiple_files(self):
972979
self.assertEqual(filenames, {'test1.txt', 'test2.txt'})
973980

974981
def test_create_external_grader_detail_without_files(self):
975-
"""Test creating a queue record without any files still works."""
976-
external_grader_instance = api.create_external_grader_detail(STUDENT_ITEM, ANSWER_ONE, queue_name="test_queue")
982+
"""Test creating a external grader without any files still works."""
983+
external_grader_instance = api.create_external_grader_detail(STUDENT_ITEM,
984+
ANSWER_ONE,
985+
queue_name="test_queue",
986+
queue_key="test_queue")
977987
self.assertEqual(external_grader_instance.queue_name, 'test_queue')
978988
self.assertEqual(external_grader_instance.files.count(), 0)

submissions/tests/test_models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def setUp(self):
324324
)
325325

326326
def test_default_status(self):
327-
"""Test that new queue records are created with 'pending' status."""
327+
"""Test that new external graders are created with 'pending' status."""
328328
self.assertEqual(self.external_grader_detail.status, 'pending')
329329
self.assertEqual(self.external_grader_detail.num_failures, 0)
330330

@@ -788,7 +788,7 @@ def test_unique_uids(self):
788788
)
789789

790790
def test_related_name_access(self):
791-
"""Test accessing files through the submission queue record."""
791+
"""Test accessing files through the submission external grader."""
792792
files = self.external_grader_detail.files.all()
793793
self.assertEqual(files.count(), 1)
794794
self.assertEqual(files.first(), self.submission_file)

submissions/tests/test_viewsets.py

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
"""
44
import json
55
import uuid
6+
from datetime import timedelta
67
from unittest.mock import patch
78

89
from django.contrib.auth import get_user_model
910
from django.core.files.base import ContentFile
1011
from django.db import DatabaseError
1112
from django.test import override_settings
1213
from django.urls import reverse
14+
from django.utils import timezone
1315
from rest_framework import status
1416
from rest_framework.permissions import AllowAny, IsAuthenticated
1517
from rest_framework.test import APITestCase
@@ -35,7 +37,7 @@ def setUp(self):
3537
password='testpass'
3638
)
3739
self.submission = SubmissionFactory()
38-
self.queue_record = ExternalGraderDetailFactory(
40+
self.external_grader_detail = ExternalGraderDetailFactory(
3941
submission=self.submission,
4042
pullkey='test_pull_key',
4143
status='pending',
@@ -145,7 +147,7 @@ def test_get_submission_invalid_transition(self, mock_update_status):
145147
self.assertEqual(response.status_code, status.HTTP_200_OK)
146148

147149
new_submission = SubmissionFactory()
148-
queue_record = ExternalGraderDetailFactory(
150+
external_grader_detail = ExternalGraderDetailFactory(
149151
submission=new_submission,
150152
queue_name=queue_name,
151153
status='pending'
@@ -160,8 +162,8 @@ def test_get_submission_invalid_transition(self, mock_update_status):
160162
self.viewset.compose_reply(False, "Error processing submission: Invalid transition")
161163
)
162164

163-
queue_record.refresh_from_db()
164-
self.assertEqual(queue_record.status, 'pending')
165+
external_grader_detail.refresh_from_db()
166+
self.assertEqual(external_grader_detail.status, 'pending')
165167

166168
def test_put_result_invalid_submission_id(self):
167169
"""Test put_result with non-existent submission ID."""
@@ -253,9 +255,9 @@ def test_put_result_set_score_failure(self):
253255

254256
self.assertEqual(response.status_code, status.HTTP_200_OK)
255257

256-
self.queue_record.refresh_from_db()
257-
self.assertEqual(self.queue_record.num_failures, 1)
258-
self.assertEqual(self.queue_record.status, 'pending')
258+
self.external_grader_detail.refresh_from_db()
259+
self.assertEqual(self.external_grader_detail.num_failures, 1)
260+
self.assertEqual(self.external_grader_detail.status, 'pending')
259261

260262
def test_put_result_set_score_fail_30_times(self):
261263
"""
@@ -279,10 +281,10 @@ def test_put_result_set_score_fail_30_times(self):
279281

280282
self.assertEqual(response.status_code, status.HTTP_200_OK)
281283

282-
self.queue_record.refresh_from_db()
283-
self.assertEqual(self.queue_record.num_failures, each+1)
284+
self.external_grader_detail.refresh_from_db()
285+
self.assertEqual(self.external_grader_detail.num_failures, each+1)
284286

285-
self.assertEqual(self.queue_record.status, 'failed')
287+
self.assertEqual(self.external_grader_detail.status, 'failed')
286288

287289
def test_put_result_auto_retire(self):
288290
"""
@@ -292,8 +294,8 @@ def test_put_result_auto_retire(self):
292294
response = self.client.post(self.url_status)
293295
self.assertEqual(response.status_code, status.HTTP_200_OK)
294296
initial_failures = 29
295-
self.queue_record.num_failures = initial_failures
296-
self.queue_record.save()
297+
self.external_grader_detail.num_failures = initial_failures
298+
self.external_grader_detail.save()
297299

298300
payload = {
299301
'xqueue_header': json.dumps({
@@ -307,19 +309,19 @@ def test_put_result_auto_retire(self):
307309
with patch('submissions.api.set_score') as mock_set_score:
308310
mock_set_score.side_effect = Exception('Test error')
309311
_ = self.client.post(self.url, payload, format='json')
310-
self.queue_record.refresh_from_db()
312+
self.external_grader_detail.refresh_from_db()
311313

312-
self.queue_record.refresh_from_db()
314+
self.external_grader_detail.refresh_from_db()
313315

314316
@patch('submissions.views.xqueue.log')
315317
def test_put_result_logging(self, mock_log):
316318
"""
317319
Test that appropriate logging occurs in various escenarios.
318320
"""
319-
self.submission.queue_record.status = 'pulled'
320-
self.submission.queue_record.save()
321-
self.submission.queue_record.refresh_from_db()
322-
self.assertEqual(self.submission.queue_record.status, 'pulled')
321+
self.submission.external_grader_detail.status = 'pulled'
322+
self.submission.external_grader_detail.save()
323+
self.submission.external_grader_detail.refresh_from_db()
324+
self.assertEqual(self.submission.external_grader_detail.status, 'pulled')
323325

324326
payload = {
325327
'xqueue_header': json.dumps({
@@ -344,10 +346,10 @@ def test_put_result_success(self, mock_log):
344346
"""
345347
Test that appropriate logging occurs in various scenarios.
346348
"""
347-
self.submission.queue_record.status = 'pulled'
348-
self.submission.queue_record.save()
349-
self.submission.queue_record.refresh_from_db()
350-
self.assertEqual(self.submission.queue_record.status, 'pulled')
349+
self.submission.external_grader_detail.status = 'pulled'
350+
self.submission.external_grader_detail.save()
351+
self.submission.external_grader_detail.refresh_from_db()
352+
self.assertEqual(self.submission.external_grader_detail.status, 'pulled')
351353

352354
payload = {
353355
'xqueue_header': json.dumps({
@@ -498,7 +500,7 @@ def test_get_submission_with_files(self):
498500

499501
file_content = b'Test file content'
500502
submission_file = SubmissionFile.objects.create(
501-
submission_queue=self.queue_record,
503+
submission_queue=self.external_grader_detail,
502504
file=ContentFile(file_content, name='test.txt'),
503505
original_filename='test.txt'
504506
)
@@ -525,7 +527,7 @@ def test_get_submission_with_multiple_files(self):
525527
created_files = []
526528
for filename, content in files_data:
527529
submission_file = SubmissionFile.objects.create(
528-
submission_queue=self.queue_record,
530+
submission_queue=self.external_grader_detail,
529531
file=ContentFile(content, name=filename),
530532
original_filename=filename
531533
)
@@ -548,7 +550,7 @@ def test_get_submission_file_urls_format(self):
548550

549551
test_uuid = uuid.uuid4()
550552
_ = SubmissionFile.objects.create(
551-
submission_queue=self.queue_record,
553+
submission_queue=self.external_grader_detail,
552554
file=ContentFile(b'content', name='test.txt'),
553555
original_filename='test.txt',
554556
uid=test_uuid
@@ -561,3 +563,29 @@ def test_get_submission_file_urls_format(self):
561563

562564
expected_url = f'/test_queue/{test_uuid}'
563565
self.assertEqual(xqueue_files['test.txt'], expected_url)
566+
567+
def test_get_submission_already_pulled_status(self):
568+
"""Test get_submission when the status is already 'pulled'."""
569+
self.client.login(username='testuser', password='testpass')
570+
571+
queue_name = 'test_already_pulled'
572+
submission = SubmissionFactory()
573+
old_time = timezone.now() - timedelta(minutes=10)
574+
575+
external_grader_detail = ExternalGraderDetailFactory(
576+
submission=submission,
577+
queue_name=queue_name,
578+
status='pulled',
579+
status_time=old_time,
580+
pullkey='original_key'
581+
)
582+
583+
response = self.client.get(self.get_submission_url, {'queue_name': queue_name})
584+
585+
self.assertEqual(response.status_code, status.HTTP_200_OK)
586+
587+
external_grader_detail.refresh_from_db()
588+
589+
self.assertEqual(external_grader_detail.pullkey, 'original_key')
590+
self.assertEqual(external_grader_detail.status, 'pulled')
591+
self.assertEqual(external_grader_detail.status_time, old_time)

0 commit comments

Comments
 (0)