| Total Complexity | 44 | 
| Total Lines | 1146 | 
| Duplicated Lines | 10.99 % | 
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like biosample.tests.test_tasks often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | #!/usr/bin/env python3 | ||
| 2 | # -*- coding: utf-8 -*- | ||
| 3 | """ | ||
| 4 | Created on Tue Oct 9 14:51:13 2018 | ||
| 5 | |||
| 6 | @author: Paolo Cozzi <[email protected]> | ||
| 7 | """ | ||
| 8 | |||
| 9 | import redis | ||
| 10 | |||
| 11 | from billiard.einfo import ExceptionInfo | ||
| 12 | from pytest import raises | ||
| 13 | from collections import Counter | ||
| 14 | from unittest.mock import patch, Mock, PropertyMock | ||
| 15 | from datetime import timedelta | ||
| 16 | |||
| 17 | from celery.exceptions import Retry | ||
| 18 | |||
| 19 | from django.test import TestCase | ||
| 20 | from django.conf import settings | ||
| 21 | from django.core import mail | ||
| 22 | from django.utils import timezone | ||
| 23 | |||
| 24 | from common.constants import ( | ||
| 25 | LOADED, ERROR, READY, NEED_REVISION, SUBMITTED, COMPLETED) | ||
| 26 | from image_app.models import Submission, Name | ||
| 27 | |||
| 28 | from ..tasks import SubmitTask, FetchStatusTask | ||
| 29 | from ..models import ManagedTeam | ||
| 30 | |||
| 31 | from .common import SubmitMixin, generate_token | ||
| 32 | |||
| 33 | |||
| 34 | class RedisMixin(): | ||
| 35 | """A class to setup a test token in redis database""" | ||
| 36 | |||
| 37 | # this will be the token key in redis database | ||
| 38 | submission_key = "token:submission:1:test" | ||
| 39 | |||
| 40 | @classmethod | ||
| 41 | def setUpClass(cls): | ||
| 42 | # calling my base class setup | ||
| 43 | super().setUpClass() | ||
| 44 | |||
| 45 | cls.redis = redis.StrictRedis( | ||
| 46 | host=settings.REDIS_HOST, | ||
| 47 | port=settings.REDIS_PORT, | ||
| 48 | db=settings.REDIS_DB) | ||
| 49 | |||
| 50 | # generate a token | ||
| 51 | token = generate_token(domains=['subs.test-team-1']) | ||
| 52 | |||
| 53 | # write token to database | ||
| 54 | cls.redis.set(cls.submission_key, token, ex=3600) | ||
| 55 | |||
| 56 | @classmethod | ||
| 57 | def tearDownClass(cls): | ||
| 58 | if cls.redis.exists(cls.submission_key): | ||
| 59 | cls.redis.delete(cls.submission_key) | ||
| 60 | |||
| 61 | super().tearDownClass() | ||
| 62 | |||
| 63 | |||
| 64 | class SubmitTestCase(SubmitMixin, RedisMixin, TestCase): | ||
| 65 | |||
| 66 | def setUp(self): | ||
| 67 | # call Mixin method | ||
| 68 | super().setUp() | ||
| 69 | |||
| 70 | # setting tasks | ||
| 71 | self.my_task = SubmitTask() | ||
| 72 | |||
| 73 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 74 |     @patch('asyncio.get_event_loop') | ||
| 75 | def test_submit(self, asyncio_mock, send_message_to_websocket_mock): | ||
| 76 | """Test submitting into biosample""" | ||
| 77 | tmp = asyncio_mock.return_value | ||
| 78 | tmp.run_until_complete = Mock() | ||
| 79 | |||
| 80 | # NOTE that I'm calling the function directly, without delay | ||
| 81 | # (AsyncResult). I've patched the time consuming task | ||
| 82 | res = self.my_task.run(submission_id=self.submission_id) | ||
| 83 | |||
| 84 | # assert a success with data uploading | ||
| 85 | self.assertEqual(res, "success") | ||
| 86 | |||
| 87 | # check submission status and message | ||
| 88 | self.submission_obj.refresh_from_db() | ||
| 89 | |||
| 90 | # check submission.state changed | ||
| 91 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 92 | self.assertEqual( | ||
| 93 | self.submission_obj.message, | ||
| 94 | "Waiting for biosample validation") | ||
| 95 | self.assertEqual( | ||
| 96 | self.submission_obj.biosample_submission_id, | ||
| 97 | "new-submission") | ||
| 98 | |||
| 99 | # check name status changed | ||
| 100 | qs = Name.objects.filter(status=SUBMITTED) | ||
| 101 | self.assertEqual(len(qs), self.n_to_submit) | ||
| 102 | |||
| 103 | # assert called mock objects | ||
| 104 | self.assertTrue(self.mock_root.called) | ||
| 105 | self.assertTrue(self.my_root.get_team_by_name.called) | ||
| 106 | self.assertTrue(self.my_team.create_submission.called) | ||
| 107 | self.assertFalse(self.my_root.get_submission_by_name.called) | ||
| 108 | self.assertEqual( | ||
| 109 | self.new_submission.create_sample.call_count, self.n_to_submit) | ||
| 110 | self.assertFalse(self.new_submission.propertymock.called) | ||
| 111 | |||
| 112 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 113 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 114 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 115 | send_message_to_websocket_mock.assert_called_with( | ||
| 116 |             { | ||
| 117 | 'message': 'Submitted', | ||
| 118 | 'notification_message': 'Waiting for biosample validation'}, 1) | ||
| 119 | |||
| 120 | # http://docs.celeryproject.org/en/latest/userguide/testing.html#tasks-and-unit-tests | ||
| 121 |     @patch("biosample.tasks.SubmitTask.retry") | ||
| 122 |     @patch("biosample.tasks.SubmitTask.submit_biosample") | ||
| 123 | def test_submit_retry(self, my_submit, my_retry): | ||
| 124 | """Test submissions with retry""" | ||
| 125 | |||
| 126 | # Set a side effect on the patched methods | ||
| 127 | # so that they raise the errors we want. | ||
| 128 | my_retry.side_effect = Retry() | ||
| 129 | my_submit.side_effect = Exception() | ||
| 130 | |||
| 131 | with raises(Retry): | ||
| 132 | self.my_task.run(submission_id=1) | ||
| 133 | |||
| 134 | self.assertTrue(my_submit.called) | ||
| 135 | self.assertTrue(my_retry.called) | ||
| 136 | |||
| 137 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 138 |     @patch('asyncio.get_event_loop') | ||
| 139 |     @patch("biosample.tasks.SubmitTask.retry") | ||
| 140 |     @patch("biosample.tasks.SubmitTask.submit_biosample") | ||
| 141 | def test_issues_with_api(self, my_submit, my_retry, asyncio_mock, | ||
| 142 | send_message_to_websocket_mock): | ||
| 143 | """Test errors with submission API""" | ||
| 144 | tmp = asyncio_mock.return_value | ||
| 145 | tmp.run_until_complete = Mock() | ||
| 146 | |||
| 147 | # Set a side effect on the patched methods | ||
| 148 | # so that they raise the errors we want. | ||
| 149 | my_retry.side_effect = Retry() | ||
| 150 | my_submit.side_effect = ConnectionError() | ||
| 151 | |||
| 152 | # call task. No retries with issues at EBI | ||
| 153 | res = self.my_task.run(submission_id=1) | ||
| 154 | |||
| 155 | # assert a success with validation taks | ||
| 156 | self.assertEqual(res, "success") | ||
| 157 | |||
| 158 | # check submission status and message | ||
| 159 | self.submission_obj.refresh_from_db() | ||
| 160 | |||
| 161 | # this is the message I want | ||
| 162 | message = "Errors in EBI API endpoints. Please try again later" | ||
| 163 | |||
| 164 | # check submission.status NOT changed | ||
| 165 | self.assertEqual(self.submission_obj.status, READY) | ||
| 166 | self.assertIn( | ||
| 167 | message, | ||
| 168 | self.submission_obj.message) | ||
| 169 | |||
| 170 | # test email sent | ||
| 171 | self.assertEqual(len(mail.outbox), 1) | ||
| 172 | |||
| 173 | # read email | ||
| 174 | email = mail.outbox[0] | ||
| 175 | |||
| 176 | self.assertEqual( | ||
| 177 | "Error in biosample submission 1", | ||
| 178 | email.subject) | ||
| 179 | |||
| 180 | self.assertTrue(my_submit.called) | ||
| 181 | self.assertFalse(my_retry.called) | ||
| 182 | |||
| 183 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 184 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 185 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 186 | send_message_to_websocket_mock.assert_called_with( | ||
| 187 |             { | ||
| 188 | 'message': 'Ready', | ||
| 189 | 'notification_message': 'Errors in EBI API endpoints. ' | ||
| 190 | 'Please try again later'}, 1) | ||
| 191 | |||
| 192 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 193 |     @patch('asyncio.get_event_loop') | ||
| 194 | def test_submit_recover( | ||
| 195 | self, asyncio_mock, send_message_to_websocket_mock): | ||
| 196 | """Test submission recovering""" | ||
| 197 | tmp = asyncio_mock.return_value | ||
| 198 | tmp.run_until_complete = Mock() | ||
| 199 | |||
| 200 | # update submission object | ||
| 201 | self.submission_obj.biosample_submission_id = "test-submission" | ||
| 202 | self.submission_obj.save() | ||
| 203 | |||
| 204 | # set one name as uploaded | ||
| 205 | name = Name.objects.get(name='ANIMAL:::ID:::132713') | ||
| 206 | name.status = SUBMITTED | ||
| 207 | name.save() | ||
| 208 | |||
| 209 | # calling submit | ||
| 210 | res = self.my_task.run(submission_id=self.submission_id) | ||
| 211 | |||
| 212 | # assert a success with data uploading | ||
| 213 | self.assertEqual(res, "success") | ||
| 214 | |||
| 215 | # reload submission | ||
| 216 | self.submission_obj.refresh_from_db() | ||
| 217 | |||
| 218 | # check submission.state changed | ||
| 219 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 220 | self.assertEqual( | ||
| 221 | self.submission_obj.message, | ||
| 222 | "Waiting for biosample validation") | ||
| 223 | |||
| 224 | # check name status changed | ||
| 225 | qs = Name.objects.filter(status=SUBMITTED) | ||
| 226 | self.assertEqual(len(qs), self.n_to_submit) | ||
| 227 | |||
| 228 | # assert called mock objects | ||
| 229 | self.assertTrue(self.mock_root.called) | ||
| 230 | self.assertFalse(self.my_root.get_team_by_name.called) | ||
| 231 | self.assertFalse(self.my_team.create_submission.called) | ||
| 232 | self.assertTrue(self.my_root.get_submission_by_name.called) | ||
| 233 | # I'm testing submission recover with 1 sample already sent, so: | ||
| 234 | self.assertEqual( | ||
| 235 | self.my_submission.create_sample.call_count, self.n_to_submit-1) | ||
| 236 | self.assertTrue(self.my_submission.propertymock.called) | ||
| 237 | |||
| 238 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 239 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 240 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 241 | send_message_to_websocket_mock.assert_called_with( | ||
| 242 |             {'message': 'Submitted', | ||
| 243 | 'notification_message': 'Waiting for biosample validation'}, 1) | ||
| 244 | |||
| 245 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 246 |     @patch('asyncio.get_event_loop') | ||
| 247 | def test_submit_no_recover(self, asyncio_mock, | ||
| 248 | send_message_to_websocket_mock): | ||
| 249 | """Test submission recovering with a closed submission""" | ||
| 250 | tmp = asyncio_mock.return_value | ||
| 251 | tmp.run_until_complete = Mock() | ||
| 252 | |||
| 253 | # update submission status | ||
| 254 | self.my_submission.propertymock = PropertyMock( | ||
| 255 | return_value='Completed') | ||
| 256 | type(self.my_submission).status = self.my_submission.propertymock | ||
| 257 | |||
| 258 | # update submission object | ||
| 259 | self.submission_obj.biosample_submission_id = "test-submission" | ||
| 260 | self.submission_obj.save() | ||
| 261 | |||
| 262 | # calling submit | ||
| 263 | res = self.my_task.run(submission_id=self.submission_id) | ||
| 264 | |||
| 265 | # assert a success with data uploading | ||
| 266 | self.assertEqual(res, "success") | ||
| 267 | |||
| 268 | # reload submission | ||
| 269 | self.submission_obj.refresh_from_db() | ||
| 270 | |||
| 271 | # check submission.state changed | ||
| 272 | self.assertEqual( | ||
| 273 | self.submission_obj.biosample_submission_id, | ||
| 274 | "new-submission") | ||
| 275 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 276 | self.assertEqual( | ||
| 277 | self.submission_obj.message, | ||
| 278 | "Waiting for biosample validation") | ||
| 279 | |||
| 280 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 281 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 282 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 283 | send_message_to_websocket_mock.assert_called_with( | ||
| 284 |             {'message': 'Submitted', | ||
| 285 | 'notification_message': 'Waiting for biosample validation'}, 1) | ||
| 286 | |||
| 287 | # check name status changed | ||
| 288 | qs = Name.objects.filter(status=SUBMITTED) | ||
| 289 | self.assertEqual(len(qs), self.n_to_submit) | ||
| 290 | |||
| 291 | # assert called mock objects | ||
| 292 | self.assertTrue(self.mock_root.called) | ||
| 293 | self.assertTrue(self.my_root.get_team_by_name.called) | ||
| 294 | self.assertTrue(self.my_root.get_submission_by_name.called) | ||
| 295 | self.assertTrue(self.my_team.create_submission.called) | ||
| 296 | self.assertFalse(self.my_submission.create_sample.called) | ||
| 297 | self.assertEqual( | ||
| 298 | self.new_submission.create_sample.call_count, self.n_to_submit) | ||
| 299 | self.assertTrue(self.my_submission.propertymock.called) | ||
| 300 | self.assertFalse(self.new_submission.propertymock.called) | ||
| 301 | |||
| 302 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 303 |     @patch('asyncio.get_event_loop') | ||
| 304 | def test_submit_patch(self, asyncio_mock, send_message_to_websocket_mock): | ||
| 305 | """Test patching submission""" | ||
| 306 | tmp = asyncio_mock.return_value | ||
| 307 | tmp.run_until_complete = Mock() | ||
| 308 | |||
| 309 | # creating mock samples | ||
| 310 | my_samples = [ | ||
| 311 |             Mock(**{'alias': 'IMAGEA000000001', | ||
| 312 | 'title': 'a 4-year old pig organic fed'}), | ||
| 313 |             Mock(**{'alias': 'IMAGES000000001', | ||
| 314 | 'title': 'semen collected when the animal turns to 4'}), | ||
| 315 | ] | ||
| 316 | |||
| 317 | # mocking set samples | ||
| 318 | self.my_submission.get_samples.return_value = my_samples | ||
| 319 | |||
| 320 | # update submission object | ||
| 321 | self.submission_obj.biosample_submission_id = "test-submission" | ||
| 322 | self.submission_obj.save() | ||
| 323 | |||
| 324 | # calling submit | ||
| 325 | res = self.my_task.run(submission_id=self.submission_id) | ||
| 326 | |||
| 327 | # assert a success with data uploading | ||
| 328 | self.assertEqual(res, "success") | ||
| 329 | |||
| 330 | # reload submission | ||
| 331 | self.submission_obj.refresh_from_db() | ||
| 332 | |||
| 333 | # check submission.state changed | ||
| 334 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 335 | self.assertEqual( | ||
| 336 | self.submission_obj.message, | ||
| 337 | "Waiting for biosample validation") | ||
| 338 | |||
| 339 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 340 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 341 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 342 | send_message_to_websocket_mock.assert_called_with( | ||
| 343 |             {'message': 'Submitted', | ||
| 344 | 'notification_message': 'Waiting for biosample validation'}, 1) | ||
| 345 | |||
| 346 | # check name status changed | ||
| 347 | qs = Name.objects.filter(status=SUBMITTED) | ||
| 348 | self.assertEqual(len(qs), self.n_to_submit) | ||
| 349 | |||
| 350 | # assert called mock objects | ||
| 351 | self.assertTrue(self.mock_root.called) | ||
| 352 | self.assertFalse(self.my_root.get_team_by_name.called) | ||
| 353 | self.assertFalse(self.my_team.create_submission.called) | ||
| 354 | self.assertTrue(self.my_root.get_submission_by_name.called) | ||
| 355 | # I've patched 2 samples in this test, so: | ||
| 356 | self.assertEqual( | ||
| 357 | self.my_submission.create_sample.call_count, 2) | ||
| 358 | self.assertTrue(self.my_submission.propertymock.called) | ||
| 359 | |||
| 360 | # testing patch | ||
| 361 | for sample in my_samples: | ||
| 362 | self.assertTrue(sample.patch.called) | ||
| 363 | |||
| 364 | View Code Duplication |     @patch('biosample.tasks.send_message_to_websocket') | |
|  | |||
| 365 |     @patch('asyncio.get_event_loop') | ||
| 366 | def test_on_failure(self, asyncio_mock, send_message_to_websocket_mock): | ||
| 367 | """Testing on failure methods""" | ||
| 368 | tmp = asyncio_mock.return_value | ||
| 369 | tmp.run_until_complete = Mock() | ||
| 370 | |||
| 371 |         exc = Exception("Test") | ||
| 372 | task_id = "test_task_id" | ||
| 373 | args = [self.submission_id] | ||
| 374 |         kwargs = {} | ||
| 375 | einfo = ExceptionInfo | ||
| 376 | |||
| 377 | # call on_failure method | ||
| 378 | self.my_task.on_failure(exc, task_id, args, kwargs, einfo) | ||
| 379 | |||
| 380 | # check submission status and message | ||
| 381 | submission = Submission.objects.get(pk=self.submission_id) | ||
| 382 | |||
| 383 | # check submission.state changed | ||
| 384 | self.assertEqual(submission.status, ERROR) | ||
| 385 | self.assertEqual( | ||
| 386 | submission.message, | ||
| 387 | "Error in biosample submission: Test") | ||
| 388 | |||
| 389 | # test email sent | ||
| 390 | self.assertEqual(len(mail.outbox), 1) | ||
| 391 | |||
| 392 | # read email | ||
| 393 | email = mail.outbox[0] | ||
| 394 | |||
| 395 | self.assertEqual( | ||
| 396 | "Error in biosample submission %s" % self.submission_id, | ||
| 397 | email.subject) | ||
| 398 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 399 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 400 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 401 | send_message_to_websocket_mock.assert_called_with( | ||
| 402 |             {'message': 'Error', | ||
| 403 | 'notification_message': 'Error in biosample submission: Test'}, 1) | ||
| 404 | |||
| 405 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 406 |     @patch('asyncio.get_event_loop') | ||
| 407 | def test_token_expired(self, asyncio_mock, send_message_to_websocket_mock): | ||
| 408 | """Testing token expiring during a submission""" | ||
| 409 | tmp = asyncio_mock.return_value | ||
| 410 | tmp.run_until_complete = Mock() | ||
| 411 | |||
| 412 | # simulating a token expiring during a submission | ||
| 413 | self.new_submission.create_sample.side_effect = RuntimeError( | ||
| 414 | "Your token is expired") | ||
| 415 | |||
| 416 | # calling task | ||
| 417 | res = self.my_task.run(submission_id=self.submission_id) | ||
| 418 | |||
| 419 | # assert a success with data uploading | ||
| 420 | self.assertEqual(res, "success") | ||
| 421 | |||
| 422 | # check submission status and message | ||
| 423 | self.submission_obj.refresh_from_db() | ||
| 424 | |||
| 425 | # check submission.state return to ready (if it was valid before, | ||
| 426 | # should be valid again, if rules are the same) | ||
| 427 | self.assertEqual(self.submission_obj.status, READY) | ||
| 428 | self.assertEqual( | ||
| 429 | self.submission_obj.message, | ||
| 430 | "Your token is expired: please submit again to resume submission") | ||
| 431 | self.assertEqual( | ||
| 432 | self.submission_obj.biosample_submission_id, | ||
| 433 | "new-submission") | ||
| 434 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 435 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 436 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 437 | send_message_to_websocket_mock.assert_called_with( | ||
| 438 |             {'message': 'Ready', | ||
| 439 | 'notification_message': 'Your token is expired: please submit ' | ||
| 440 | 'again to resume submission'}, 1) | ||
| 441 | |||
| 442 | # check name status unchanged (counts are equal to setUp name queryset) | ||
| 443 | qs = Name.objects.filter(status=READY) | ||
| 444 | self.assertEqual(len(qs), self.name_qs.count()) | ||
| 445 | |||
| 446 | # test email sent | ||
| 447 | self.assertEqual(len(mail.outbox), 1) | ||
| 448 | |||
| 449 | # read email | ||
| 450 | email = mail.outbox[0] | ||
| 451 | |||
| 452 | self.assertEqual( | ||
| 453 | "Error in biosample submission 1", | ||
| 454 | email.subject) | ||
| 455 | |||
| 456 | # assert called mock objects | ||
| 457 | self.assertTrue(self.mock_root.called) | ||
| 458 | self.assertTrue(self.my_root.get_team_by_name.called) | ||
| 459 | self.assertTrue(self.my_team.create_submission.called) | ||
| 460 | self.assertFalse(self.my_root.get_submission_by_name.called) | ||
| 461 | |||
| 462 | # is called once. With the first call, I got an exception and I | ||
| 463 | # dont't do the second request | ||
| 464 | self.assertEqual(self.new_submission.create_sample.call_count, 1) | ||
| 465 | self.assertFalse(self.new_submission.propertymock.called) | ||
| 466 | |||
| 467 | |||
| 468 | class UpdateSubmissionTestCase(SubmitMixin, RedisMixin, TestCase): | ||
| 469 | """Test a submission for an already completed submission which | ||
| 470 | receives one update, is valid and need to be updated in biosample""" | ||
| 471 | |||
| 472 | def setUp(self): | ||
| 473 | # call Mixin method | ||
| 474 | super().setUp() | ||
| 475 | |||
| 476 | # get all name objects and set status to completed | ||
| 477 | self.name_qs.update(status=COMPLETED) | ||
| 478 | |||
| 479 | # Now get first animal and set status to ready. Take also its sample | ||
| 480 | # and assign a fake biosample id | ||
| 481 | self.animal_name = Name.objects.get(pk=3) | ||
| 482 | self.sample_name = Name.objects.get(pk=4) | ||
| 483 | |||
| 484 | # update name objects. In this case, animal was modified | ||
| 485 | self.animal_name.status = READY | ||
| 486 | self.animal_name.save() | ||
| 487 | |||
| 488 | # sample is supposed to be submitted with success | ||
| 489 | self.sample_name.status = COMPLETED | ||
| 490 | self.sample_name.biosample_id = "FAKES123456" | ||
| 491 | self.sample_name.save() | ||
| 492 | |||
| 493 | # setting tasks | ||
| 494 | self.my_task = SubmitTask() | ||
| 495 | |||
| 496 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 497 |     @patch('asyncio.get_event_loop') | ||
| 498 | def test_submit(self, asyncio_mock, send_message_to_websocket_mock): | ||
| 499 | """Test submitting into biosample""" | ||
| 500 | tmp = asyncio_mock.return_value | ||
| 501 | tmp.run_until_complete = Mock() | ||
| 502 | |||
| 503 | res = self.my_task.run(submission_id=self.submission_id) | ||
| 504 | |||
| 505 | # assert a success with data uploading | ||
| 506 | self.assertEqual(res, "success") | ||
| 507 | |||
| 508 | # check submission status and message | ||
| 509 | self.submission_obj.refresh_from_db() | ||
| 510 | |||
| 511 | # check submission.state changed | ||
| 512 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 513 | self.assertEqual( | ||
| 514 | self.submission_obj.message, | ||
| 515 | "Waiting for biosample validation") | ||
| 516 | self.assertEqual( | ||
| 517 | self.submission_obj.biosample_submission_id, | ||
| 518 | "new-submission") | ||
| 519 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 520 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 521 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 522 | send_message_to_websocket_mock.assert_called_with( | ||
| 523 |             {'message': 'Submitted', | ||
| 524 | 'notification_message': 'Waiting for biosample validation'}, 1) | ||
| 525 | |||
| 526 | # check name status changed for animal | ||
| 527 | self.animal_name.refresh_from_db() | ||
| 528 | self.assertEqual(self.animal_name.status, SUBMITTED) | ||
| 529 | |||
| 530 | # check that sample status is unchanged | ||
| 531 | self.sample_name.refresh_from_db() | ||
| 532 | self.assertEqual(self.sample_name.status, COMPLETED) | ||
| 533 | |||
| 534 | # assert that all statuses (except one) remain unchanged | ||
| 535 | qs = Name.objects.filter(status=COMPLETED) | ||
| 536 | self.assertEqual(qs.count(), self.name_qs.count()-1) | ||
| 537 | |||
| 538 | # assert called mock objects | ||
| 539 | self.assertTrue(self.mock_root.called) | ||
| 540 | self.assertTrue(self.my_root.get_team_by_name.called) | ||
| 541 | self.assertTrue(self.my_team.create_submission.called) | ||
| 542 | self.assertFalse(self.my_root.get_submission_by_name.called) | ||
| 543 | self.assertEqual(self.new_submission.create_sample.call_count, 1) | ||
| 544 | self.assertFalse(self.new_submission.propertymock.called) | ||
| 545 | |||
| 546 | |||
| 547 | class FetchMixin(): | ||
| 548 | """Mixin for fetching status""" | ||
| 549 | |||
| 550 | fixtures = [ | ||
| 551 | 'biosample/account', | ||
| 552 | 'biosample/managedteam', | ||
| 553 | 'image_app/dictcountry', | ||
| 554 | 'image_app/dictrole', | ||
| 555 | 'image_app/organization', | ||
| 556 | 'image_app/submission', | ||
| 557 | 'image_app/user' | ||
| 558 | ] | ||
| 559 | |||
| 560 | @classmethod | ||
| 561 | def setUpClass(cls): | ||
| 562 | # calling my base class setup | ||
| 563 | super().setUpClass() | ||
| 564 | |||
| 565 | unmanaged = ManagedTeam.objects.get(pk=2) | ||
| 566 | unmanaged.delete() | ||
| 567 | |||
| 568 | # starting mocked objects | ||
| 569 |         cls.mock_root_patcher = patch('pyUSIrest.client.Root') | ||
| 570 | cls.mock_root = cls.mock_root_patcher.start() | ||
| 571 | |||
| 572 |         cls.mock_auth_patcher = patch('biosample.helpers.Auth') | ||
| 573 | cls.mock_auth = cls.mock_auth_patcher.start() | ||
| 574 | |||
| 575 | @classmethod | ||
| 576 | def tearDownClass(cls): | ||
| 577 | cls.mock_root_patcher.stop() | ||
| 578 | cls.mock_auth_patcher.stop() | ||
| 579 | |||
| 580 | # calling base method | ||
| 581 | super().tearDownClass() | ||
| 582 | |||
| 583 | def setUp(self): | ||
| 584 | # calling my base setup | ||
| 585 | super().setUp() | ||
| 586 | |||
| 587 | # get a submission object | ||
| 588 | self.submission_obj = Submission.objects.get(pk=1) | ||
| 589 | |||
| 590 | # set a status which I can fetch_status | ||
| 591 | self.submission_obj.status = SUBMITTED | ||
| 592 | self.submission_obj.biosample_submission_id = "test-fetch" | ||
| 593 | self.submission_obj.save() | ||
| 594 | |||
| 595 | # set status for names, like submittask does. Only sample not unknown | ||
| 596 | # are submitted | ||
| 597 | self.name_qs = Name.objects.exclude(name__contains="unknown") | ||
| 598 | self.name_qs.update(status=SUBMITTED) | ||
| 599 | |||
| 600 | # count number of names in UID for such submission (exclude | ||
| 601 | # unknown animals) | ||
| 602 | self.n_to_submit = self.name_qs.count() | ||
| 603 | |||
| 604 | # track submission ID | ||
| 605 | self.submission_obj_id = self.submission_obj.id | ||
| 606 | |||
| 607 | # start root object | ||
| 608 | self.my_root = self.mock_root.return_value | ||
| 609 | |||
| 610 | # define my task | ||
| 611 | self.my_task = FetchStatusTask() | ||
| 612 | |||
| 613 | # change lock_id (useful when running test during cron) | ||
| 614 | self.my_task.lock_id = "test-FetchStatusTask" | ||
| 615 | |||
| 616 | def common_tests(self, my_submission): | ||
| 617 | # passing submission to Mocked Root | ||
| 618 | self.my_root.get_submission_by_name.return_value = my_submission | ||
| 619 | |||
| 620 | # NOTE that I'm calling the function directly, without delay | ||
| 621 | # (AsyncResult). I've patched the time consuming task | ||
| 622 | res = self.my_task.run() | ||
| 623 | |||
| 624 | # assert a success with data uploading | ||
| 625 | self.assertEqual(res, "success") | ||
| 626 | |||
| 627 | self.assertTrue(self.mock_auth.called) | ||
| 628 | self.assertTrue(self.mock_root.called) | ||
| 629 | self.assertTrue(self.my_root.get_submission_by_name.called) | ||
| 630 | |||
| 631 | |||
| 632 | class FetchCompletedTestCase(FetchMixin, TestCase): | ||
| 633 | """a completed submission with two samples""" | ||
| 634 | |||
| 635 | fixtures = [ | ||
| 636 | 'biosample/account', | ||
| 637 | 'biosample/managedteam', | ||
| 638 | 'image_app/animal', | ||
| 639 | 'image_app/dictbreed', | ||
| 640 | 'image_app/dictcountry', | ||
| 641 | 'image_app/dictrole', | ||
| 642 | 'image_app/dictsex', | ||
| 643 | 'image_app/dictspecie', | ||
| 644 | 'image_app/dictstage', | ||
| 645 | 'image_app/dictuberon', | ||
| 646 | 'image_app/name', | ||
| 647 | 'image_app/organization', | ||
| 648 | 'image_app/publication', | ||
| 649 | 'image_app/sample', | ||
| 650 | 'image_app/submission', | ||
| 651 | 'image_app/user' | ||
| 652 | ] | ||
| 653 | |||
| 654 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 655 |     @patch('asyncio.get_event_loop') | ||
| 656 | def test_fetch_status(self, asyncio_mock, send_message_to_websocket_mock): | ||
| 657 | # a completed submission with two samples | ||
| 658 | tmp = asyncio_mock.return_value | ||
| 659 | tmp.run_until_complete = Mock() | ||
| 660 | my_submission = Mock() | ||
| 661 | my_submission.name = "test-fetch" | ||
| 662 | my_submission.status = 'Completed' | ||
| 663 | |||
| 664 | # Add samples | ||
| 665 | my_sample1 = Mock() | ||
| 666 | my_sample1.name = "test-animal" | ||
| 667 | my_sample1.alias = "IMAGEA000000001" | ||
| 668 | my_sample1.accession = "SAMEA0000001" | ||
| 669 | my_sample2 = Mock() | ||
| 670 | my_sample2.name = "test-sample" | ||
| 671 | my_sample2.alias = "IMAGES000000001" | ||
| 672 | my_sample2.accession = "SAMEA0000002" | ||
| 673 | my_submission.get_samples.return_value = [my_sample1, my_sample2] | ||
| 674 | |||
| 675 | # assert task and mock methods called | ||
| 676 | self.common_tests(my_submission) | ||
| 677 | |||
| 678 | # assert status for submissions | ||
| 679 | self.submission_obj.refresh_from_db() | ||
| 680 | self.assertEqual(self.submission_obj.status, COMPLETED) | ||
| 681 | |||
| 682 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 683 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 684 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 685 | send_message_to_websocket_mock.assert_called_with( | ||
| 686 |             {'message': 'Completed', | ||
| 687 | 'notification_message': 'Successful submission into biosample'}, | ||
| 688 | 1) | ||
| 689 | |||
| 690 | # check name status changed | ||
| 691 | qs = Name.objects.filter(status=COMPLETED) | ||
| 692 | self.assertEqual(len(qs), 2) | ||
| 693 | |||
| 694 | # fetch two name objects | ||
| 695 | name = Name.objects.get(name='ANIMAL:::ID:::132713') | ||
| 696 | self.assertEqual(name.biosample_id, "SAMEA0000001") | ||
| 697 | |||
| 698 | name = Name.objects.get(name='Siems_0722_393449') | ||
| 699 | self.assertEqual(name.biosample_id, "SAMEA0000002") | ||
| 700 | |||
| 701 | def test_fetch_status_no_accession(self): | ||
| 702 | # a completed submission with two samples | ||
| 703 | my_submission = Mock() | ||
| 704 | my_submission.name = "test-fetch" | ||
| 705 | my_submission.status = 'Submitted' | ||
| 706 | |||
| 707 | # Add samples | ||
| 708 | my_sample1 = Mock() | ||
| 709 | my_sample1.name = "test-animal" | ||
| 710 | my_sample1.alias = "IMAGEA000000001" | ||
| 711 | my_sample1.accession = None | ||
| 712 | my_sample2 = Mock() | ||
| 713 | my_sample2.name = "test-sample" | ||
| 714 | my_sample2.alias = "IMAGES000000001" | ||
| 715 | my_sample2.accession = None | ||
| 716 | my_submission.get_samples.return_value = [my_sample1, my_sample2] | ||
| 717 | |||
| 718 | # assert task and mock methods called | ||
| 719 | self.common_tests(my_submission) | ||
| 720 | |||
| 721 | # assert status for submissions | ||
| 722 | self.submission_obj.refresh_from_db() | ||
| 723 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 724 | |||
| 725 | # check name status changed | ||
| 726 | qs = Name.objects.filter(status=SUBMITTED) | ||
| 727 | self.assertEqual(len(qs), self.n_to_submit) | ||
| 728 | |||
| 729 | # http://docs.celeryproject.org/en/latest/userguide/testing.html#tasks-and-unit-tests | ||
| 730 |     @patch("biosample.tasks.FetchStatusTask.retry") | ||
| 731 |     @patch("biosample.tasks.FetchStatusTask.fetch_queryset") | ||
| 732 | def test_fetch_status_retry(self, my_fetch, my_retry): | ||
| 733 | """Test fetch status with retry""" | ||
| 734 | |||
| 735 | # Set a side effect on the patched methods | ||
| 736 | # so that they raise the errors we want. | ||
| 737 | my_retry.side_effect = Retry() | ||
| 738 | my_fetch.side_effect = ConnectionError() | ||
| 739 | |||
| 740 | with raises(Retry): | ||
| 741 | self.my_task.run() | ||
| 742 | |||
| 743 | self.assertTrue(my_fetch.called) | ||
| 744 | self.assertTrue(my_retry.called) | ||
| 745 | |||
| 746 | # Test a non blocking instance | ||
| 747 |     @patch("biosample.tasks.FetchStatusTask.fetch_queryset") | ||
| 748 |     @patch("redis.lock.Lock.acquire", return_value=False) | ||
| 749 | def test_fetch_status_nb(self, my_lock, my_fetch): | ||
| 750 | """Test FetchSTatus while a lock is present""" | ||
| 751 | |||
| 752 | res = self.my_task.run() | ||
| 753 | |||
| 754 | # assert database is locked | ||
| 755 | self.assertEqual(res, "%s already running!" % (self.my_task.name)) | ||
| 756 | self.assertFalse(my_fetch.called) | ||
| 757 | |||
| 758 | |||
| 759 | class FetchNotInDBTestCase(FetchMixin, TestCase): | ||
| 760 | """A submission not in db""" | ||
| 761 | |||
| 762 | def test_fetch_status(self): | ||
| 763 | # mocking submissions. A submission not in db | ||
| 764 | my_submission = Mock() | ||
| 765 | my_submission.name = "not-present-in-db" | ||
| 766 | |||
| 767 | # assert task and mock methods called | ||
| 768 | self.common_tests(my_submission) | ||
| 769 | |||
| 770 | |||
| 771 | class FetchWithErrorsTestCase(FetchMixin, TestCase): | ||
| 772 | """Test a submission with errors for biosample""" | ||
| 773 | |||
| 774 | fixtures = [ | ||
| 775 | 'biosample/account', | ||
| 776 | 'biosample/managedteam', | ||
| 777 | 'image_app/animal', | ||
| 778 | 'image_app/dictbreed', | ||
| 779 | 'image_app/dictcountry', | ||
| 780 | 'image_app/dictrole', | ||
| 781 | 'image_app/dictsex', | ||
| 782 | 'image_app/dictspecie', | ||
| 783 | 'image_app/dictstage', | ||
| 784 | 'image_app/dictuberon', | ||
| 785 | 'image_app/name', | ||
| 786 | 'image_app/organization', | ||
| 787 | 'image_app/publication', | ||
| 788 | 'image_app/sample', | ||
| 789 | 'image_app/submission', | ||
| 790 | 'image_app/user' | ||
| 791 | ] | ||
| 792 | |||
| 793 | def setUp(self): | ||
| 794 | # calling my base setup | ||
| 795 | super().setUp() | ||
| 796 | |||
| 797 | # a draft submission with errors | ||
| 798 | my_submission = Mock() | ||
| 799 | my_submission.name = "test-fetch" | ||
| 800 | my_submission.status = 'Draft' | ||
| 801 |         my_submission.has_errors.return_value = Counter({True: 1, False: 1}) | ||
| 802 |         my_submission.get_status.return_value = Counter({'Complete': 2}) | ||
| 803 | |||
| 804 | # Add samples. Suppose that first failed, second is ok | ||
| 805 | my_validation_result1 = Mock() | ||
| 806 |         my_validation_result1.errorMessages = { | ||
| 807 | 'Ena': [ | ||
| 808 | 'a sample message', | ||
| 809 | ] | ||
| 810 | } | ||
| 811 | |||
| 812 | my_sample1 = Mock() | ||
| 813 | my_sample1.name = "test-animal" | ||
| 814 | my_sample1.alias = "IMAGEA000000001" | ||
| 815 | my_sample1.has_errors.return_value = True | ||
| 816 | my_sample1.get_validation_result.return_value = my_validation_result1 | ||
| 817 | |||
| 818 | # sample2 is ok | ||
| 819 | my_validation_result2 = Mock() | ||
| 820 | my_validation_result2.errorMessages = None | ||
| 821 | |||
| 822 | my_sample2 = Mock() | ||
| 823 | my_sample2.name = "test-sample" | ||
| 824 | my_sample2.alias = "IMAGES000000001" | ||
| 825 | my_sample2.has_errors.return_value = False | ||
| 826 | my_sample2.get_validation_result.return_value = my_validation_result2 | ||
| 827 | |||
| 828 | # simulate that IMAGEA000000001 has errors | ||
| 829 | my_submission.get_samples.return_value = [my_sample1, my_sample2] | ||
| 830 | |||
| 831 | # track objects | ||
| 832 | self.my_submission = my_submission | ||
| 833 | self.my_validation_result1 = my_validation_result1 | ||
| 834 | self.my_validation_result2 = my_validation_result2 | ||
| 835 | self.my_sample1 = my_sample1 | ||
| 836 | self.my_sample2 = my_sample2 | ||
| 837 | |||
| 838 | # track names | ||
| 839 | self.animal_name = Name.objects.get(pk=3) | ||
| 840 | self.sample_name = Name.objects.get(pk=4) | ||
| 841 | |||
| 842 | def common_tests(self): | ||
| 843 | # assert task and mock methods called | ||
| 844 | super().common_tests(self.my_submission) | ||
| 845 | |||
| 846 | # assert custom mock attributes called | ||
| 847 | self.assertTrue(self.my_sample1.has_errors.called) | ||
| 848 | self.assertTrue(self.my_sample1.get_validation_result.called) | ||
| 849 | |||
| 850 | # if sample has no errors, no all methods will be called | ||
| 851 | self.assertTrue(self.my_sample2.has_errors.called) | ||
| 852 | self.assertFalse(self.my_sample2.get_validation_result.called) | ||
| 853 | |||
| 854 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 855 |     @patch('asyncio.get_event_loop') | ||
| 856 | def test_fetch_status(self, asyncio_mock, send_message_to_websocket_mock): | ||
| 857 | tmp = asyncio_mock.return_value | ||
| 858 | tmp.run_until_complete = Mock() | ||
| 859 | # assert task and mock methods called | ||
| 860 | self.common_tests() | ||
| 861 | |||
| 862 | # assert submission status | ||
| 863 | self.submission_obj.refresh_from_db() | ||
| 864 | self.assertEqual(self.submission_obj.status, NEED_REVISION) | ||
| 865 | |||
| 866 | # check name status changed only for animal (not sample) | ||
| 867 | self.animal_name.refresh_from_db() | ||
| 868 | self.assertEqual(self.animal_name.status, NEED_REVISION) | ||
| 869 | |||
| 870 | self.sample_name.refresh_from_db() | ||
| 871 | self.assertEqual(self.sample_name.status, SUBMITTED) | ||
| 872 | |||
| 873 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 874 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 875 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 876 | send_message_to_websocket_mock.assert_called_with( | ||
| 877 |             {'message': 'Need Revision', | ||
| 878 | 'notification_message': 'Error in biosample submission'}, 1) | ||
| 879 | |||
| 880 |     @patch('biosample.tasks.send_message_to_websocket') | ||
| 881 |     @patch('asyncio.get_event_loop') | ||
| 882 | def test_email_sent(self, asyncio_mock, send_message_to_websocket_mock): | ||
| 883 | tmp = asyncio_mock.return_value | ||
| 884 | tmp.run_until_complete = Mock() | ||
| 885 | # assert task and mock methods called | ||
| 886 | self.common_tests() | ||
| 887 | |||
| 888 | # test email sent | ||
| 889 | self.assertEqual(len(mail.outbox), 1) | ||
| 890 | |||
| 891 | # read email | ||
| 892 | email = mail.outbox[0] | ||
| 893 | |||
| 894 | self.assertEqual( | ||
| 895 | "Error in biosample submission %s" % self.submission_obj_id, | ||
| 896 | email.subject) | ||
| 897 | |||
| 898 | # check for error messages in object | ||
| 899 |         self.assertIn("a sample message", email.body) | ||
| 900 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 901 | |||
| 902 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 903 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 904 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 905 | send_message_to_websocket_mock.assert_called_with( | ||
| 906 |             {'message': 'Need Revision', | ||
| 907 | 'notification_message': 'Error in biosample submission'}, 1) | ||
| 908 | |||
| 909 | |||
| 910 | class FetchDraftTestCase(FetchMixin, TestCase): | ||
| 911 | """a draft submission without errors""" | ||
| 912 | |||
| 913 | def test_fetch_status(self): | ||
| 914 | # a draft submission without errors | ||
| 915 | my_submission = Mock() | ||
| 916 | my_submission.name = "test-fetch" | ||
| 917 | my_submission.status = 'Draft' | ||
| 918 |         my_submission.has_errors.return_value = Counter({False: 1}) | ||
| 919 |         my_submission.get_status.return_value = Counter({'Complete': 1}) | ||
| 920 | |||
| 921 | # assert task and mock methods called | ||
| 922 | self.common_tests(my_submission) | ||
| 923 | |||
| 924 | # assert status for submissions | ||
| 925 | self.submission_obj.refresh_from_db() | ||
| 926 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 927 | |||
| 928 | # testing a finalized biosample condition | ||
| 929 | self.assertTrue(my_submission.finalize.called) | ||
| 930 | |||
| 931 | def test_fetch_status_pending(self): | ||
| 932 | """Testing status with pending validation""" | ||
| 933 | |||
| 934 | # a draft submission without errors | ||
| 935 | my_submission = Mock() | ||
| 936 | my_submission.name = "test-fetch" | ||
| 937 | my_submission.status = 'Draft' | ||
| 938 |         my_submission.has_errors.return_value = Counter({False: 1}) | ||
| 939 |         my_submission.get_status.return_value = Counter({'Pending': 1}) | ||
| 940 | |||
| 941 | # assert task and mock methods called | ||
| 942 | self.common_tests(my_submission) | ||
| 943 | |||
| 944 | # assert status for submissions | ||
| 945 | self.submission_obj.refresh_from_db() | ||
| 946 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 947 | |||
| 948 | # testing a not finalized biosample condition | ||
| 949 | self.assertFalse(my_submission.finalize.called) | ||
| 950 | |||
| 951 | def test_fetch_status_submitted(self): | ||
| 952 | """Testing status during biosample submission""" | ||
| 953 | |||
| 954 | # a draft submission without errors | ||
| 955 | my_submission = Mock() | ||
| 956 | my_submission.name = "test-fetch" | ||
| 957 | my_submission.status = 'Submitted' | ||
| 958 |         my_submission.has_errors.return_value = Counter({False: 1}) | ||
| 959 |         my_submission.get_status.return_value = Counter({'Complete': 1}) | ||
| 960 | |||
| 961 | # assert task and mock methods called | ||
| 962 | self.common_tests(my_submission) | ||
| 963 | |||
| 964 | # assert status for submissions | ||
| 965 | self.submission_obj.refresh_from_db() | ||
| 966 | self.assertEqual(self.submission_obj.status, SUBMITTED) | ||
| 967 | |||
| 968 | # testing a not finalized biosample condition | ||
| 969 | self.assertFalse(my_submission.finalize.called) | ||
| 970 | |||
| 971 | |||
| 972 | class FetchLongTaskTestCase(FetchMixin, TestCase): | ||
| 973 | """A submission wich remain in the same status for a long time""" | ||
| 974 | |||
| 975 | def setUp(self): | ||
| 976 | # calling my base setup | ||
| 977 | super().setUp() | ||
| 978 | |||
| 979 | # make "now" 2 months ago | ||
| 980 | testtime = timezone.now() - timedelta(days=60) | ||
| 981 | |||
| 982 | # https://devblog.kogan.com/blog/testing-auto-now-datetime-fields-in-django | ||
| 983 |         with patch('django.utils.timezone.now') as mock_now: | ||
| 984 | mock_now.return_value = testtime | ||
| 985 | |||
| 986 | # update submission updated time with an older date than now | ||
| 987 | self.submission_obj.updated_at = testtime | ||
| 988 | self.submission_obj.save() | ||
| 989 | |||
| 990 | View Code Duplication |     @patch('biosample.tasks.send_message_to_websocket') | |
| 991 |     @patch('asyncio.get_event_loop') | ||
| 992 | def test_error_in_submitted_status(self, asyncio_mock, | ||
| 993 | send_message_to_websocket_mock): | ||
| 994 | tmp = asyncio_mock.return_value | ||
| 995 | tmp.run_until_complete = Mock() | ||
| 996 | # a still running submission | ||
| 997 | self.my_submission = Mock() | ||
| 998 | self.my_submission.name = "test-fetch" | ||
| 999 | self.my_submission.status = 'Submitted' | ||
| 1000 | |||
| 1001 | # assert task and mock methods called | ||
| 1002 | self.common_tests(self.my_submission) | ||
| 1003 | |||
| 1004 | # test email sent | ||
| 1005 | self.assertEqual(len(mail.outbox), 1) | ||
| 1006 | |||
| 1007 | # read email | ||
| 1008 | email = mail.outbox[0] | ||
| 1009 | |||
| 1010 | self.assertEqual( | ||
| 1011 | "Error in biosample submission %s" % self.submission_obj_id, | ||
| 1012 | email.subject) | ||
| 1013 | |||
| 1014 | # check submission.state changed | ||
| 1015 | self.submission_obj.refresh_from_db() | ||
| 1016 | |||
| 1017 | self.assertEqual(self.submission_obj.status, ERROR) | ||
| 1018 | self.assertIn( | ||
| 1019 |             "Biosample subission {} remained with the same status".format( | ||
| 1020 | self.submission_obj), | ||
| 1021 | self.submission_obj.message | ||
| 1022 | ) | ||
| 1023 | |||
| 1024 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 1025 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 1026 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 1027 | send_message_to_websocket_mock.assert_called_with( | ||
| 1028 |             {'message': 'Error', | ||
| 1029 | 'notification_message': ( | ||
| 1030 | 'Biosample subission Cryoweb ' | ||
| 1031 | '(United Kingdom, test) remained with the ' | ||
| 1032 | 'same status for more than 5 days. Please ' | ||
| 1033 | 'report it to InjectTool team') | ||
| 1034 | }, 1) | ||
| 1035 | |||
| 1036 | View Code Duplication |     @patch('biosample.tasks.send_message_to_websocket') | |
| 1037 |     @patch('asyncio.get_event_loop') | ||
| 1038 | def test_error_in_draft_status(self, asyncio_mock, | ||
| 1039 | send_message_to_websocket_mock): | ||
| 1040 | tmp = asyncio_mock.return_value | ||
| 1041 | tmp.run_until_complete = Mock() | ||
| 1042 | # a still running submission | ||
| 1043 | self.my_submission = Mock() | ||
| 1044 | self.my_submission.name = "test-fetch" | ||
| 1045 | self.my_submission.status = 'Draft' | ||
| 1046 | |||
| 1047 | # assert task and mock methods called | ||
| 1048 | self.common_tests(self.my_submission) | ||
| 1049 | |||
| 1050 | # test email sent | ||
| 1051 | self.assertEqual(len(mail.outbox), 1) | ||
| 1052 | |||
| 1053 | # read email | ||
| 1054 | email = mail.outbox[0] | ||
| 1055 | |||
| 1056 | self.assertEqual( | ||
| 1057 | "Error in biosample submission %s" % self.submission_obj_id, | ||
| 1058 | email.subject) | ||
| 1059 | |||
| 1060 | # check submission.state changed | ||
| 1061 | self.submission_obj.refresh_from_db() | ||
| 1062 | |||
| 1063 | self.assertEqual(self.submission_obj.status, ERROR) | ||
| 1064 | self.assertIn( | ||
| 1065 |             "Biosample subission {} remained with the same status".format( | ||
| 1066 | self.submission_obj), | ||
| 1067 | self.submission_obj.message | ||
| 1068 | ) | ||
| 1069 | |||
| 1070 | self.assertEqual(asyncio_mock.call_count, 1) | ||
| 1071 | self.assertEqual(tmp.run_until_complete.call_count, 1) | ||
| 1072 | self.assertEqual(send_message_to_websocket_mock.call_count, 1) | ||
| 1073 | send_message_to_websocket_mock.assert_called_with( | ||
| 1074 |             {'message': 'Error', | ||
| 1075 | 'notification_message': ( | ||
| 1076 | 'Biosample subission Cryoweb ' | ||
| 1077 | '(United Kingdom, test) remained with the ' | ||
| 1078 | 'same status for more than 5 days. Please ' | ||
| 1079 | 'report it to InjectTool team') | ||
| 1080 | }, 1) | ||
| 1081 | |||
| 1082 | |||
| 1083 | class FetchUnsupportedStatusTestCase(FetchMixin, TestCase): | ||
| 1084 | """A submission object with a status I can ignore""" | ||
| 1085 | |||
| 1086 | def setUp(self): | ||
| 1087 | # calling my base setup | ||
| 1088 | super().setUp() | ||
| 1089 | |||
| 1090 | # a still running submission | ||
| 1091 | self.my_submission = Mock() | ||
| 1092 | self.my_submission.name = "test-fetch" | ||
| 1093 | |||
| 1094 | # passing submission to Mocked Root | ||
| 1095 | self.my_root.get_submission_by_name.return_value = self.my_submission | ||
| 1096 | |||
| 1097 | def update_status(self, status): | ||
| 1098 | # change status | ||
| 1099 | self.submission_obj.status = status | ||
| 1100 | self.submission_obj.save() | ||
| 1101 | |||
| 1102 | # override FetchMixing methods | ||
| 1103 | def common_tests(self, status): | ||
| 1104 | # update submission status | ||
| 1105 | self.update_status(status) | ||
| 1106 | |||
| 1107 | # NOTE that I'm calling the function directly, without delay | ||
| 1108 | # (AsyncResult). I've patched the time consuming task | ||
| 1109 | res = self.my_task.run() | ||
| 1110 | |||
| 1111 | # assert a success with data uploading | ||
| 1112 | self.assertEqual(res, "success") | ||
| 1113 | |||
| 1114 | self.assertFalse(self.mock_auth.called) | ||
| 1115 | self.assertFalse(self.mock_root.called) | ||
| 1116 | self.assertFalse(self.my_root.get_submission_by_name.called) | ||
| 1117 | self.assertFalse(self.my_submission.follow_url.called) | ||
| 1118 | |||
| 1119 | # assert status for submissions | ||
| 1120 | self.submission_obj.refresh_from_db() | ||
| 1121 | self.assertEqual(self.submission_obj.status, status) | ||
| 1122 | |||
| 1123 | def test_loaded(self): | ||
| 1124 | """Test fecth_status with a loaded submission""" | ||
| 1125 | |||
| 1126 | # assert task and mock methods called | ||
| 1127 | self.common_tests(LOADED) | ||
| 1128 | |||
| 1129 | def test_need_revision(self): | ||
| 1130 | """Test fecth_status with a need_revision submission""" | ||
| 1131 | |||
| 1132 | # assert task and mock methods called | ||
| 1133 | self.common_tests(NEED_REVISION) | ||
| 1134 | |||
| 1135 | def test_ready(self): | ||
| 1136 | """Test fecth_status with a ready submission""" | ||
| 1137 | |||
| 1138 | # assert task and mock methods called | ||
| 1139 | self.common_tests(READY) | ||
| 1140 | |||
| 1141 | def test_completed(self): | ||
| 1142 | """Test fecth_status with a completed submission""" | ||
| 1143 | |||
| 1144 | # assert task and mock methods called | ||
| 1145 | self.common_tests(COMPLETED) | ||
| 1146 |