From 1a2996e5f6fc9440a0df96dba5d64b9667cbfd04 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Fri, 24 May 2024 11:30:01 -0300 Subject: [PATCH] feat: expire submissions after 14 days (#7461) * feat: expire submissions after 14 days * test: update test_cancel_stale_submissions --- ietf/settings.py | 3 +++ ietf/submit/tasks.py | 19 ++++++++++++++++-- ietf/submit/tests.py | 47 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/ietf/settings.py b/ietf/settings.py index 8bb264bd6..95a8ad4e4 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -817,6 +817,9 @@ IDSUBMIT_CHECKER_CLASSES = ( # Max time to allow for validation before a submission is subject to cancellation IDSUBMIT_MAX_VALIDATION_TIME = datetime.timedelta(minutes=20) +# Age at which a submission expires if not posted +IDSUBMIT_EXPIRATION_AGE = datetime.timedelta(days=14) + IDSUBMIT_MANUAL_STAGING_DIR = '/tmp/' IDSUBMIT_FILE_TYPES = ( diff --git a/ietf/submit/tasks.py b/ietf/submit/tasks.py index 382bff7fa..9a13268bc 100644 --- a/ietf/submit/tasks.py +++ b/ietf/submit/tasks.py @@ -37,19 +37,34 @@ def process_and_accept_uploaded_submission_task(submission_id): @shared_task def cancel_stale_submissions(): now = timezone.now() - stale_submissions = Submission.objects.filter( + # first check for submissions gone stale awaiting validation + stale_unvalidated_submissions = Submission.objects.filter( state_id='validating', ).annotate( submitted_at=Min('submissionevent__time'), ).filter( submitted_at__lt=now - settings.IDSUBMIT_MAX_VALIDATION_TIME, ) - for subm in stale_submissions: + for subm in stale_unvalidated_submissions: age = now - subm.submitted_at log.log(f'Canceling stale submission (id={subm.id}, age={age})') cancel_submission(subm) create_submission_event(None, subm, 'Submission canceled: validation checks took too long') + # now check for expired submissions + expired_submissions = Submission.objects.exclude( + state_id__in=["posted", "cancel"], + ).annotate( + submitted_at=Min("submissionevent__time"), + ).filter( + submitted_at__lt=now - settings.IDSUBMIT_EXPIRATION_AGE, + ) + for subm in expired_submissions: + age = now - subm.submitted_at + log.log(f'Canceling expired submission (id={subm.id}, age={age})') + cancel_submission(subm) + create_submission_event(None, subm, 'Submission canceled: expired without being posted') + @shared_task(bind=True) def poke(self): diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py index 58a47aef8..618f237e4 100644 --- a/ietf/submit/tests.py +++ b/ietf/submit/tests.py @@ -42,7 +42,7 @@ from ietf.group.models import Group from ietf.group.utils import setup_default_community_list_for_group from ietf.meeting.models import Meeting from ietf.meeting.factories import MeetingFactory -from ietf.name.models import FormalLanguageName +from ietf.name.models import DraftSubmissionStateName, FormalLanguageName from ietf.person.models import Person from ietf.person.factories import UserFactory, PersonFactory, EmailFactory from ietf.submit.factories import SubmissionFactory, SubmissionExtResourceFactory @@ -3136,28 +3136,59 @@ class AsyncSubmissionTests(BaseSubmitTestCase): self.assertContains(r, s.name) self.assertContains(r, 'This submission is being processed and validated.', status_code=200) - @override_settings(IDSUBMIT_MAX_VALIDATION_TIME=datetime.timedelta(minutes=30)) + @override_settings( + IDSUBMIT_MAX_VALIDATION_TIME=datetime.timedelta(minutes=30), + IDSUBMIT_EXPIRATION_AGE=datetime.timedelta(minutes=90), + ) def test_cancel_stale_submissions(self): + # these will be lists of (Submission, "state_id") pairs + submissions_to_skip = [] + submissions_to_cancel = [] + + # submissions in the validating state fresh_submission = SubmissionFactory(state_id='validating') fresh_submission.submissionevent_set.create( desc='fake created event', time=timezone.now() - datetime.timedelta(minutes=15), ) + submissions_to_skip.append((fresh_submission, "validating")) + stale_submission = SubmissionFactory(state_id='validating') stale_submission.submissionevent_set.create( desc='fake created event', time=timezone.now() - datetime.timedelta(minutes=30, seconds=1), ) + submissions_to_cancel.append((stale_submission, "validating")) + + # submissions in other states + for state in DraftSubmissionStateName.objects.filter(used=True).exclude(slug="validating"): + to_skip = SubmissionFactory(state_id=state.pk) + to_skip.submissionevent_set.create( + desc="fake created event", + time=timezone.now() - datetime.timedelta(minutes=45), # would be canceled if it were "validating" + ) + submissions_to_skip.append((to_skip, state.pk)) + to_expire = SubmissionFactory(state_id=state.pk) + to_expire.submissionevent_set.create( + desc="fake created event", + time=timezone.now() - datetime.timedelta(minutes=90, seconds=1), + ) + if state.pk in ["posted", "cancel"]: + submissions_to_skip.append((to_expire, state.pk)) # these ones should not be expired regardless of age + else: + submissions_to_cancel.append(((to_expire, state.pk))) cancel_stale_submissions() - fresh_submission = Submission.objects.get(pk=fresh_submission.pk) - self.assertEqual(fresh_submission.state_id, 'validating') - self.assertEqual(fresh_submission.submissionevent_set.count(), 1) + for _subm, original_state_id in submissions_to_skip: + subm = Submission.objects.get(pk=_subm.pk) + self.assertEqual(subm.state_id, original_state_id) + self.assertEqual(subm.submissionevent_set.count(), 1) - stale_submission = Submission.objects.get(pk=stale_submission.pk) - self.assertEqual(stale_submission.state_id, 'cancel') - self.assertEqual(stale_submission.submissionevent_set.count(), 2) + for _subm, _ in submissions_to_cancel: + subm = Submission.objects.get(pk=_subm.pk) + self.assertEqual(subm.state_id, "cancel") + self.assertEqual(subm.submissionevent_set.count(), 2) class ApiSubmitTests(BaseSubmitTestCase):