Completed
Pull Request — master (#44)
by Paolo
11:03 queued 04:44
created

FetchWithErrorsTestCase.common_tests()   A

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
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 Tue Oct  9 14:51:13 2018
5
6
@author: Paolo Cozzi <[email protected]>
7
"""
8
9
from pytest import raises
10
from collections import Counter
11
from unittest.mock import patch, Mock
12
from datetime import timedelta
13
14
from celery.exceptions import Retry
15
16
from django.test import TestCase
17
from django.core import mail
18
from django.utils import timezone
19
20
from common.constants import (
21
    LOADED, ERROR, READY, NEED_REVISION, SUBMITTED, COMPLETED)
22
from common.tests import WebSocketMixin
23
from image_app.models import Submission, Name
24
25
from ..tasks.retrieval import FetchStatusTask, FetchStatusHelper
26
from ..models import ManagedTeam, Submission as USISubmission
27
28
29
class FetchMixin():
30
    """Mixin for fetching status"""
31
32
    fixtures = [
33
        'biosample/account',
34
        'biosample/managedteam',
35
        'image_app/dictcountry',
36
        'image_app/dictrole',
37
        'image_app/organization',
38
        'image_app/submission',
39
        'image_app/user'
40
    ]
41
42
    @classmethod
43
    def setUpClass(cls):
44
        # calling my base class setup
45
        super().setUpClass()
46
47
        unmanaged = ManagedTeam.objects.get(pk=2)
48
        unmanaged.delete()
49
50
        # starting mocked objects
51
        cls.mock_root_patcher = patch('pyUSIrest.client.Root')
52
        cls.mock_root = cls.mock_root_patcher.start()
53
54
        cls.mock_auth_patcher = patch('biosample.helpers.Auth')
55
        cls.mock_auth = cls.mock_auth_patcher.start()
56
57
    @classmethod
58
    def tearDownClass(cls):
59
        cls.mock_root_patcher.stop()
60
        cls.mock_auth_patcher.stop()
61
62
        # calling base method
63
        super().tearDownClass()
64
65
    def setUp(self):
66
        # calling my base setup
67
        super().setUp()
68
69
        # get a submission object
70
        self.submission_obj = Submission.objects.get(pk=1)
71
72
        # set a status which I can fetch_status
73
        self.submission_obj.status = SUBMITTED
74
        self.submission_obj.save()
75
76
        # set status for names, like submittask does. Only sample not unknown
77
        # are submitted
78
        self.name_qs = Name.objects.exclude(name__contains="unknown")
79
        self.name_qs.update(status=SUBMITTED)
80
81
        # count number of names in UID for such submission (exclude
82
        # unknown animals)
83
        self.n_to_submit = self.name_qs.count()
84
85
        # track submission ID
86
        self.submission_obj_id = self.submission_obj.id
87
88
        # start root object
89
        self.my_root = self.mock_root.return_value
90
91
92
class FetchStatusHelperMixin(FetchMixin):
93
    """Test class for FetchStatusHelper"""
94
95
    fixtures = [
96
        'biosample/account',
97
        'biosample/managedteam',
98
        'biosample/submission',
99
        'biosample/submissiondata',
100
        'image_app/animal',
101
        'image_app/dictbreed',
102
        'image_app/dictcountry',
103
        'image_app/dictrole',
104
        'image_app/dictsex',
105
        'image_app/dictspecie',
106
        'image_app/dictstage',
107
        'image_app/dictuberon',
108
        'image_app/name',
109
        'image_app/organization',
110
        'image_app/publication',
111
        'image_app/sample',
112
        'image_app/submission',
113
        'image_app/user'
114
    ]
115
116
    def setUp(self):
117
        # calling my base setup
118
        super().setUp()
119
120
        # define a biosample submission object
121
        self.my_submission = Mock()
122
        self.my_submission.name = "test-fetch"
123
124
        # passing submission to Mocked Root
125
        self.my_root.get_submission_by_name.return_value = self.my_submission
126
127
        # get a biosample.model.Submission and update object
128
        self.usi_submission = USISubmission.objects.get(pk=1)
129
        self.usi_submission.usi_submission_name = self.my_submission.name
130
        self.usi_submission.status = SUBMITTED
131
        self.usi_submission.save()
132
133
        # ok setup the object
134
        self.status_helper = FetchStatusHelper(self.usi_submission)
135
136
        # track names
137
        self.animal_name = Name.objects.get(pk=3)
138
        self.sample_name = Name.objects.get(pk=4)
139
140
    def common_tests(self):
141
        """Assert stuff for each test"""
142
143
        # call stuff
144
        self.status_helper.check_submission_status()
145
146
        # UID submission status remain the same
147
        self.submission_obj.refresh_from_db()
148
        self.assertEqual(self.submission_obj.status, SUBMITTED)
149
150
        self.assertTrue(self.mock_auth.called)
151
        self.assertTrue(self.mock_root.called)
152
        self.assertTrue(self.my_root.get_submission_by_name.called)
153
154
155
class FetchCompletedTestCase(FetchStatusHelperMixin, TestCase):
156
    """a completed submission with two samples"""
157
158
    def setUp(self):
159
        # calling my base setup
160
        super().setUp()
161
162
        # a completed submission with two samples
163
        self.my_submission.status = 'Completed'
164
165
    def test_fetch_status(self):
166
        """Test fetch status for a complete submission"""
167
168
        # Add samples
169
        my_sample1 = Mock()
170
        my_sample1.name = "test-animal"
171
        my_sample1.alias = "IMAGEA000000001"
172
        my_sample1.accession = "SAMEA0000001"
173
        my_sample2 = Mock()
174
        my_sample2.name = "test-sample"
175
        my_sample2.alias = "IMAGES000000001"
176
        my_sample2.accession = "SAMEA0000002"
177
        self.my_submission.get_samples.return_value = [my_sample1, my_sample2]
178
179
        # assert auth, root and get_submission by name called
180
        self.common_tests()
181
182
        # USI submission status changed
183
        self.usi_submission.refresh_from_db()
184
        self.assertEqual(self.usi_submission.status, COMPLETED)
185
186
        # check name status changed
187
        qs = Name.objects.filter(status=COMPLETED)
188
        self.assertEqual(len(qs), 2)
189
190
        # fetch two name objects
191
        self.animal_name.refresh_from_db()
192
        self.assertEqual(self.animal_name.biosample_id, "SAMEA0000001")
193
194
        self.sample_name.refresh_from_db()
195
        self.assertEqual(self.sample_name.biosample_id, "SAMEA0000002")
196
197
    def test_fetch_status_no_accession(self):
198
        """Test fetch status for a submission which doens't send accession
199
        no updates in such case"""
200
201
        # Add samples
202
        my_sample1 = Mock()
203
        my_sample1.name = "test-animal"
204
        my_sample1.alias = "IMAGEA000000001"
205
        my_sample1.accession = None
206
        my_sample2 = Mock()
207
        my_sample2.name = "test-sample"
208
        my_sample2.alias = "IMAGES000000001"
209
        my_sample2.accession = None
210
        self.my_submission.get_samples.return_value = [my_sample1, my_sample2]
211
212
        # assert auth, root and get_submission by name called
213
        self.common_tests()
214
215
        # USI submission status didn't change
216
        self.usi_submission.refresh_from_db()
217
        self.assertEqual(self.usi_submission.status, SUBMITTED)
218
219
        # check name status didn't changed
220
        qs = Name.objects.filter(status=SUBMITTED)
221
        self.assertEqual(len(qs), self.n_to_submit)
222
223
224
class FetchWithErrorsTestCase(FetchStatusHelperMixin, TestCase):
225
    """Test a submission with errors for biosample"""
226
227
    def setUp(self):
228
        # calling my base setup
229
        super().setUp()
230
231
        # a draft submission with errors
232
        self.my_submission.status = 'Draft'
233
        self.my_submission.has_errors.return_value = Counter(
234
            {True: 1, False: 1})
235
        self.my_submission.get_status.return_value = Counter({'Complete': 2})
236
237
        # Add samples. Suppose that first failed, second is ok
238
        my_validation_result1 = Mock()
239
        my_validation_result1.errorMessages = {
240
            'Ena': [
241
                'a sample message',
242
            ]
243
        }
244
245
        my_sample1 = Mock()
246
        my_sample1.name = "test-animal"
247
        my_sample1.alias = "IMAGEA000000001"
248
        my_sample1.has_errors.return_value = True
249
        my_sample1.get_validation_result.return_value = my_validation_result1
250
251
        # sample2 is ok
252
        my_validation_result2 = Mock()
253
        my_validation_result2.errorMessages = None
254
255
        my_sample2 = Mock()
256
        my_sample2.name = "test-sample"
257
        my_sample2.alias = "IMAGES000000001"
258
        my_sample2.has_errors.return_value = False
259
        my_sample2.get_validation_result.return_value = my_validation_result2
260
261
        # simulate that IMAGEA000000001 has errors
262
        self.my_submission.get_samples.return_value = [my_sample1, my_sample2]
263
264
        # track other objects
265
        self.my_sample1 = my_sample1
266
        self.my_sample2 = my_sample2
267
268
    def common_tests(self):
269
        # assert auth, root and get_submission by name called
270
        super().common_tests()
271
272
        # assert custom mock attributes called
273
        self.assertTrue(self.my_sample1.has_errors.called)
274
        self.assertTrue(self.my_sample1.get_validation_result.called)
275
276
        # if sample has no errors, no all methods will be called
277
        self.assertTrue(self.my_sample2.has_errors.called)
278
        self.assertFalse(self.my_sample2.get_validation_result.called)
279
280
    def test_fetch_status(self):
281
        # assert tmock methods called
282
        self.common_tests()
283
284
        # USI submission changed
285
        self.usi_submission.refresh_from_db()
286
        self.assertEqual(self.usi_submission.status, NEED_REVISION)
287
288
        # check name status changed only for animal (not sample)
289
        self.animal_name.refresh_from_db()
290
        self.assertEqual(self.animal_name.status, NEED_REVISION)
291
292
        self.sample_name.refresh_from_db()
293
        self.assertEqual(self.sample_name.status, SUBMITTED)
294
295
296
class FetchDraftTestCase(FetchStatusHelperMixin, TestCase):
297
    """a draft submission without errors"""
298
299
    def common_tests(self):
300
        # assert auth, root and get_submission by name called
301
        super().common_tests()
302
303
        # USI submission status didn't change
304
        self.usi_submission.refresh_from_db()
305
        self.assertEqual(self.usi_submission.status, SUBMITTED)
306
307
    def test_fetch_status(self):
308
        # a draft submission without errors
309
        self.my_submission.status = 'Draft'
310
        self.my_submission.has_errors.return_value = Counter({False: 2})
311
        self.my_submission.get_status.return_value = Counter({'Complete': 2})
312
313
        # assert mock methods called
314
        self.common_tests()
315
316
        # testing a finalized biosample condition
317
        self.assertTrue(self.my_submission.finalize.called)
318
319
    def test_fetch_status_pending(self):
320
        """Testing status with pending validation"""
321
322
        # a draft submission without errors
323
        self.my_submission.status = 'Draft'
324
        self.my_submission.has_errors.return_value = Counter({False: 2})
325
        self.my_submission.get_status.return_value = Counter({'Pending': 2})
326
327
        # assert mock methods called
328
        self.common_tests()
329
330
        # testing a not finalized biosample condition
331
        self.assertFalse(self.my_submission.finalize.called)
332
333
    def test_fetch_status_submitted(self):
334
        """Testing status during biosample submission"""
335
336
        # a draft submission without errors
337
        self.my_submission.status = 'Submitted'
338
        self.my_submission.has_errors.return_value = Counter({False: 2})
339
        self.my_submission.get_status.return_value = Counter({'Complete': 2})
340
341
        # assert mock methods called
342
        self.common_tests()
343
344
        # testing a not finalized biosample condition
345
        self.assertFalse(self.my_submission.finalize.called)
346
347
348
class FetchLongStatusTestCase(FetchStatusHelperMixin, TestCase):
349
    """A submission wich remain in the same status for a long time"""
350
351
    def setUp(self):
352
        # calling my base setup
353
        super().setUp()
354
355
        # make "now" 2 months ago
356
        testtime = timezone.now() - timedelta(days=60)
357
358
        # https://devblog.kogan.com/blog/testing-auto-now-datetime-fields-in-django
359
        with patch('django.utils.timezone.now') as mock_now:
360
            mock_now.return_value = testtime
361
362
            # update submission updated time with an older date than now
363
            self.usi_submission.updated_at = testtime
364
            self.usi_submission.save()
365
366
    def common_tests(self):
367
        # assert auth, root and get_submission by name called
368
        super().common_tests()
369
370
        # biosample.models.Submission status changed
371
        self.assertEqual(self.usi_submission.status, ERROR)
372
        self.assertIn(
373
            "Biosample submission '{}' remained with the same status".format(
374
                self.my_submission.name),
375
            self.usi_submission.message
376
        )
377
378
    def test_error_in_submitted_status(self):
379
        # a still running submission
380
        self.my_submission.status = 'Submitted'
381
382
        # assert mock methods called
383
        self.common_tests()
384
385
    def test_error_in_draft_status(self):
386
        # a still running submission
387
        self.my_submission.status = 'Draft'
388
389
        # assert mock methods called
390
        self.common_tests()
391
392
393
class FetchUnsupportedStatusTestCase(FetchMixin, TestCase):
394
    """A submission object with a status I can ignore. Task will exit
395
    immediatey"""
396
397
    def setUp(self):
398
        # calling my base setup
399
        super().setUp()
400
401
        # define my task
402
        self.my_task = FetchStatusTask()
403
404
        # change lock_id (useful when running test during cron)
405
        self.my_task.lock_id = "test-FetchStatusTask"
406
407
    def update_status(self, status):
408
        # change status
409
        self.submission_obj.status = status
410
        self.submission_obj.save()
411
412
    # override FetchMixing methods
413
    def common_tests(self, status):
414
        # update submission status
415
        self.update_status(status)
416
417
        # NOTE that I'm calling the function directly, without delay
418
        # (AsyncResult). I've patched the time consuming task
419
        res = self.my_task.run()
420
421
        # assert a success with data uploading
422
        self.assertEqual(res, "success")
423
424
        self.assertFalse(self.mock_auth.called)
425
        self.assertFalse(self.mock_root.called)
426
        self.assertFalse(self.my_root.get_submission_by_name.called)
427
428
        # assert status for submissions
429
        self.submission_obj.refresh_from_db()
430
        self.assertEqual(self.submission_obj.status, status)
431
432
    def test_loaded(self):
433
        """Test fecth_status with a loaded submission"""
434
435
        # assert task and mock methods called
436
        self.common_tests(LOADED)
437
438
    def test_need_revision(self):
439
        """Test fecth_status with a need_revision submission"""
440
441
        # assert task and mock methods called
442
        self.common_tests(NEED_REVISION)
443
444
    def test_ready(self):
445
        """Test fecth_status with a ready submission"""
446
447
        # assert task and mock methods called
448
        self.common_tests(READY)
449
450
    def test_completed(self):
451
        """Test fecth_status with a completed submission"""
452
453
        # assert task and mock methods called
454
        self.common_tests(COMPLETED)
455