Passed
Pull Request — master (#26)
by Paolo
02:00
created

biosample.tests.test_tasks   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 1146
Duplicated Lines 10.99 %

Importance

Changes 0
Metric Value
wmc 44
eloc 676
dl 126
loc 1146
rs 8.804
c 0
b 0
f 0

39 Methods

Rating   Name   Duplication   Size   Complexity  
A RedisMixin.setUpClass() 0 15 1
A SubmitTestCase.setUp() 0 6 1
A RedisMixin.tearDownClass() 0 6 2
A FetchUnsupportedStatusTestCase.test_completed() 0 5 1
A FetchMixin.setUp() 0 32 1
A FetchDraftTestCase.test_fetch_status() 0 17 1
A FetchMixin.tearDownClass() 0 7 1
A FetchLongTaskTestCase.test_error_in_submitted_status() 43 45 1
A SubmitTestCase.test_on_failure() 40 40 1
A FetchNotInDBTestCase.test_fetch_status() 0 7 1
A FetchWithErrorsTestCase.test_email_sent() 0 28 1
A SubmitTestCase.test_submit() 0 46 1
A FetchMixin.common_tests() 0 14 1
A FetchWithErrorsTestCase.common_tests() 0 11 1
A FetchLongTaskTestCase.test_error_in_draft_status() 43 45 1
B SubmitTestCase.test_submit_no_recover() 0 56 1
A FetchMixin.setUpClass() 0 14 1
A FetchCompletedTestCase.test_fetch_status_retry() 0 15 2
A FetchWithErrorsTestCase.test_fetch_status() 0 25 1
A FetchUnsupportedStatusTestCase.common_tests() 0 19 1
A FetchWithErrorsTestCase.setUp() 0 48 1
A FetchDraftTestCase.test_fetch_status_submitted() 0 19 1
A FetchUnsupportedStatusTestCase.update_status() 0 4 1
A FetchUnsupportedStatusTestCase.test_loaded() 0 5 1
A SubmitTestCase.test_submit_recover() 0 52 1
A FetchDraftTestCase.test_fetch_status_pending() 0 19 1
A FetchUnsupportedStatusTestCase.test_ready() 0 5 1
A SubmitTestCase.test_submit_retry() 0 15 2
A UpdateSubmissionTestCase.setUp() 0 23 1
A UpdateSubmissionTestCase.test_submit() 0 49 1
A FetchUnsupportedStatusTestCase.setUp() 0 10 1
A FetchCompletedTestCase.test_fetch_status_no_accession() 0 27 1
B SubmitTestCase.test_submit_patch() 0 61 2
A FetchLongTaskTestCase.setUp() 0 14 2
B SubmitTestCase.test_token_expired() 0 61 1
A FetchCompletedTestCase.test_fetch_status_nb() 0 10 1
A FetchCompletedTestCase.test_fetch_status() 0 46 1
A FetchUnsupportedStatusTestCase.test_need_revision() 0 5 1
A SubmitTestCase.test_issues_with_api() 0 54 1

How to fix   Duplicated Code    Complexity   

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:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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 patches 2 samples in this sample, 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')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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 (count 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')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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