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

biosample.tests.test_tasks_submission   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 561
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 32
eloc 270
dl 0
loc 561
rs 9.84
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
A SubmitTaskTestCase.test_unmanaged() 0 13 1
A SubmissionHelperTestCase.setUp() 0 16 1
A SubmitTaskTestCase.common_test() 0 20 1
A SubmissionHelperTestCase.test_properties() 0 14 1
A SubmissionHelperTestCase.test_update_sample() 0 27 2
A SubmissionCompleteTaskTestCase.setUp() 0 9 1
A SplitSubmissionMixin.setUp() 0 10 1
A SubmissionHelperTestCase.test_recover_submission_error() 0 21 1
A SubmissionHelperTestCase.test_start_submission() 0 22 1
A SubmissionHelperTestCase.test_mark_submission() 0 14 1
A SubmitTaskTestCase.setUp() 0 24 1
A SplitSubmissionTaskUpdateTestCase.setUp() 0 8 1
A SplitSubmissionMixin.generic_check() 0 23 2
A SplitSubmissionTaskUpdateTestCase.test_split_submission() 0 8 1
A SubmissionCompleteTaskTestCase.test_submission_complete() 0 21 1
A SubmitTaskTestCase.test_submit() 0 9 1
A SubmissionHelperTestCase.test_read_samples() 0 23 1
A SplitSubmissionTaskTestCase.test_sample_already_in_submission() 0 11 1
A SubmitTaskTestCase.test_token_expired() 0 14 1
A SplitSubmissionTaskTestCase.test_split_submission() 0 8 1
A SubmissionHelperTestCase.test_read_token() 0 8 1
A SubmissionHelperTestCase.test_create_sample() 0 17 1
A SplitSubmissionTaskUpdateTestCase.test_split_submission_issues() 0 16 1
A SubmissionHelperTestCase.test_create_submission() 0 14 1
A SubmitTaskTestCase.tearDown() 0 8 1
A SubmissionHelperTestCase.test_add_samples() 0 14 1
A SplitSubmissionTaskTestCase.test_split_submission_partial() 0 9 1
A SubmitTaskTestCase.test_issues_with_api() 0 15 1
A SubmissionHelperTestCase.test_recover_submission() 0 26 1
A SplitSubmissionMixin.tearDown() 0 6 1
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
"""
4
Created on Wed Jul 17 10:51:40 2019
5
6
@author: Paolo Cozzi <[email protected]>
7
"""
8
9
from unittest.mock import patch, PropertyMock, Mock
10
11
from django.test import TestCase
12
13
from common.constants import READY, COMPLETED, ERROR, SUBMITTED, WAITING
14
15
from .common import TaskFailureMixin, RedisMixin, BaseMixin
16
from ..models import Submission as USISubmission, SubmissionData
17
from ..tasks.submission import (
18
    SubmissionHelper, SplitSubmissionTask, SubmitTask, SubmissionError,
19
    SubmissionCompleteTask)
20
21
22
class SubmissionFeaturesMixin(BaseMixin):
23
    """Common features for SubmitTask and SubmissionHelper"""
24
25
    # overriding BaseMixin features
26
    fixtures = [
27
        'biosample/account',
28
        'biosample/managedteam',
29
        'biosample/submission',
30
        'biosample/submissiondata',
31
        'image_app/animal',
32
        'image_app/dictbreed',
33
        'image_app/dictcountry',
34
        'image_app/dictrole',
35
        'image_app/dictsex',
36
        'image_app/dictspecie',
37
        'image_app/dictstage',
38
        'image_app/dictuberon',
39
        'image_app/name',
40
        'image_app/ontology',
41
        'image_app/organization',
42
        'image_app/publication',
43
        'image_app/sample',
44
        'image_app/submission',
45
        'image_app/user'
46
    ]
47
48
49
class SplitSubmissionMixin(TaskFailureMixin, BaseMixin):
50
    """Generic stuff for SplitSubmissionTask tests"""
51
52
    def setUp(self):
53
        # call Mixin method
54
        super().setUp()
55
56
        # setting tasks
57
        self.my_task = SplitSubmissionTask()
58
59
        # patching objects
60
        self.mock_chord_patcher = patch('biosample.tasks.submission.chord')
61
        self.mock_chord = self.mock_chord_patcher.start()
62
63
        # other function are not called since chord is patched
64
65
    def tearDown(self):
66
        # stopping mock objects
67
        self.mock_chord_patcher.stop()
68
69
        # calling base object
70
        super().tearDown()
71
72
    def generic_check(self, res, n_of_submission, n_of_submissiondata):
73
        """Generic check for created data"""
74
75
        # assert a success with data uploading
76
        self.assertEqual(res, "success")
77
78
        # get usi_submission qs
79
        usi_submissions_qs = USISubmission.objects.all()
80
81
        # asserting two biosample.submission data objects
82
        self.assertEqual(usi_submissions_qs.count(), n_of_submission)
83
84
        # assert two data for each submission
85
        for usi_submission in usi_submissions_qs:
86
            self.assertEqual(usi_submission.status, READY)
87
88
            # grab submission data queryset
89
            submission_data_qs = SubmissionData.objects.filter(
90
                submission=usi_submission)
91
            self.assertEqual(submission_data_qs.count(), n_of_submissiondata)
92
93
        # assert mock objects called
94
        self.assertTrue(self.mock_chord.called)
95
96
97
class SplitSubmissionTaskTestCase(SplitSubmissionMixin, TestCase):
98
    # ovverride MAX_SAMPLES in order to split data
99
    @patch('biosample.tasks.submission.MAX_SAMPLES', 2)
100
    def test_split_submission(self):
101
        """Test splitting submission data"""
102
103
        res = self.my_task.run(submission_id=self.submission_id)
104
105
        self.generic_check(res, n_of_submission=2, n_of_submissiondata=2)
106
        self.assertEqual(self.n_to_submit, SubmissionData.objects.count())
107
108
    # ovverride MAX_SAMPLES in order to split data
109
    @patch('biosample.tasks.submission.MAX_SAMPLES', 2)
110
    def test_split_submission_partial(self):
111
        """Test splitting submission data with some data already submitted"""
112
113
        self.name_qs.filter(pk__in=[3, 4]).update(status=COMPLETED)
114
115
        res = self.my_task.run(submission_id=self.submission_id)
116
117
        self.generic_check(res, n_of_submission=1, n_of_submissiondata=2)
118
119
    @patch('biosample.tasks.submission.MAX_SAMPLES', 2)
120
    def test_sample_already_in_submission(self):
121
        """Test splitting submission with sample in a opened submission"""
122
123
        # call task once
124
        self.my_task.run(submission_id=self.submission_id)
125
126
        # call a task a second time
127
        res = self.my_task.run(submission_id=self.submission_id)
128
129
        self.generic_check(res, n_of_submission=2, n_of_submissiondata=2)
130
131
132
class SplitSubmissionTaskUpdateTestCase(
133
        SubmissionFeaturesMixin, SplitSubmissionMixin, TestCase):
134
    """
135
    A particoular test case: I submit data once and then biosample tell me
136
    that there are errors in submission data. So I mark name with need revision
137
    status and the submission is already opened. I can't send data within a
138
    new submission, I need to restore things a submit the data I have (since
139
    to_biosample() is called on user data when submitting, there are no issues
140
    with old data in biosample.models)"""
141
142
    def setUp(self):
143
        # call Mixin method
144
        super().setUp()
145
146
        # ok in this case, my samples are READY since I passed validation
147
        # after fail into biosample stage. My submission will be in SUBMITTED
148
        # stage
149
        USISubmission.objects.update(status=SUBMITTED)
150
151
    # ovverride MAX_SAMPLES in order to split data
152
    @patch('biosample.tasks.submission.MAX_SAMPLES', 2)
153
    def test_split_submission(self):
154
        """Test splitting submission data"""
155
156
        res = self.my_task.run(submission_id=self.submission_id)
157
158
        self.generic_check(res, n_of_submission=2, n_of_submissiondata=2)
159
        self.assertEqual(self.n_to_submit, SubmissionData.objects.count())
160
161
    # ovverride MAX_SAMPLES in order to split data
162
    @patch('biosample.tasks.submission.MAX_SAMPLES', 2)
163
    def test_split_submission_issues(self):
164
        """Test splitting submission data with issues"""
165
166
        # add a new submission data with the same sample, just to test
167
        # exception handling
168
        # https://stackoverflow.com/a/4736172/4385116
169
        submission_data = SubmissionData.objects.get(pk=1)
170
        submission_data.pk = None
171
        submission_data.save()
172
173
        self.assertRaisesRegex(
174
            SubmissionError,
175
            "More than one submission opened",
176
            self.my_task.run,
177
            submission_id=self.submission_id)
178
179
180
class SubmissionHelperTestCase(RedisMixin, SubmissionFeaturesMixin, TestCase):
181
182
    def setUp(self):
183
        # call Mixin method
184
        super().setUp()
185
186
        # set my pk
187
        self.usi_submission_id = 1
188
189
        # Instantiating SubmissionHelper with biosample.submission pk
190
        self.submission_helper = SubmissionHelper(self.usi_submission_id)
191
192
        # get a biosample.submission object
193
        self.usi_submission = USISubmission.objects.get(
194
            pk=self.usi_submission_id)
195
196
        # set attributes with class baseMixin class attributes for semplicity
197
        self.submission_helper.root = self.my_root
198
199
    def test_properties(self):
200
        """Asserting read properties"""
201
202
        owner = self.submission_helper.owner
203
        self.assertEqual(owner.username, "test")
204
205
        team_name = self.submission_helper.team_name
206
        self.assertEqual(team_name, "subs.test-team-1")
207
208
        self.assertIsNone(self.submission_helper.usi_submission_name)
209
        self.submission_helper.usi_submission_name = "test"
210
211
        self.usi_submission.refresh_from_db()
212
        self.assertEqual(self.usi_submission.usi_submission_name, "test")
213
214
    def test_read_token(self):
215
        """testing token from redis DB"""
216
217
        token = self.submission_helper.read_token()
218
        self.assertEqual(self.token, token)
219
220
        # assert called mock objects
221
        self.assertTrue(self.mock_root.called)
222
223
    @patch.object(SubmissionHelper, "read_samples")
224
    def test_recover_submission(self, my_helper):
225
        """Testing submission recover"""
226
227
        # base case: no usi_submission_name so False is expected
228
        self.assertFalse(self.submission_helper.recover_submission())
229
230
        # assign a usi_submission_name
231
        self.submission_helper.usi_submission_name = "test-submission"
232
233
        # asserted final returned status
234
        self.assertTrue(self.submission_helper.recover_submission())
235
236
        # assert a recovered document
237
        self.assertEqual(
238
            "test-submission",
239
            self.submission_helper.usi_submission.name)
240
241
        # assert get_samples called functions called
242
        self.assertTrue(my_helper.called)
243
244
        # asserting others mock objects called
245
        self.assertFalse(self.my_root.get_team_by_name.called)
246
        self.assertFalse(self.my_team.create_submission.called)
247
        self.assertTrue(self.my_root.get_submission_by_name.called)
248
        self.assertTrue(self.my_submission.propertymock.called)
249
250
    def test_recover_submission_error(self):
251
        """Testing submission recover for a closed submission"""
252
253
        # assign a usi_submission_name
254
        self.submission_helper.usi_submission_name = "test-submission"
255
256
        # change submission status
257
        self.my_submission.propertymock = PropertyMock(
258
            return_value='Completed')
259
        type(self.my_submission).status = self.my_submission.propertymock
260
261
        self.assertRaisesRegex(
262
            SubmissionError,
263
            "Cannot recover submission",
264
            self.submission_helper.recover_submission)
265
266
        # asserting others mock objects called
267
        self.assertFalse(self.my_root.get_team_by_name.called)
268
        self.assertFalse(self.my_team.create_submission.called)
269
        self.assertTrue(self.my_root.get_submission_by_name.called)
270
        self.assertTrue(self.my_submission.propertymock.called)
271
272
    def test_create_submission(self):
273
        """Testing submission create"""
274
275
        self.submission_helper.create_submission()
276
277
        # assert a new document
278
        self.assertEqual(
279
            "new-submission",
280
            self.submission_helper.usi_submission.name)
281
282
        # asserting others mock objects called
283
        self.assertTrue(self.my_root.get_team_by_name.called)
284
        self.assertTrue(self.my_team.create_submission.called)
285
        self.assertFalse(self.my_root.get_submission_by_name.called)
286
287
    @patch.object(SubmissionHelper, "create_submission")
288
    @patch.object(SubmissionHelper, "recover_submission")
289
    def test_start_submission(self, my_recover, my_create):
290
        """testing start a submission"""
291
292
        def create_submission():
293
            """Simulate create submission (already tested)"""
294
295
            self.submission_helper.usi_submission = self.new_submission
296
            return self.new_submission
297
298
        my_recover.return_value = False
299
        my_create.side_effect = create_submission
300
301
        usi_submission = self.submission_helper.start_submission()
302
303
        # assert a new document
304
        self.assertEqual("new-submission", usi_submission.name)
305
306
        # assert mock methods called
307
        self.assertTrue(my_recover.called)
308
        self.assertTrue(my_create.called)
309
310
    def test_read_samples(self):
311
312
        # creating mock samples
313
        my_samples = [
314
            Mock(**{'alias': 'IMAGEA000000001',
315
                    'title': 'a 4-year old pig organic fed'}),
316
            Mock(**{'alias': 'IMAGES000000001',
317
                    'title': 'semen collected when the animal turns to 4'}),
318
        ]
319
320
        # mocking set samples
321
        self.my_submission.get_samples.return_value = my_samples
322
        self.submission_helper.usi_submission = self.my_submission
323
324
        # calling function
325
        submitted_samples = self.submission_helper.read_samples()
326
327
        self.assertIsInstance(submitted_samples, dict)
328
        self.assertEqual(len(submitted_samples), 2)
329
330
        keys = submitted_samples.keys()
331
        self.assertIn('IMAGEA000000001', keys)
332
        self.assertIn('IMAGES000000001', keys)
333
334
    def test_create_sample(self):
335
        # get a model object
336
        submission_data = SubmissionData.objects.get(pk=1)
337
        model = submission_data.content_object
338
339
        # get a biosample submission
340
        self.submission_helper.usi_submission = self.my_submission
341
342
        # add model to biosample submission
343
        self.submission_helper.create_or_update_sample(model)
344
345
        # assert status
346
        self.assertEqual(model.name.status, SUBMITTED)
347
348
        # testing things
349
        self.assertEqual(
350
            self.my_submission.create_sample.call_count, 1)
351
352
    def test_update_sample(self):
353
        # creating mock samples
354
        my_samples = [
355
            Mock(**{'alias': 'IMAGEA000000001',
356
                    'title': 'a 4-year old pig organic fed'}),
357
        ]
358
359
        # mocking set samples
360
        self.my_submission.get_samples.return_value = my_samples
361
        self.submission_helper.usi_submission = self.my_submission
362
363
        # read samples through function (already tested)
364
        self.submission_helper.read_samples()
365
366
        # get a model object
367
        submission_data = SubmissionData.objects.get(pk=1)
368
        model = submission_data.content_object
369
370
        # add model to biosample submission
371
        self.submission_helper.create_or_update_sample(model)
372
373
        # assert status
374
        self.assertEqual(model.name.status, SUBMITTED)
375
376
        # testing patch
377
        for sample in my_samples:
378
            self.assertTrue(sample.patch.called)
379
380
    @patch.object(SubmissionHelper, "create_or_update_sample")
381
    def test_add_samples(self, my_create):
382
        """Test adding samples"""
383
384
        # simulate a submission recover: mark an animal as already submitted
385
        submission_data = SubmissionData.objects.get(pk=1)
386
        submission_data.content_object.name.status = SUBMITTED
387
        submission_data.content_object.name.save()
388
389
        # calling method
390
        self.submission_helper.add_samples()
391
392
        # assert create sample in biosample called once
393
        my_create.assert_called_once()
394
395
    def test_mark_submission(self):
396
        """test adding status message to submission"""
397
398
        self.submission_helper.mark_fail(message="test")
399
400
        self.usi_submission.refresh_from_db()
401
        self.assertEqual(self.usi_submission.status, ERROR)
402
        self.assertEqual(self.usi_submission.message, "test")
403
404
        self.submission_helper.mark_success()
405
406
        self.usi_submission.refresh_from_db()
407
        self.assertEqual(self.usi_submission.status, SUBMITTED)
408
        self.assertEqual(self.usi_submission.message, "Submitted to biosample")
409
410
411
class SubmitTaskTestCase(SubmissionFeaturesMixin, TestCase):
412
    """Test submission task"""
413
414
    def setUp(self):
415
        # call Mixin method
416
        super().setUp()
417
418
        # setting tasks
419
        self.my_task = SubmitTask()
420
421
        # set my pk
422
        self.usi_submission_id = 1
423
424
        # get a biosample.submission object
425
        self.usi_submission = USISubmission.objects.get(
426
            pk=self.usi_submission_id)
427
428
        # starting mocked objects
429
        self.mock_read_patcher = patch.object(SubmissionHelper, "read_token")
430
        self.mock_read = self.mock_read_patcher.start()
431
432
        self.mock_start_patcher = patch.object(
433
            SubmissionHelper, "start_submission")
434
        self.mock_start = self.mock_start_patcher.start()
435
436
        self.mock_add_patcher = patch.object(SubmissionHelper, "add_samples")
437
        self.mock_add = self.mock_add_patcher.start()
438
439
    def tearDown(self):
440
        # stopping mock objects
441
        self.mock_read_patcher.stop()
442
        self.mock_start_patcher.stop()
443
        self.mock_add_patcher.stop()
444
445
        # calling base object
446
        super().tearDown()
447
448
    def common_test(self, task_result, message, status):
449
        # assert a success with data uploading
450
        self.assertEqual(task_result, ("success", self.usi_submission_id))
451
452
        # check submission status and message
453
        self.usi_submission.refresh_from_db()
454
455
        # check submission.status changed
456
        self.assertEqual(self.usi_submission.status, status)
457
        self.assertIn(message, self.usi_submission.message)
458
459
        # no status changes for UID submission (will callback change status)
460
        self.assertEqual(self.submission_obj.status, WAITING)
461
        self.assertEqual(
462
            self.submission_obj.message,
463
            "Waiting for biosample submission")
464
465
        self.assertTrue(self.mock_read.called)
466
        self.assertTrue(self.mock_start.called)
467
        self.assertTrue(self.mock_add.called)
468
469
    def test_submit(self):
470
        """Test submitting into biosample"""
471
472
        # NOTE that I'm calling the function directly, without delay
473
        # (AsyncResult). I've patched the time consuming task
474
        # Remeber that submission_id will be biosample.models.Submission.id
475
        res = self.my_task.run(usi_submission_id=self.usi_submission_id)
476
477
        self.common_test(res, "Submitted to biosample", SUBMITTED)
478
479
    def test_issues_with_api(self):
480
        """Test errors with submission API"""
481
482
        # Set a side effect on the patched methods
483
        # so that they raise the errors we want.
484
        self.mock_add.side_effect = ConnectionError()
485
486
        # call task. No retries with issues at EBI
487
        res = self.my_task.run(usi_submission_id=self.usi_submission_id)
488
489
        # this is the message I want
490
        message = "Errors in EBI API endpoints. Please try again later"
491
492
        # assert anyway a success
493
        self.common_test(res, message, READY)
494
495
    def test_token_expired(self):
496
        """Testing token expiring during a submission"""
497
498
        # simulating a token expiring during a submission
499
        self.mock_add.side_effect = RuntimeError("Your token is expired")
500
501
        # calling task
502
        res = self.my_task.run(usi_submission_id=self.usi_submission_id)
503
504
        message = (
505
            "Your token is expired: please submit again to resume submission")
506
507
        # assert anyway a success
508
        self.common_test(res, message, READY)
509
510
    def test_unmanaged(self):
511
        """Testing unmanaged Exception"""
512
513
        # simulating a token expiring during a submission
514
        self.mock_add.side_effect = Exception("Unmanaged")
515
516
        # calling task
517
        res = self.my_task.run(usi_submission_id=self.usi_submission_id)
518
519
        message = "Exception: Unmanaged"
520
521
        # assert anyway a success
522
        self.common_test(res, message, ERROR)
523
524
525
class SubmissionCompleteTaskTestCase(
526
        SubmissionFeaturesMixin, TaskFailureMixin, BaseMixin, TestCase):
527
528
    """Test class for SubmissionCompleteTask"""
529
530
    def setUp(self):
531
        # call Mixin method
532
        super().setUp()
533
534
        # setting tasks
535
        self.my_task = SubmissionCompleteTask()
536
537
        # these will be the tasks arguments, indipendently by status etc
538
        self.my_tasks_args = ([("success", 1), ("success", 2)], )
539
540
    def test_submission_complete(self):
541
        """test no issues after a submission"""
542
543
        # calling task
544
        res = self.my_task.run(
545
            *self.my_tasks_args,
546
            uid_submission_id=self.submission_id)
547
548
        # assert a success with data uploading
549
        self.assertEqual(res, "success")
550
551
        message = 'Submitted'
552
        notification_message = 'Waiting for biosample validation'
553
554
        # check status and messages
555
        self.submission_obj.refresh_from_db()
556
        self.assertEqual(self.submission_obj.status, SUBMITTED)
557
        self.assertEqual(self.submission_obj.message, notification_message)
558
559
        # calling a WebSocketMixin method
560
        self.check_message(message, notification_message)
561