Coverage for src / mesh / tests / models / test_submission_models.py: 100%
313 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-11 09:47 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-08-11 09:47 +0000
1from unittest.mock import patch
3from django.db.models import Prefetch
4from django.utils import timezone
6from mesh.models.exceptions import SubmissionStateError
7from mesh.models.orm.editorial_models import EditorSubmissionRight
9# from ...models.factories import ReviewFactory
10from mesh.models.orm.factories import (
11 EditorialDecisionFactory,
12 SubmissionAuthorFactory,
13 SubmissionFactory,
14 SubmissionVersionFactory,
15 UserFactory,
16)
17from mesh.models.orm.submission_models import (
18 Submission,
19 SubmissionAuthor,
20 SubmissionLog,
21 SubmissionMainFile,
22 SubmissionState,
23 SubmissionVersion,
24)
25from mesh.models.orm.user_models import User
26from mesh.tests.base_test_case import BaseTestCase
29class SubmissionTestCase(BaseTestCase):
30 # Emptying the Submission table will delete all other models thanks to their
31 # cascade relationship. All models are related to a Submission somehow, except
32 # JournalSection and user models.
33 used_models = [Submission]
34 class_used_models = [User]
36 @classmethod
37 def setUpClass(cls):
38 super().setUpClass()
39 cls.user_author = UserFactory.create()
41 # def test_unique_submission_name_per_user(self):
42 # submission = SubmissionFactory.create(created_by=self.user_author)
43 # # We need to encapsulate the database exception in a transaction, otherwise
44 # # successive DB operations are not permitted.
45 # with transaction.atomic():
46 # self.assertRaises(
47 # IntegrityError,
48 # SubmissionFactory.create,
49 # name=submission.name,
50 # created_by=self.user_author,
51 # )
53 def test_date_submission(self):
54 submission = SubmissionFactory.create(created_by=self.user_author)
55 self.assertNotEqual(submission.date_created, submission.date_first_version)
56 self.assertEqual(submission.date_submission, submission.date_created)
58 submission.date_first_version = timezone.now()
59 submission.save()
61 self.assertNotEqual(submission.date_created, submission.date_first_version)
62 self.assertEqual(submission.date_submission, submission.date_first_version)
64 def test_is_draft(self):
65 submission = SubmissionFactory.create(created_by=self.user_author)
66 self.assertTrue(submission.is_draft)
68 submission.state = SubmissionState.SUBMITTED.value
69 self.assertFalse(submission.is_draft)
71 def test_version_attributes(self):
72 """
73 This tests the submission attributes `all_versions` and `current_version`
74 along with the `unique_draft_submission_version` constraint
75 """
76 submission = SubmissionFactory.create(created_by=self.user_author)
77 versions = submission.versions.all()
78 self.assertEqual(len(versions), 0)
79 self.assertIsNone(versions.first())
81 version_1 = SubmissionVersion(submission=submission, created_by=self.user_author)
82 version_1.save()
84 submission = Submission.objects.get(pk=submission.pk)
85 versions = submission.versions.all()
86 self.assertEqual(len(versions), 1)
87 self.assertEqual(versions[0].pk, version_1.pk)
88 self.assertEqual(versions.first().pk, version_1.pk)
90 version_2 = SubmissionVersion(submission=submission, created_by=self.user_author)
91 # The UniqueConstraint has been removed.
92 # You can have more than 1 version submitted (when you reach Round 3)
93 # # Can't save because version_1 is not submitted yet.
94 # with transaction.atomic():
95 # self.assertRaises(
96 # IntegrityError,
97 # version_2.save,
98 # )
100 version_1.submitted = True
101 version_1.save()
102 version_2.save()
103 submission = Submission.objects.get(pk=submission.pk)
104 versions = submission.versions.all()
105 self.assertEqual(len(versions), 2)
106 self.assertEqual(versions[0].pk, version_2.pk)
107 self.assertEqual(versions[1].pk, version_1.pk)
108 self.assertEqual(versions.first().pk, version_2.pk)
110 def test_all_authors(self):
111 submission = SubmissionFactory.create()
112 authors = submission.authors.all()
113 self.assertEqual(len(authors), 0)
115 author_1 = SubmissionAuthorFactory.create(submission=submission, last_name="B")
116 author_2 = SubmissionAuthorFactory.create(submission=submission, last_name="A")
117 submission = Submission.objects.get(pk=submission.pk)
118 authors = submission.authors.all()
119 self.assertEqual(len(authors), 2)
120 self.assertEqual(authors[0].pk, author_2.pk)
121 self.assertEqual(authors[1].pk, author_1.pk)
123 # # Test unique constraint
124 # with transaction.atomic():
125 # self.assertRaises(
126 # IntegrityError,
127 # SubmissionAuthorFactory.create,
128 # submission=submission,
129 # email=author_1.email,
130 # )
132 def test_all_assigned_editors(self):
133 submission = SubmissionFactory.create()
134 editor_1 = UserFactory.create(first_name="Jean")
135 editor_2 = UserFactory.create(first_name="Amélie")
136 _ = EditorSubmissionRight.objects.create(user=editor_1, submission=submission)
137 _ = EditorSubmissionRight.objects.create(user=editor_2, submission=submission)
139 assigned_editors = submission.all_assigned_editors
140 self.assertEqual(len(assigned_editors), 2)
141 self.assertEqual(assigned_editors[0].pk, editor_2.pk)
142 self.assertEqual(assigned_editors[1].pk, editor_1.pk)
145class SubmissionWorkflowTestCase(BaseTestCase):
146 used_models = [Submission]
148 @classmethod
149 def setUpClass(cls):
150 super().setUpClass()
151 cls.user_author = UserFactory.create()
152 cls.user_reviewer = UserFactory.create()
153 cls.user_editor = UserFactory.create()
154 cls.user_journal_manager = UserFactory.create(journal_manager=True)
155 cls.user_admin = UserFactory.create(is_superuser=True)
157 @classmethod
158 def tearDownClass(cls):
159 super().tearDownClass()
160 User.objects.all().delete()
162 def setUp(self):
163 super().setUp()
165 def test_is_submittable(self):
166 submission = SubmissionFactory.create(created_by=self.user_author)
167 self.assertFalse(submission.is_submittable())
169 submission.state = SubmissionState.OPENED.value
170 submission.author_agreement = True
171 submission.save()
172 submission_version = SubmissionVersionFactory.create(submission=submission)
173 SubmissionMainFile.objects.create(attached_to=submission_version, file=self.dummy_file)
174 SubmissionAuthorFactory.create(submission=submission)
175 submission = Submission.objects.get(pk=submission.pk)
176 self.assertTrue(submission.is_submittable())
178 # Test each condition separately
179 submission.state = SubmissionState.SUBMITTED.value
180 self.assertFalse(submission.is_submittable())
182 submission.state = SubmissionState.OPENED.value
183 self.assertTrue(submission.is_submittable())
184 SubmissionAuthor.objects.all().delete()
185 submission = Submission.objects.get(pk=submission.pk)
186 self.assertFalse(submission.is_submittable())
188 SubmissionAuthorFactory.create(submission=submission)
189 submission = Submission.objects.get(pk=submission.pk)
190 self.assertTrue(submission.is_submittable())
191 submission_version.submitted = True
192 submission_version.save()
193 submission = Submission.objects.get(pk=submission.pk)
194 self.assertFalse(submission.is_submittable())
196 submission_version.submitted = False
197 submission_version.save()
198 submission = Submission.objects.get(pk=submission.pk)
199 self.assertTrue(submission.is_submittable())
200 SubmissionMainFile.objects.all().delete()
201 submission = Submission.objects.get(pk=submission.pk)
202 self.assertFalse(submission.is_submittable())
204 def test_submit(self):
205 submission = SubmissionFactory.create(created_by=self.user_author, author_agreement=True)
206 submission_version = SubmissionVersionFactory.create(submission=submission)
207 submission = Submission.objects.get(pk=submission.pk)
208 self.assertFalse(submission_version.submitted)
209 self.assertIsNone(submission_version.date_submitted)
210 self.assertEqual(submission.state, SubmissionState.OPENED.value)
211 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists())
212 request = self.dummy_request()
213 request.user = self.user_author
214 date_last_activity = submission.date_last_activity
215 self.assertIsNone(date_last_activity)
217 # Test submitting non-submittable submission
218 with patch.object(Submission, "is_submittable", lambda _: False):
219 self.assertRaises(SubmissionStateError, submission.submit, self.user_author)
221 submission = Submission.objects.get(pk=submission.pk)
222 self.assertEqual(submission.state, SubmissionState.OPENED.value)
223 submission_version = submission.versions.first()
224 self.assertFalse(submission_version.submitted)
225 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists())
226 date_last_activity = submission.date_last_activity
227 self.assertIsNone(date_last_activity)
229 # Test submiting a submittable OPENED submission
230 with patch.object(Submission, "is_submittable", lambda _: True):
231 submission.submit(self.user_author)
233 submission = Submission.objects.get(pk=submission.pk)
234 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
235 submission_version: SubmissionVersion = submission.versions.first()
236 self.assertTrue(submission_version.submitted)
237 self.assertIsNotNone(submission_version.date_submitted)
238 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission).all()), 1)
239 date_last_activity = submission.date_last_activity
240 self.assertIsNotNone(date_last_activity)
242 # Test submiting a 2nd version of the REVISION_REQUESTED submission
243 submission_version.review_open = False
244 submission_version.save()
245 submission_version_2 = SubmissionVersionFactory.create(submission=submission)
246 self.assertFalse(submission_version_2.submitted)
247 submission.state = SubmissionState.REVISION_REQUESTED.value
248 submission = Submission.objects.get(pk=submission.pk)
250 with patch.object(Submission, "is_submittable", lambda _: True):
251 submission.submit(self.user_author)
253 submission = Submission.objects.get(pk=submission.pk)
254 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
255 current_version: SubmissionVersion = submission.versions.first()
256 self.assertEqual(current_version.pk, submission_version_2.pk)
257 self.assertTrue(current_version.submitted)
258 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission).all()), 2)
259 date_last_activity_2 = submission.date_last_activity
260 self.assertIsNotNone(date_last_activity_2)
261 self.assertGreater(date_last_activity_2, date_last_activity)
263 def test_is_reviewable(self):
264 submission = SubmissionFactory.create(state=SubmissionState.SUBMITTED.value)
265 version = SubmissionVersionFactory.create(
266 submission=submission, submitted=True, review_open=False
267 )
268 # Not reviewable if the version has no main file
269 submission = Submission.objects.prefetch_related(
270 Prefetch("versions", SubmissionVersion.objects.all(), to_attr="versions_censored")
271 ).get(pk=submission.pk)
272 self.assertFalse(submission.is_reviewable())
274 SubmissionMainFile.objects.create(file=self.dummy_file, attached_to=version)
275 submission = Submission.objects.prefetch_related(
276 Prefetch("versions", SubmissionVersion.objects.all(), to_attr="versions_censored")
277 ).get(pk=submission.pk)
278 self.assertTrue(submission.is_reviewable())
280 # Test all submission state
281 submission.state = SubmissionState.REVISION_SUBMITTED.value
282 submission.save()
283 self.assertTrue(submission.is_reviewable())
285 submission.state = SubmissionState.OPENED.value
286 submission.save()
287 self.assertFalse(submission.is_reviewable())
289 submission.state = SubmissionState.ON_REVIEW.value
290 submission.save()
291 self.assertFalse(submission.is_reviewable())
293 submission.state = SubmissionState.REVISION_REQUESTED.value
294 submission.save()
295 self.assertFalse(submission.is_reviewable())
297 submission.state = SubmissionState.ACCEPTED.value
298 submission.save()
299 self.assertFalse(submission.is_reviewable())
301 submission.state = SubmissionState.REJECTED.value
302 submission.save()
303 self.assertFalse(submission.is_reviewable())
305 # Test version parameters
306 # Already on review
307 submission.state = SubmissionState.SUBMITTED.value
308 submission.save()
309 self.assertTrue(submission.is_reviewable())
310 version.review_open = True
311 version.save()
312 submission = Submission.objects.prefetch_related(
313 Prefetch("versions", SubmissionVersion.objects.all(), to_attr="versions_censored")
314 ).get(pk=submission.pk)
315 self.assertFalse(submission.is_reviewable())
317 version.review_open = False
318 version.save()
319 submission = Submission.objects.prefetch_related(
320 Prefetch("versions", SubmissionVersion.objects.all(), to_attr="versions_censored")
321 ).get(pk=submission.pk)
322 self.assertTrue(submission.is_reviewable())
323 version.submitted = False
324 version.save()
325 submission = Submission.objects.prefetch_related(
326 Prefetch("versions", SubmissionVersion.objects.all(), to_attr="versions_censored")
327 ).get(pk=submission.pk)
328 self.assertFalse(submission.is_reviewable())
330 def test_start_review_process(self):
331 submission = SubmissionFactory.create(
332 created_by=self.user_author, state=SubmissionState.SUBMITTED.value
333 )
334 version = SubmissionVersionFactory.create(
335 submission=submission, submitted=True, review_open=False
336 )
337 SubmissionMainFile.objects.create(file=self.dummy_file, attached_to=version)
339 submission = Submission.objects.get(pk=submission.pk)
341 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists())
342 self.assertEqual(submission.state, SubmissionState.SUBMITTED.value)
344 request = self.dummy_request()
345 request.user = self.user_editor
346 with patch.object(Submission, "is_reviewable", lambda _: False):
347 self.assertRaises(
348 SubmissionStateError, submission.start_review_process, self.user_editor
349 )
351 submission = Submission.objects.get(pk=submission.pk)
352 version.refresh_from_db()
353 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists())
354 self.assertEqual(submission.state, SubmissionState.SUBMITTED.value)
355 self.assertFalse(version.review_open)
357 with patch.object(Submission, "is_reviewable", lambda _: True):
358 submission.start_review_process(self.user_editor)
360 submission = Submission.objects.get(pk=submission.pk)
361 version.refresh_from_db()
362 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), 1)
363 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
364 self.assertTrue(version.review_open)
366 def test_apply_editorial_decision(self):
367 submission = SubmissionFactory.create(
368 created_by=self.user_author, state=SubmissionState.ON_REVIEW.value
369 )
370 version = SubmissionVersionFactory.create(
371 submission=submission, submitted=True, review_open=True
372 )
373 SubmissionMainFile.objects.create(file=self.dummy_file, attached_to=version)
375 submission = Submission.objects.get(pk=submission.pk)
376 log_count = 0
377 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
378 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
379 self.assertTrue(version.review_open)
381 decision = EditorialDecisionFactory.create(
382 version=version, value=SubmissionState.REVISION_REQUESTED.value
383 )
384 request = self.dummy_request()
385 request.user = self.user_editor
386 with patch.object(Submission, "is_draft", True):
387 self.assertRaises(
388 SubmissionStateError,
389 submission.apply_editorial_decision,
390 decision,
391 self.user_editor,
392 )
394 submission = Submission.objects.get(pk=submission.pk)
395 version.refresh_from_db()
396 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
397 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
398 self.assertTrue(version.review_open)
400 with patch.object(Submission, "is_draft", False):
401 submission.apply_editorial_decision(decision, self.user_editor)
403 submission = Submission.objects.get(pk=submission.pk)
404 version.refresh_from_db()
405 log_count += 1
406 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
407 self.assertEqual(submission.state, SubmissionState.REVISION_REQUESTED.value)
408 self.assertFalse(version.review_open)
410 # Test that an editorial decision is possible from all submisison state
411 # SUBMITTED
412 submission.state = SubmissionState.SUBMITTED.value
413 submission.save()
414 with patch.object(Submission, "is_draft", False):
415 submission.apply_editorial_decision(decision, self.user_editor)
417 submission = Submission.objects.get(pk=submission.pk)
418 version.refresh_from_db()
419 log_count += 1
420 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
421 self.assertEqual(submission.state, SubmissionState.REVISION_REQUESTED.value)
422 self.assertFalse(version.review_open)
424 # REVISION_REQUESTED
425 submission.state = SubmissionState.REVISION_REQUESTED.value
426 submission.save()
427 decision.value = SubmissionState.ACCEPTED.value
428 decision.save()
429 with patch.object(Submission, "is_draft", False):
430 submission.apply_editorial_decision(decision, self.user_editor)
432 submission = Submission.objects.get(pk=submission.pk)
433 version.refresh_from_db()
434 log_count += 1
435 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
436 self.assertEqual(submission.state, SubmissionState.ACCEPTED.value)
437 self.assertFalse(version.review_open)
439 # REVISION_SUBMITTED
440 submission.state = SubmissionState.REVISION_SUBMITTED.value
441 submission.save()
442 with patch.object(Submission, "is_draft", False):
443 submission.apply_editorial_decision(decision, self.user_editor)
445 submission = Submission.objects.get(pk=submission.pk)
446 version.refresh_from_db()
447 log_count += 1
448 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
449 self.assertEqual(submission.state, SubmissionState.ACCEPTED.value)
450 self.assertFalse(version.review_open)
452 # REJECTED
453 submission.state = SubmissionState.REJECTED.value
454 submission.save()
455 with patch.object(Submission, "is_draft", False):
456 submission.apply_editorial_decision(decision, self.user_editor)
458 submission = Submission.objects.get(pk=submission.pk)
459 version.refresh_from_db()
460 log_count += 1
461 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
462 self.assertEqual(submission.state, SubmissionState.ACCEPTED.value)
463 self.assertFalse(version.review_open)
465 # ACCEPTED
466 submission.state = SubmissionState.ACCEPTED.value
467 submission.save()
468 decision.value = SubmissionState.REJECTED.value
469 decision.save()
470 with patch.object(Submission, "is_draft", False):
471 submission.apply_editorial_decision(decision, self.user_editor)
473 submission = Submission.objects.get(pk=submission.pk)
474 version.refresh_from_db()
475 log_count += 1
476 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
477 self.assertEqual(submission.state, SubmissionState.REJECTED.value)
478 self.assertFalse(version.review_open)