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

1from unittest.mock import patch 

2 

3from django.db.models import Prefetch 

4from django.utils import timezone 

5 

6from mesh.models.exceptions import SubmissionStateError 

7from mesh.models.orm.editorial_models import EditorSubmissionRight 

8 

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 

27 

28 

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] 

35 

36 @classmethod 

37 def setUpClass(cls): 

38 super().setUpClass() 

39 cls.user_author = UserFactory.create() 

40 

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 # ) 

52 

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) 

57 

58 submission.date_first_version = timezone.now() 

59 submission.save() 

60 

61 self.assertNotEqual(submission.date_created, submission.date_first_version) 

62 self.assertEqual(submission.date_submission, submission.date_first_version) 

63 

64 def test_is_draft(self): 

65 submission = SubmissionFactory.create(created_by=self.user_author) 

66 self.assertTrue(submission.is_draft) 

67 

68 submission.state = SubmissionState.SUBMITTED.value 

69 self.assertFalse(submission.is_draft) 

70 

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()) 

80 

81 version_1 = SubmissionVersion(submission=submission, created_by=self.user_author) 

82 version_1.save() 

83 

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) 

89 

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 # ) 

99 

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) 

109 

110 def test_all_authors(self): 

111 submission = SubmissionFactory.create() 

112 authors = submission.authors.all() 

113 self.assertEqual(len(authors), 0) 

114 

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) 

122 

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 # ) 

131 

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) 

138 

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) 

143 

144 

145class SubmissionWorkflowTestCase(BaseTestCase): 

146 used_models = [Submission] 

147 

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) 

156 

157 @classmethod 

158 def tearDownClass(cls): 

159 super().tearDownClass() 

160 User.objects.all().delete() 

161 

162 def setUp(self): 

163 super().setUp() 

164 

165 def test_is_submittable(self): 

166 submission = SubmissionFactory.create(created_by=self.user_author) 

167 self.assertFalse(submission.is_submittable()) 

168 

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()) 

177 

178 # Test each condition separately 

179 submission.state = SubmissionState.SUBMITTED.value 

180 self.assertFalse(submission.is_submittable()) 

181 

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()) 

187 

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()) 

195 

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()) 

203 

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) 

216 

217 # Test submitting non-submittable submission 

218 with patch.object(Submission, "is_submittable", lambda _: False): 

219 self.assertRaises(SubmissionStateError, submission.submit, self.user_author) 

220 

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) 

228 

229 # Test submiting a submittable OPENED submission 

230 with patch.object(Submission, "is_submittable", lambda _: True): 

231 submission.submit(self.user_author) 

232 

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) 

241 

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) 

249 

250 with patch.object(Submission, "is_submittable", lambda _: True): 

251 submission.submit(self.user_author) 

252 

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) 

262 

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()) 

273 

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()) 

279 

280 # Test all submission state 

281 submission.state = SubmissionState.REVISION_SUBMITTED.value 

282 submission.save() 

283 self.assertTrue(submission.is_reviewable()) 

284 

285 submission.state = SubmissionState.OPENED.value 

286 submission.save() 

287 self.assertFalse(submission.is_reviewable()) 

288 

289 submission.state = SubmissionState.ON_REVIEW.value 

290 submission.save() 

291 self.assertFalse(submission.is_reviewable()) 

292 

293 submission.state = SubmissionState.REVISION_REQUESTED.value 

294 submission.save() 

295 self.assertFalse(submission.is_reviewable()) 

296 

297 submission.state = SubmissionState.ACCEPTED.value 

298 submission.save() 

299 self.assertFalse(submission.is_reviewable()) 

300 

301 submission.state = SubmissionState.REJECTED.value 

302 submission.save() 

303 self.assertFalse(submission.is_reviewable()) 

304 

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()) 

316 

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()) 

329 

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) 

338 

339 submission = Submission.objects.get(pk=submission.pk) 

340 

341 self.assertFalse(SubmissionLog.objects.filter(attached_to=submission).exists()) 

342 self.assertEqual(submission.state, SubmissionState.SUBMITTED.value) 

343 

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 ) 

350 

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) 

356 

357 with patch.object(Submission, "is_reviewable", lambda _: True): 

358 submission.start_review_process(self.user_editor) 

359 

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) 

365 

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) 

374 

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) 

380 

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 ) 

393 

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) 

399 

400 with patch.object(Submission, "is_draft", False): 

401 submission.apply_editorial_decision(decision, self.user_editor) 

402 

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) 

409 

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) 

416 

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) 

423 

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) 

431 

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) 

438 

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) 

444 

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) 

451 

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) 

457 

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) 

464 

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) 

472 

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)