Coverage for src/mesh/tests/models/test_submission_models.py: 100%
307 statements
« prev ^ index » next coverage.py v7.9.0, created at 2026-02-04 09:42 +0000
« prev ^ index » next coverage.py v7.9.0, created at 2026-02-04 09:42 +0000
1from unittest.mock import patch
3from django.utils import timezone
5from mesh.model.exceptions import SubmissionStateError
7from ...models.editorial_models import EditorSubmissionRight
9# from ...models.factories import ReviewFactory
10from ...models.factories import (
11 EditorialDecisionFactory,
12 SubmissionAuthorFactory,
13 SubmissionFactory,
14 SubmissionVersionFactory,
15 UserFactory,
16)
17from ...models.submission_models import (
18 Submission,
19 SubmissionAuthor,
20 SubmissionLog,
21 SubmissionMainFile,
22 SubmissionState,
23 SubmissionVersion,
24)
25from ...models.user_models import User
26from ..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 self.assertEqual(len(submission.all_versions), 0)
78 self.assertIsNone(submission.current_version)
80 version_1 = SubmissionVersion(submission=submission, created_by=self.user_author)
81 version_1.save()
83 submission = Submission.objects.get(pk=submission.pk)
84 self.assertEqual(len(submission.all_versions), 1)
85 self.assertEqual(submission.all_versions[0].pk, version_1.pk)
86 self.assertEqual(submission.current_version.pk, version_1.pk) # type:ignore
88 version_2 = SubmissionVersion(submission=submission, created_by=self.user_author)
89 # The UniqueConstraint has been removed.
90 # You can have more than 1 version submitted (when you reach Round 3)
91 # # Can't save because version_1 is not submitted yet.
92 # with transaction.atomic():
93 # self.assertRaises(
94 # IntegrityError,
95 # version_2.save,
96 # )
98 version_1.submitted = True
99 version_1.save()
100 version_2.save()
101 submission = Submission.objects.get(pk=submission.pk)
102 self.assertEqual(len(submission.all_versions), 2)
103 self.assertEqual(submission.all_versions[0].pk, version_2.pk)
104 self.assertEqual(submission.all_versions[1].pk, version_1.pk)
105 self.assertEqual(submission.current_version.pk, version_2.pk) # type:ignore
107 def test_all_authors(self):
108 submission = SubmissionFactory.create()
109 self.assertEqual(len(submission.all_authors), 0)
111 author_1 = SubmissionAuthorFactory.create(submission=submission, first_name="B")
112 author_2 = SubmissionAuthorFactory.create(submission=submission, first_name="A")
113 submission = Submission.objects.get(pk=submission.pk)
114 self.assertEqual(len(submission.all_authors), 2)
115 self.assertEqual(submission.all_authors[0].pk, author_2.pk)
116 self.assertEqual(submission.all_authors[1].pk, author_1.pk)
118 # # Test unique constraint
119 # with transaction.atomic():
120 # self.assertRaises(
121 # IntegrityError,
122 # SubmissionAuthorFactory.create,
123 # submission=submission,
124 # email=author_1.email,
125 # )
127 def test_all_assigned_editors(self):
128 submission = SubmissionFactory.create()
129 editor_1 = UserFactory.create(first_name="Jean")
130 editor_2 = UserFactory.create(first_name="Amélie")
131 _ = EditorSubmissionRight.objects.create(user=editor_1, submission=submission)
132 _ = EditorSubmissionRight.objects.create(user=editor_2, submission=submission)
134 assigned_editors = submission.all_assigned_editors
135 self.assertEqual(len(assigned_editors), 2)
136 self.assertEqual(assigned_editors[0].pk, editor_2.pk)
137 self.assertEqual(assigned_editors[1].pk, editor_1.pk)
140class SubmissionWorkflowTestCase(BaseTestCase):
141 used_models = [Submission]
143 @classmethod
144 def setUpClass(cls):
145 super().setUpClass()
146 cls.user_author = UserFactory.create()
147 cls.user_reviewer = UserFactory.create()
148 cls.user_editor = UserFactory.create()
149 cls.user_journal_manager = UserFactory.create(journal_manager=True)
150 cls.user_admin = UserFactory.create(is_superuser=True)
152 @classmethod
153 def tearDownClass(cls):
154 super().tearDownClass()
155 User.objects.all().delete()
157 def setUp(self):
158 super().setUp()
160 def test_is_submittable(self):
161 submission = SubmissionFactory.create(created_by=self.user_author)
162 self.assertFalse(submission.is_submittable)
164 submission.state = SubmissionState.OPENED.value
165 submission.author_agreement = True
166 submission.save()
167 submission_version = SubmissionVersionFactory.create(submission=submission)
168 SubmissionMainFile.objects.create(attached_to=submission_version, file=self.dummy_file)
169 SubmissionAuthorFactory.create(submission=submission)
170 submission = Submission.objects.get(pk=submission.pk)
171 self.assertTrue(submission.is_submittable)
173 # Test each condition separately
174 submission.state = SubmissionState.SUBMITTED.value
175 self.assertFalse(submission.is_submittable)
177 submission.state = SubmissionState.OPENED.value
178 self.assertTrue(submission.is_submittable)
179 SubmissionAuthor.objects.all().delete()
180 submission = Submission.objects.get(pk=submission.pk)
181 self.assertFalse(submission.is_submittable)
183 SubmissionAuthorFactory.create(submission=submission)
184 submission = Submission.objects.get(pk=submission.pk)
185 self.assertTrue(submission.is_submittable)
186 submission_version.submitted = True
187 submission_version.save()
188 submission = Submission.objects.get(pk=submission.pk)
189 self.assertFalse(submission.is_submittable)
191 submission_version.submitted = False
192 submission_version.save()
193 submission = Submission.objects.get(pk=submission.pk)
194 self.assertTrue(submission.is_submittable)
195 SubmissionMainFile.objects.all().delete()
196 submission = Submission.objects.get(pk=submission.pk)
197 self.assertFalse(submission.is_submittable)
199 def test_submit(self):
200 submission = SubmissionFactory.create(created_by=self.user_author, author_agreement=True)
201 submission_version = SubmissionVersionFactory.create(submission=submission)
202 submission = Submission.objects.get(pk=submission.pk)
203 self.assertFalse(submission_version.submitted)
204 self.assertIsNone(submission_version.date_submitted)
205 self.assertEqual(submission.state, SubmissionState.OPENED.value)
206 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists())
207 request = self.dummy_request()
208 request.user = self.user_author
209 date_last_activity = submission.date_last_activity # type:ignore
210 self.assertIsNone(date_last_activity)
212 # Test submitting non-submittable submission
213 with patch.object(Submission, "is_submittable", False):
214 self.assertRaises(SubmissionStateError, submission.submit, self.user_author)
216 submission = Submission.objects.get(pk=submission.pk)
217 self.assertEqual(submission.state, SubmissionState.OPENED.value)
218 submission_version: SubmissionVersion = submission.current_version # type:ignore
219 self.assertFalse(submission_version.submitted)
220 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists())
221 date_last_activity = submission.date_last_activity # type:ignore
222 self.assertIsNone(date_last_activity)
224 # Test submiting a submittable OPENED submission
225 with patch.object(Submission, "is_submittable", True):
226 submission.submit(self.user_author)
228 submission = Submission.objects.get(pk=submission.pk)
229 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
230 submission_version: SubmissionVersion = submission.current_version # type:ignore
231 self.assertTrue(submission_version.submitted)
232 self.assertIsNotNone(submission_version.date_submitted)
233 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission).all()), 1)
234 date_last_activity = submission.date_last_activity # type:ignore
235 self.assertIsNotNone(date_last_activity)
237 # Test submiting a 2nd version of the REVISION_REQUESTED submission
238 submission_version.review_open = False
239 submission_version.save()
240 submission_version_2 = SubmissionVersionFactory.create(submission=submission)
241 self.assertFalse(submission_version_2.submitted)
242 submission.state = SubmissionState.REVISION_REQUESTED.value
243 submission = Submission.objects.get(pk=submission.pk)
245 with patch.object(Submission, "is_submittable", True):
246 submission.submit(self.user_author)
248 submission = Submission.objects.get(pk=submission.pk)
249 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
250 current_version: SubmissionVersion = submission.current_version # type:ignore
251 self.assertEqual(current_version.pk, submission_version_2.pk)
252 self.assertTrue(current_version.submitted)
253 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission).all()), 2)
254 date_last_activity_2 = submission.date_last_activity # type:ignore
255 self.assertIsNotNone(date_last_activity_2)
256 self.assertGreater(date_last_activity_2, date_last_activity)
258 def test_is_reviewable(self):
259 submission = SubmissionFactory.create(state=SubmissionState.SUBMITTED.value)
260 version = SubmissionVersionFactory.create(
261 submission=submission, submitted=True, review_open=False
262 )
263 # Not reviewable if the version has no main file
264 submission = Submission.objects.get(pk=submission.pk)
265 self.assertFalse(submission.is_reviewable)
267 SubmissionMainFile.objects.create(file=self.dummy_file, attached_to=version)
268 submission = Submission.objects.get(pk=submission.pk)
269 self.assertTrue(submission.is_reviewable)
271 # Test all submission state
272 submission.state = SubmissionState.REVISION_SUBMITTED.value
273 submission.save()
274 self.assertTrue(submission.is_reviewable)
276 submission.state = SubmissionState.OPENED.value
277 submission.save()
278 self.assertFalse(submission.is_reviewable)
280 submission.state = SubmissionState.ON_REVIEW.value
281 submission.save()
282 self.assertFalse(submission.is_reviewable)
284 submission.state = SubmissionState.REVISION_REQUESTED.value
285 submission.save()
286 self.assertFalse(submission.is_reviewable)
288 submission.state = SubmissionState.ACCEPTED.value
289 submission.save()
290 self.assertFalse(submission.is_reviewable)
292 submission.state = SubmissionState.REJECTED.value
293 submission.save()
294 self.assertFalse(submission.is_reviewable)
296 # Test version parameters
297 # Already on review
298 submission.state = SubmissionState.SUBMITTED.value
299 submission.save()
300 self.assertTrue(submission.is_reviewable)
301 version.review_open = True
302 version.save()
303 submission = Submission.objects.get(pk=submission.pk)
304 self.assertFalse(submission.is_reviewable)
306 version.review_open = False
307 version.save()
308 submission = Submission.objects.get(pk=submission.pk)
309 self.assertTrue(submission.is_reviewable)
310 version.submitted = False
311 version.save()
312 submission = Submission.objects.get(pk=submission.pk)
313 self.assertFalse(submission.is_reviewable)
315 def test_start_review_process(self):
316 submission = SubmissionFactory.create(
317 created_by=self.user_author, state=SubmissionState.SUBMITTED.value
318 )
319 version = SubmissionVersionFactory.create(
320 submission=submission, submitted=True, review_open=False
321 )
322 SubmissionMainFile.objects.create(file=self.dummy_file, attached_to=version)
324 submission = Submission.objects.get(pk=submission.pk)
326 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists())
327 self.assertEqual(submission.state, SubmissionState.SUBMITTED.value)
329 request = self.dummy_request()
330 request.user = self.user_editor
331 with patch.object(Submission, "is_reviewable", False):
332 self.assertRaises(
333 SubmissionStateError, submission.start_review_process, self.user_editor
334 )
336 submission = Submission.objects.get(pk=submission.pk)
337 version.refresh_from_db()
338 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists())
339 self.assertEqual(submission.state, SubmissionState.SUBMITTED.value)
340 self.assertFalse(version.review_open)
342 with patch.object(Submission, "is_reviewable", True):
343 submission.start_review_process(self.user_editor)
345 submission = Submission.objects.get(pk=submission.pk)
346 version.refresh_from_db()
347 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), 1)
348 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
349 self.assertTrue(version.review_open)
351 def test_apply_editorial_decision(self):
352 submission = SubmissionFactory.create(
353 created_by=self.user_author, state=SubmissionState.ON_REVIEW.value
354 )
355 version = SubmissionVersionFactory.create(
356 submission=submission, submitted=True, review_open=True
357 )
358 SubmissionMainFile.objects.create(file=self.dummy_file, attached_to=version)
360 submission = Submission.objects.get(pk=submission.pk)
361 log_count = 0
362 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
363 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
364 self.assertTrue(version.review_open)
366 decision = EditorialDecisionFactory.create(
367 version=version, value=SubmissionState.REVISION_REQUESTED.value
368 )
369 request = self.dummy_request()
370 request.user = self.user_editor
371 with patch.object(Submission, "is_draft", True):
372 self.assertRaises(
373 SubmissionStateError,
374 submission.apply_editorial_decision,
375 decision,
376 self.user_editor,
377 )
379 submission = Submission.objects.get(pk=submission.pk)
380 version.refresh_from_db()
381 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
382 self.assertEqual(submission.state, SubmissionState.ON_REVIEW.value)
383 self.assertTrue(version.review_open)
385 with patch.object(Submission, "is_draft", False):
386 submission.apply_editorial_decision(decision, self.user_editor)
388 submission = Submission.objects.get(pk=submission.pk)
389 version.refresh_from_db()
390 log_count += 1
391 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
392 self.assertEqual(submission.state, SubmissionState.REVISION_REQUESTED.value)
393 self.assertFalse(version.review_open)
395 # Test that an editorial decision is possible from all submisison state
396 # SUBMITTED
397 submission.state = SubmissionState.SUBMITTED.value
398 submission.save()
399 with patch.object(Submission, "is_draft", False):
400 submission.apply_editorial_decision(decision, self.user_editor)
402 submission = Submission.objects.get(pk=submission.pk)
403 version.refresh_from_db()
404 log_count += 1
405 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
406 self.assertEqual(submission.state, SubmissionState.REVISION_REQUESTED.value)
407 self.assertFalse(version.review_open)
409 # REVISION_REQUESTED
410 submission.state = SubmissionState.REVISION_REQUESTED.value
411 submission.save()
412 decision.value = SubmissionState.ACCEPTED.value
413 decision.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.ACCEPTED.value)
422 self.assertFalse(version.review_open)
424 # REVISION_SUBMITTED
425 submission.state = SubmissionState.REVISION_SUBMITTED.value
426 submission.save()
427 with patch.object(Submission, "is_draft", False):
428 submission.apply_editorial_decision(decision, self.user_editor)
430 submission = Submission.objects.get(pk=submission.pk)
431 version.refresh_from_db()
432 log_count += 1
433 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
434 self.assertEqual(submission.state, SubmissionState.ACCEPTED.value)
435 self.assertFalse(version.review_open)
437 # REJECTED
438 submission.state = SubmissionState.REJECTED.value
439 submission.save()
440 with patch.object(Submission, "is_draft", False):
441 submission.apply_editorial_decision(decision, self.user_editor)
443 submission = Submission.objects.get(pk=submission.pk)
444 version.refresh_from_db()
445 log_count += 1
446 self.assertEqual(len(SubmissionLog.objects.filter(attached_to=submission)), log_count)
447 self.assertEqual(submission.state, SubmissionState.ACCEPTED.value)
448 self.assertFalse(version.review_open)
450 # ACCEPTED
451 submission.state = SubmissionState.ACCEPTED.value
452 submission.save()
453 decision.value = SubmissionState.REJECTED.value
454 decision.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.REJECTED.value)
463 self.assertFalse(version.review_open)