Passed
Pull Request — master (#44)
by Paolo
05:38
created

SplitSubmissionTaskTestCase.test_sample_already_in_submission()   A

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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