Completed
Pull Request — master (#44)
by Paolo
05:54
created

biosample.tests.test_tasks_retrieval   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 597
Duplicated Lines 12.73 %

Importance

Changes 0
Metric Value
wmc 28
eloc 322
dl 76
loc 597
rs 10
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A FetchLongTaskTestCase.setUp() 0 14 2
A FetchCompletedTestCase.test_fetch_status() 0 40 1
A FetchUnsupportedStatusTestCase.test_ready() 0 5 1
A FetchWithErrorsTestCase.setUp() 0 48 1
A FetchLongTaskTestCase.test_error_in_draft_status() 38 38 1
A FetchCompletedTestCase.test_fetch_status_no_accession() 0 27 1
A FetchWithErrorsTestCase.test_email_sent() 0 22 1
A FetchDraftTestCase.test_fetch_status_pending() 0 19 1
A FetchCompletedTestCase.test_fetch_status_nb() 0 10 1
A FetchWithErrorsTestCase.common_tests() 0 11 1
A FetchUnsupportedStatusTestCase.update_status() 0 4 1
A FetchWithErrorsTestCase.test_fetch_status() 0 20 1
A FetchUnsupportedStatusTestCase.test_completed() 0 5 1
A FetchUnsupportedStatusTestCase.test_loaded() 0 5 1
A FetchUnsupportedStatusTestCase.setUp() 0 10 1
A FetchUnsupportedStatusTestCase.test_need_revision() 0 5 1
A FetchDraftTestCase.test_fetch_status() 0 17 1
A FetchMixin.setUp() 0 32 1
A FetchNotInDBTestCase.test_fetch_status() 0 7 1
A FetchMixin.tearDownClass() 0 7 1
A FetchMixin.setUpClass() 0 14 1
A FetchUnsupportedStatusTestCase.common_tests() 0 19 1
A FetchMixin.common_tests() 0 14 1
A FetchCompletedTestCase.test_fetch_status_retry() 0 15 2
A FetchDraftTestCase.test_fetch_status_submitted() 0 19 1
A FetchLongTaskTestCase.test_error_in_submitted_status() 38 38 1

How to fix   Duplicated Code   

Duplicated Code

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:

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
from pytest import raises
10
from collections import Counter
11
from unittest.mock import patch, Mock
12
from datetime import timedelta
13
14
from celery.exceptions import Retry
15
16
from django.test import TestCase
17
from django.core import mail
18
from django.utils import timezone
19
20
from common.constants import (
21
    LOADED, ERROR, READY, NEED_REVISION, SUBMITTED, COMPLETED)
22
from common.tests import WebSocketMixin
23
from image_app.models import Submission, Name
24
25
from ..tasks import FetchStatusTask
26
from ..models import ManagedTeam
27
28
29
class FetchMixin():
30
    """Mixin for fetching status"""
31
32
    fixtures = [
33
        'biosample/account',
34
        'biosample/managedteam',
35
        'image_app/dictcountry',
36
        'image_app/dictrole',
37
        'image_app/organization',
38
        'image_app/submission',
39
        'image_app/user'
40
    ]
41
42
    @classmethod
43
    def setUpClass(cls):
44
        # calling my base class setup
45
        super().setUpClass()
46
47
        unmanaged = ManagedTeam.objects.get(pk=2)
48
        unmanaged.delete()
49
50
        # starting mocked objects
51
        cls.mock_root_patcher = patch('pyUSIrest.client.Root')
52
        cls.mock_root = cls.mock_root_patcher.start()
53
54
        cls.mock_auth_patcher = patch('biosample.helpers.Auth')
55
        cls.mock_auth = cls.mock_auth_patcher.start()
56
57
    @classmethod
58
    def tearDownClass(cls):
59
        cls.mock_root_patcher.stop()
60
        cls.mock_auth_patcher.stop()
61
62
        # calling base method
63
        super().tearDownClass()
64
65
    def setUp(self):
66
        # calling my base setup
67
        super().setUp()
68
69
        # get a submission object
70
        self.submission_obj = Submission.objects.get(pk=1)
71
72
        # set a status which I can fetch_status
73
        self.submission_obj.status = SUBMITTED
74
        self.submission_obj.biosample_submission_id = "test-fetch"
75
        self.submission_obj.save()
76
77
        # set status for names, like submittask does. Only sample not unknown
78
        # are submitted
79
        self.name_qs = Name.objects.exclude(name__contains="unknown")
80
        self.name_qs.update(status=SUBMITTED)
81
82
        # count number of names in UID for such submission (exclude
83
        # unknown animals)
84
        self.n_to_submit = self.name_qs.count()
85
86
        # track submission ID
87
        self.submission_obj_id = self.submission_obj.id
88
89
        # start root object
90
        self.my_root = self.mock_root.return_value
91
92
        # define my task
93
        self.my_task = FetchStatusTask()
94
95
        # change lock_id (useful when running test during cron)
96
        self.my_task.lock_id = "test-FetchStatusTask"
97
98
    def common_tests(self, my_submission):
99
        # passing submission to Mocked Root
100
        self.my_root.get_submission_by_name.return_value = my_submission
101
102
        # NOTE that I'm calling the function directly, without delay
103
        # (AsyncResult). I've patched the time consuming task
104
        res = self.my_task.run()
105
106
        # assert a success with data uploading
107
        self.assertEqual(res, "success")
108
109
        self.assertTrue(self.mock_auth.called)
110
        self.assertTrue(self.mock_root.called)
111
        self.assertTrue(self.my_root.get_submission_by_name.called)
112
113
114
class FetchCompletedTestCase(FetchMixin, WebSocketMixin, TestCase):
115
    """a completed submission with two samples"""
116
117
    fixtures = [
118
        'biosample/account',
119
        'biosample/managedteam',
120
        'image_app/animal',
121
        'image_app/dictbreed',
122
        'image_app/dictcountry',
123
        'image_app/dictrole',
124
        'image_app/dictsex',
125
        'image_app/dictspecie',
126
        'image_app/dictstage',
127
        'image_app/dictuberon',
128
        'image_app/name',
129
        'image_app/organization',
130
        'image_app/publication',
131
        'image_app/sample',
132
        'image_app/submission',
133
        'image_app/user'
134
    ]
135
136
    def test_fetch_status(self):
137
        # a completed submission with two samples
138
        my_submission = Mock()
139
        my_submission.name = "test-fetch"
140
        my_submission.status = 'Completed'
141
142
        # Add samples
143
        my_sample1 = Mock()
144
        my_sample1.name = "test-animal"
145
        my_sample1.alias = "IMAGEA000000001"
146
        my_sample1.accession = "SAMEA0000001"
147
        my_sample2 = Mock()
148
        my_sample2.name = "test-sample"
149
        my_sample2.alias = "IMAGES000000001"
150
        my_sample2.accession = "SAMEA0000002"
151
        my_submission.get_samples.return_value = [my_sample1, my_sample2]
152
153
        # assert task and mock methods called
154
        self.common_tests(my_submission)
155
156
        # assert status for submissions
157
        self.submission_obj.refresh_from_db()
158
        self.assertEqual(self.submission_obj.status, COMPLETED)
159
160
        # check name status changed
161
        qs = Name.objects.filter(status=COMPLETED)
162
        self.assertEqual(len(qs), 2)
163
164
        # fetch two name objects
165
        name = Name.objects.get(name='ANIMAL:::ID:::132713')
166
        self.assertEqual(name.biosample_id, "SAMEA0000001")
167
168
        name = Name.objects.get(name='Siems_0722_393449')
169
        self.assertEqual(name.biosample_id, "SAMEA0000002")
170
171
        message = 'Completed'
172
        notification_message = 'Successful submission into biosample'
173
174
        # calling a WebSocketMixin method
175
        self.check_message(message, notification_message)
176
177
    def test_fetch_status_no_accession(self):
178
        # a completed submission with two samples
179
        my_submission = Mock()
180
        my_submission.name = "test-fetch"
181
        my_submission.status = 'Submitted'
182
183
        # Add samples
184
        my_sample1 = Mock()
185
        my_sample1.name = "test-animal"
186
        my_sample1.alias = "IMAGEA000000001"
187
        my_sample1.accession = None
188
        my_sample2 = Mock()
189
        my_sample2.name = "test-sample"
190
        my_sample2.alias = "IMAGES000000001"
191
        my_sample2.accession = None
192
        my_submission.get_samples.return_value = [my_sample1, my_sample2]
193
194
        # assert task and mock methods called
195
        self.common_tests(my_submission)
196
197
        # assert status for submissions
198
        self.submission_obj.refresh_from_db()
199
        self.assertEqual(self.submission_obj.status, SUBMITTED)
200
201
        # check name status changed
202
        qs = Name.objects.filter(status=SUBMITTED)
203
        self.assertEqual(len(qs), self.n_to_submit)
204
205
    # http://docs.celeryproject.org/en/latest/userguide/testing.html#tasks-and-unit-tests
206
    @patch("biosample.tasks.FetchStatusTask.retry")
207
    @patch("biosample.tasks.FetchStatusTask.fetch_queryset")
208
    def test_fetch_status_retry(self, my_fetch, my_retry):
209
        """Test fetch status with retry"""
210
211
        # Set a side effect on the patched methods
212
        # so that they raise the errors we want.
213
        my_retry.side_effect = Retry()
214
        my_fetch.side_effect = ConnectionError()
215
216
        with raises(Retry):
217
            self.my_task.run()
218
219
        self.assertTrue(my_fetch.called)
220
        self.assertTrue(my_retry.called)
221
222
    # Test a non blocking instance
223
    @patch("biosample.tasks.FetchStatusTask.fetch_queryset")
224
    @patch("redis.lock.Lock.acquire", return_value=False)
225
    def test_fetch_status_nb(self, my_lock, my_fetch):
226
        """Test FetchSTatus while a lock is present"""
227
228
        res = self.my_task.run()
229
230
        # assert database is locked
231
        self.assertEqual(res, "%s already running!" % (self.my_task.name))
232
        self.assertFalse(my_fetch.called)
233
234
235
class FetchNotInDBTestCase(FetchMixin, TestCase):
236
    """A submission not in db"""
237
238
    def test_fetch_status(self):
239
        # mocking submissions. A submission not in db
240
        my_submission = Mock()
241
        my_submission.name = "not-present-in-db"
242
243
        # assert task and mock methods called
244
        self.common_tests(my_submission)
245
246
247
class FetchWithErrorsTestCase(FetchMixin, WebSocketMixin, TestCase):
248
    """Test a submission with errors for biosample"""
249
250
    fixtures = [
251
        'biosample/account',
252
        'biosample/managedteam',
253
        'image_app/animal',
254
        'image_app/dictbreed',
255
        'image_app/dictcountry',
256
        'image_app/dictrole',
257
        'image_app/dictsex',
258
        'image_app/dictspecie',
259
        'image_app/dictstage',
260
        'image_app/dictuberon',
261
        'image_app/name',
262
        'image_app/organization',
263
        'image_app/publication',
264
        'image_app/sample',
265
        'image_app/submission',
266
        'image_app/user'
267
    ]
268
269
    def setUp(self):
270
        # calling my base setup
271
        super().setUp()
272
273
        # a draft submission with errors
274
        my_submission = Mock()
275
        my_submission.name = "test-fetch"
276
        my_submission.status = 'Draft'
277
        my_submission.has_errors.return_value = Counter({True: 1, False: 1})
278
        my_submission.get_status.return_value = Counter({'Complete': 2})
279
280
        # Add samples. Suppose that first failed, second is ok
281
        my_validation_result1 = Mock()
282
        my_validation_result1.errorMessages = {
283
            'Ena': [
284
                'a sample message',
285
            ]
286
        }
287
288
        my_sample1 = Mock()
289
        my_sample1.name = "test-animal"
290
        my_sample1.alias = "IMAGEA000000001"
291
        my_sample1.has_errors.return_value = True
292
        my_sample1.get_validation_result.return_value = my_validation_result1
293
294
        # sample2 is ok
295
        my_validation_result2 = Mock()
296
        my_validation_result2.errorMessages = None
297
298
        my_sample2 = Mock()
299
        my_sample2.name = "test-sample"
300
        my_sample2.alias = "IMAGES000000001"
301
        my_sample2.has_errors.return_value = False
302
        my_sample2.get_validation_result.return_value = my_validation_result2
303
304
        # simulate that IMAGEA000000001 has errors
305
        my_submission.get_samples.return_value = [my_sample1, my_sample2]
306
307
        # track objects
308
        self.my_submission = my_submission
309
        self.my_validation_result1 = my_validation_result1
310
        self.my_validation_result2 = my_validation_result2
311
        self.my_sample1 = my_sample1
312
        self.my_sample2 = my_sample2
313
314
        # track names
315
        self.animal_name = Name.objects.get(pk=3)
316
        self.sample_name = Name.objects.get(pk=4)
317
318
    def common_tests(self):
319
        # assert task and mock methods called
320
        super().common_tests(self.my_submission)
321
322
        # assert custom mock attributes called
323
        self.assertTrue(self.my_sample1.has_errors.called)
324
        self.assertTrue(self.my_sample1.get_validation_result.called)
325
326
        # if sample has no errors, no all methods will be called
327
        self.assertTrue(self.my_sample2.has_errors.called)
328
        self.assertFalse(self.my_sample2.get_validation_result.called)
329
330
    def test_fetch_status(self):
331
        # assert task and mock methods called
332
        self.common_tests()
333
334
        # assert submission status
335
        self.submission_obj.refresh_from_db()
336
        self.assertEqual(self.submission_obj.status, NEED_REVISION)
337
338
        # check name status changed only for animal (not sample)
339
        self.animal_name.refresh_from_db()
340
        self.assertEqual(self.animal_name.status, NEED_REVISION)
341
342
        self.sample_name.refresh_from_db()
343
        self.assertEqual(self.sample_name.status, SUBMITTED)
344
345
        message = 'Need Revision'
346
        notification_message = 'Error in biosample submission'
347
348
        # calling a WebSocketMixin method
349
        self.check_message(message, notification_message)
350
351
    def test_email_sent(self):
352
        # assert task and mock methods called
353
        self.common_tests()
354
355
        # test email sent
356
        self.assertEqual(len(mail.outbox), 1)
357
358
        # read email
359
        email = mail.outbox[0]
360
361
        self.assertEqual(
362
            "Error in biosample submission %s" % self.submission_obj_id,
363
            email.subject)
364
365
        # check for error messages in object
366
        self.assertIn("a sample message", email.body)
367
368
        message = 'Need Revision'
369
        notification_message = 'Error in biosample submission'
370
371
        # calling a WebSocketMixin method
372
        self.check_message(message, notification_message)
373
374
375
class FetchDraftTestCase(FetchMixin, TestCase):
376
    """a draft submission without errors"""
377
378
    def test_fetch_status(self):
379
        # a draft submission without errors
380
        my_submission = Mock()
381
        my_submission.name = "test-fetch"
382
        my_submission.status = 'Draft'
383
        my_submission.has_errors.return_value = Counter({False: 1})
384
        my_submission.get_status.return_value = Counter({'Complete': 1})
385
386
        # assert task and mock methods called
387
        self.common_tests(my_submission)
388
389
        # assert status for submissions
390
        self.submission_obj.refresh_from_db()
391
        self.assertEqual(self.submission_obj.status, SUBMITTED)
392
393
        # testing a finalized biosample condition
394
        self.assertTrue(my_submission.finalize.called)
395
396
    def test_fetch_status_pending(self):
397
        """Testing status with pending validation"""
398
399
        # a draft submission without errors
400
        my_submission = Mock()
401
        my_submission.name = "test-fetch"
402
        my_submission.status = 'Draft'
403
        my_submission.has_errors.return_value = Counter({False: 1})
404
        my_submission.get_status.return_value = Counter({'Pending': 1})
405
406
        # assert task and mock methods called
407
        self.common_tests(my_submission)
408
409
        # assert status for submissions
410
        self.submission_obj.refresh_from_db()
411
        self.assertEqual(self.submission_obj.status, SUBMITTED)
412
413
        # testing a not finalized biosample condition
414
        self.assertFalse(my_submission.finalize.called)
415
416
    def test_fetch_status_submitted(self):
417
        """Testing status during biosample submission"""
418
419
        # a draft submission without errors
420
        my_submission = Mock()
421
        my_submission.name = "test-fetch"
422
        my_submission.status = 'Submitted'
423
        my_submission.has_errors.return_value = Counter({False: 1})
424
        my_submission.get_status.return_value = Counter({'Complete': 1})
425
426
        # assert task and mock methods called
427
        self.common_tests(my_submission)
428
429
        # assert status for submissions
430
        self.submission_obj.refresh_from_db()
431
        self.assertEqual(self.submission_obj.status, SUBMITTED)
432
433
        # testing a not finalized biosample condition
434
        self.assertFalse(my_submission.finalize.called)
435
436
437
class FetchLongTaskTestCase(FetchMixin, WebSocketMixin, TestCase):
438
    """A submission wich remain in the same status for a long time"""
439
440
    def setUp(self):
441
        # calling my base setup
442
        super().setUp()
443
444
        # make "now" 2 months ago
445
        testtime = timezone.now() - timedelta(days=60)
446
447
        # https://devblog.kogan.com/blog/testing-auto-now-datetime-fields-in-django
448
        with patch('django.utils.timezone.now') as mock_now:
449
            mock_now.return_value = testtime
450
451
            # update submission updated time with an older date than now
452
            self.submission_obj.updated_at = testtime
453
            self.submission_obj.save()
454
455 View Code Duplication
    def test_error_in_submitted_status(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
456
        # a still running submission
457
        self.my_submission = Mock()
458
        self.my_submission.name = "test-fetch"
459
        self.my_submission.status = 'Submitted'
460
461
        # assert task and mock methods called
462
        self.common_tests(self.my_submission)
463
464
        # test email sent
465
        self.assertEqual(len(mail.outbox), 1)
466
467
        # read email
468
        email = mail.outbox[0]
469
470
        self.assertEqual(
471
            "Error in biosample submission %s" % self.submission_obj_id,
472
            email.subject)
473
474
        # check submission.state changed
475
        self.submission_obj.refresh_from_db()
476
477
        self.assertEqual(self.submission_obj.status, ERROR)
478
        self.assertIn(
479
            "Biosample subission {} remained with the same status".format(
480
                    self.submission_obj),
481
            self.submission_obj.message
482
            )
483
484
        message = 'Error'
485
        notification_message = (
486
            'Biosample subission Cryoweb '
487
            '(United Kingdom, test) remained with the '
488
            'same status for more than 5 days. Please '
489
            'report it to InjectTool team')
490
491
        # calling a WebSocketMixin method
492
        self.check_message(message, notification_message)
493
494 View Code Duplication
    def test_error_in_draft_status(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
495
        # a still running submission
496
        self.my_submission = Mock()
497
        self.my_submission.name = "test-fetch"
498
        self.my_submission.status = 'Draft'
499
500
        # assert task and mock methods called
501
        self.common_tests(self.my_submission)
502
503
        # test email sent
504
        self.assertEqual(len(mail.outbox), 1)
505
506
        # read email
507
        email = mail.outbox[0]
508
509
        self.assertEqual(
510
            "Error in biosample submission %s" % self.submission_obj_id,
511
            email.subject)
512
513
        # check submission.state changed
514
        self.submission_obj.refresh_from_db()
515
516
        self.assertEqual(self.submission_obj.status, ERROR)
517
        self.assertIn(
518
            "Biosample subission {} remained with the same status".format(
519
                    self.submission_obj),
520
            self.submission_obj.message
521
            )
522
523
        message = 'Error'
524
        notification_message = (
525
            'Biosample subission Cryoweb '
526
            '(United Kingdom, test) remained with the '
527
            'same status for more than 5 days. Please '
528
            'report it to InjectTool team')
529
530
        # calling a WebSocketMixin method
531
        self.check_message(message, notification_message)
532
533
534
class FetchUnsupportedStatusTestCase(FetchMixin, TestCase):
535
    """A submission object with a status I can ignore"""
536
537
    def setUp(self):
538
        # calling my base setup
539
        super().setUp()
540
541
        # a still running submission
542
        self.my_submission = Mock()
543
        self.my_submission.name = "test-fetch"
544
545
        # passing submission to Mocked Root
546
        self.my_root.get_submission_by_name.return_value = self.my_submission
547
548
    def update_status(self, status):
549
        # change status
550
        self.submission_obj.status = status
551
        self.submission_obj.save()
552
553
    # override FetchMixing methods
554
    def common_tests(self, status):
555
        # update submission status
556
        self.update_status(status)
557
558
        # NOTE that I'm calling the function directly, without delay
559
        # (AsyncResult). I've patched the time consuming task
560
        res = self.my_task.run()
561
562
        # assert a success with data uploading
563
        self.assertEqual(res, "success")
564
565
        self.assertFalse(self.mock_auth.called)
566
        self.assertFalse(self.mock_root.called)
567
        self.assertFalse(self.my_root.get_submission_by_name.called)
568
        self.assertFalse(self.my_submission.follow_url.called)
569
570
        # assert status for submissions
571
        self.submission_obj.refresh_from_db()
572
        self.assertEqual(self.submission_obj.status, status)
573
574
    def test_loaded(self):
575
        """Test fecth_status with a loaded submission"""
576
577
        # assert task and mock methods called
578
        self.common_tests(LOADED)
579
580
    def test_need_revision(self):
581
        """Test fecth_status with a need_revision submission"""
582
583
        # assert task and mock methods called
584
        self.common_tests(NEED_REVISION)
585
586
    def test_ready(self):
587
        """Test fecth_status with a ready submission"""
588
589
        # assert task and mock methods called
590
        self.common_tests(READY)
591
592
    def test_completed(self):
593
        """Test fecth_status with a completed submission"""
594
595
        # assert task and mock methods called
596
        self.common_tests(COMPLETED)
597