Passed
Pull Request — master (#38)
by Paolo
01:31
created

validation.tests.test_tasks   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 1015
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 41
eloc 597
dl 0
loc 1015
rs 9.1199
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
B ValidateTaskTest.test_unsupported_status() 0 60 1
B ValidateTaskTest.test_validate_submission_warnings() 0 101 2
A ValidateUpdatedSubmissionStatusTest.test_update_status_error() 0 10 2
A ValidateSubmissionTest.test_check_valid_statuses() 0 13 1
A ValidateSubmissionTest.test_create_validation_summary() 0 31 1
A ValidateTaskTest.test_issues_with_ruleset() 0 43 1
A ValidateUpdatedSubmissionStatusTest.test_update_status_pass() 0 15 2
A ValidateSubmissionMixin.setUp() 0 29 1
A ValidateTaskTest.setUp() 0 8 1
A CustomWebSocketMixin.check_async_called() 0 31 2
A ValidateSubmissionStatusTest.setUp() 0 11 1
B ValidateTaskTest.test_validate_submission() 0 67 2
A ValidateUpdatedSubmissionStatusTest.update_status() 0 33 3
A ValidateSubmissionTest.setUp() 0 7 1
A ValidateTaskTest.tearDown() 0 6 1
A ValidateSubmissionStatusTest.test_update_status_error() 0 6 1
A ValidateTaskTest.test_validate_retry() 0 23 2
B ValidateTaskTest.test_validate_submission_wrong_json() 0 80 2
A ValidateTaskTest.test_issues_with_ontologychache() 0 43 1
A ValidateTaskTest.test_on_failure() 0 34 1
A ValidateSubmissionStatusTest.test_update_status_pass() 0 6 1
A ValidateTaskTest.test_issues_with_api() 0 46 1
A ValidateSubmissionTest.test_has_keys() 0 15 1
A CustomWebSocketMixin.check_async_not_called() 0 6 1
A ValidateUpdatedSubmissionStatusTest.test_update_status_warning() 0 13 2
B ValidateTaskTest.test_validate_submission_errors() 0 107 3
A ValidateSubmissionStatusTest.check_status() 0 31 1
A ValidateSubmissionStatusTest.test_update_status_warning() 0 6 1
A ValidateUpdatedSubmissionStatusTest.setUp() 0 23 1

How to fix   Complexity   

Complexity

Complex classes like validation.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 Fri Oct  5 11:39:21 2018
5
6
@author: Paolo Cozzi <[email protected]>
7
"""
8
9
import json
10
11
from collections import Counter
12
from unittest.mock import patch, Mock
13
from billiard.einfo import ExceptionInfo
14
from celery.exceptions import Retry
15
from pytest import raises
16
from image_validation.ValidationResult import (
17
    ValidationResultColumn, ValidationResultRecord)
18
19
from django.core import mail
20
from django.test import TestCase
21
22
from common.constants import LOADED, ERROR, READY, NEED_REVISION, COMPLETED
23
from common.tests import WebSocketMixin
24
from common.tests import PersonMixinTestCase
25
from image_app.models import Submission, Person, Name, Animal, Sample
26
27
from ..tasks import ValidateTask, ValidationError, ValidateSubmission
28
from ..helpers import OntologyCacheError, RulesetError
29
from ..models import ValidationSummary
30
from .common import PickableMock, MetaDataValidationTestMixin
31
32
33
class ValidateSubmissionMixin(
34
        PersonMixinTestCase, MetaDataValidationTestMixin):
35
    """A mixin to define common stuff for testing data validation"""
36
37
    # an attribute for PersonMixinTestCase
38
    person = Person
39
40
    fixtures = [
41
        'image_app/animal',
42
        'image_app/dictbreed',
43
        'image_app/dictcountry',
44
        'image_app/dictrole',
45
        'image_app/dictsex',
46
        'image_app/dictspecie',
47
        'image_app/dictstage',
48
        'image_app/dictuberon',
49
        'image_app/name',
50
        'image_app/ontology',
51
        'image_app/organization',
52
        'image_app/publication',
53
        'image_app/sample',
54
        'image_app/submission',
55
        'image_app/user',
56
        "validation/validationresult",
57
        'validation/validationsummary'
58
    ]
59
60
    def setUp(self):
61
        # calling base methods
62
        super().setUp()
63
64
        # get a submission object
65
        self.submission = Submission.objects.get(pk=1)
66
67
        # set a status which I can validate
68
        self.submission.status = LOADED
69
        self.submission.save()
70
71
        # track submission ID
72
        self.submission_id = self.submission.id
73
74
        # track names
75
        self.name_qs = Name.objects.exclude(name__contains="unknown")
76
77
        # track animal and samples
78
        self.animal_qs = Animal.objects.filter(
79
            name__submission=self.submission)
80
        self.sample_qs = Sample.objects.filter(
81
            name__submission=self.submission)
82
83
        # track animal and samples count
84
        self.n_animals = self.animal_qs.count()
85
        self.n_samples = self.sample_qs.count()
86
87
        # setting tasks
88
        self.my_task = ValidateTask()
89
90
91
class CustomWebSocketMixin(WebSocketMixin):
92
    """Override setUp to mock websocket objects"""
93
94
    def check_async_called(
95
            self, message, notification_message, validation_message=None,
96
            pk=1):
97
98
        """Check django channels async messages called"""
99
100
        # defining default validation message.
101
        if not validation_message:
102
            animal_qs = Animal.objects.filter(
103
                name__submission=self.submission)
104
            sample_qs = Sample.objects.filter(
105
                name__submission=self.submission)
106
107
            validation_message = {
108
                'animals': self.n_animals,
109
                'samples': self.n_samples,
110
                'animal_unkn': animal_qs.filter(
111
                    name__validationresult__isnull=True).count(),
112
                'sample_unkn': sample_qs.filter(
113
                    name__validationresult__isnull=True).count(),
114
                'animal_issues': 0,
115
                'sample_issues': 0
116
            }
117
118
        self.assertEqual(self.asyncio_mock.call_count, 1)
119
        self.assertEqual(self.run_until.run_until_complete.call_count, 1)
120
        self.assertEqual(self.send_msg_ws.call_count, 1)
121
        self.send_msg_ws.assert_called_with(
122
            {'message': message,
123
             'notification_message': notification_message,
124
             'validation_message': validation_message}, pk)
125
126
    def check_async_not_called(self):
127
        """Check django channels async messages not called"""
128
129
        self.assertEqual(self.asyncio_mock.call_count, 0)
130
        self.assertEqual(self.run_until.run_until_complete.call_count, 0)
131
        self.assertEqual(self.send_msg_ws.call_count, 0)
132
133
134
class ValidateSubmissionTest(ValidateSubmissionMixin, TestCase):
135
136
    def setUp(self):
137
        # calling base methods
138
        super().setUp()
139
140
        # get a submission data object (with no ruleset)
141
        self.submission_data = ValidateSubmission(
142
            self.submission, ruleset=None)
143
144
    def test_check_valid_statuses(self):
145
        """test validation supporting statuses"""
146
147
        self.assertTrue(self.submission_data.check_valid_statuses())
148
149
        # set a fake status
150
        self.submission_data.statuses_animals['foo'] = 1
151
        self.assertFalse(self.submission_data.check_valid_statuses())
152
153
        # reset and set sample status status
154
        self.submission_data.statuses_animals = Counter()
155
        self.submission_data.statuses_samples['foo'] = 1
156
        self.assertFalse(self.submission_data.check_valid_statuses())
157
158
    def test_has_keys(self):
159
        """Test has error, warning, JSON in validation tests"""
160
161
        self.assertFalse(self.submission_data.has_errors_in_rules())
162
        self.assertFalse(self.submission_data.has_warnings_in_rules())
163
        self.assertFalse(self.submission_data.has_errors_in_json())
164
165
        # set a fake status
166
        self.submission_data.statuses_animals['JSON'] = 1
167
        self.submission_data.statuses_animals['Error'] = 1
168
        self.submission_data.statuses_samples['Warning'] = 1
169
170
        self.assertTrue(self.submission_data.has_errors_in_rules())
171
        self.assertTrue(self.submission_data.has_warnings_in_rules())
172
        self.assertTrue(self.submission_data.has_errors_in_json())
173
174
    def test_create_validation_summary(self):
175
        """Create and set validation summary object"""
176
177
        # get valdiationsummary objects
178
        summary_qs = ValidationSummary.objects.filter(
179
            submission=self.submission)
180
181
        # wipe out validationsummary objects
182
        summary_qs.delete()
183
184
        # set up messages
185
        self.submission_data.statuses_animals['JSON'] = 1
186
        self.submission_data.statuses_animals['Error'] = 1
187
        self.submission_data.statuses_samples['Warning'] = 1
188
189
        self.submission_data.messages_animals['test json'] = 1
190
        self.submission_data.messages_animals['test error'] = 1
191
        self.submission_data.messages_samples['test warning'] = 1
192
193
        # call function
194
        self.submission_data.create_validation_summary()
195
196
        # assert I have two objects
197
        self.assertTrue(summary_qs.count(), 2)
198
199
        # get animal vs
200
        animal_summary = summary_qs.get(type="animal")
201
        self.assertEqual(animal_summary.all_count, self.n_animals)
202
203
        sample_summary = summary_qs.get(type="sample")
204
        self.assertEqual(sample_summary.all_count, self.n_samples)
205
206
207
class ValidateTaskTest(
208
        CustomWebSocketMixin, ValidateSubmissionMixin, TestCase):
209
210
    def setUp(self):
211
        # calling base methods
212
        super().setUp()
213
214
        # mocking task retry
215
        self.validate_retry_patcher = patch(
216
            "validation.tasks.ValidateTask.retry")
217
        self.validate_retry = self.validate_retry_patcher.start()
218
219
    def tearDown(self):
220
        # stopping my mock objects
221
        self.validate_retry_patcher.stop()
222
223
        # calling base methods
224
        super().tearDown()
225
226
    def test_on_failure(self):
227
        """Testing on failure methods"""
228
229
        exc = Exception("Test")
230
        task_id = "test_task_id"
231
        args = [self.submission_id]
232
        kwargs = {}
233
        einfo = ExceptionInfo
234
235
        # call on_failure method
236
        self.my_task.on_failure(exc, task_id, args, kwargs, einfo)
237
238
        # check submission status and message
239
        submission = Submission.objects.get(pk=self.submission_id)
240
241
        # check submission.state changed
242
        self.assertEqual(submission.status, ERROR)
243
        self.assertEqual(
244
            submission.message,
245
            "Unknown error in validation - Test")
246
247
        # test email sent
248
        self.assertGreater(len(mail.outbox), 1)
249
250
        # read email
251
        email = mail.outbox[0]
252
253
        self.assertEqual(
254
            "Error in IMAGE Validation: Unknown error in validation - Test",
255
            email.subject)
256
257
        self.check_async_called(
258
            'Error',
259
            'Unknown error in validation - Test')
260
261
    @patch("validation.tasks.ValidateSubmission.validate_model")
262
    def test_validate_retry(self, my_validate):
263
        """Test validation with retry"""
264
265
        # Set a side effect on the patched methods
266
        # so that they raise the errors we want.
267
        self.validate_retry.side_effect = Retry()
268
269
        my_validate.side_effect = Exception()
270
271
        with raises(Retry):
272
            self.my_task.run(submission_id=self.submission_id)
273
274
        # asserting mock validate_model called
275
        self.assertTrue(my_validate.called)
276
277
        # asserting my mock objects
278
        self.assertTrue(self.read_in_ruleset.called)
279
        self.assertTrue(self.check_ruleset.called)
280
        self.assertTrue(self.validate_retry.called)
281
282
        # asserting django channels not called
283
        self.check_async_not_called()
284
285
    @patch("validation.tasks.ValidateSubmission.validate_model")
286
    def test_issues_with_api(self, my_validate):
287
        """Test errors with validation API"""
288
289
        my_validate.side_effect = json.decoder.JSONDecodeError(
290
            msg="test", doc="test", pos=1)
291
292
        # call task. No retries with issues at EBI
293
        res = self.my_task.run(submission_id=self.submission_id)
294
295
        # assert a success with validation taks
296
        self.assertEqual(res, "success")
297
298
        # check submission status and message
299
        self.submission.refresh_from_db()
300
301
        # this is the message I want
302
        message = "Errors in EBI API endpoints. Please try again later"
303
304
        # check submission.status changed to NEED_REVISION
305
        self.assertEqual(self.submission.status, LOADED)
306
        self.assertIn(
307
            message,
308
            self.submission.message)
309
310
        # test email sent
311
        self.assertEqual(len(mail.outbox), 1)
312
313
        # read email
314
        email = mail.outbox[0]
315
316
        self.assertEqual(
317
            "Error in IMAGE Validation: %s" % message,
318
            email.subject)
319
320
        # asserting mock validate_model called
321
        self.assertTrue(my_validate.called)
322
323
        # asserting my mock objects
324
        self.assertTrue(self.read_in_ruleset.called)
325
        self.assertTrue(self.check_ruleset.called)
326
        self.assertFalse(self.validate_retry.called)
327
328
        self.check_async_called(
329
            'Loaded',
330
            'Errors in EBI API endpoints. Please try again later'
331
        )
332
333
    def test_issues_with_ontologychache(self):
334
        """Test errors with validation API when loading OntologyCache
335
        objects"""
336
337
        # return a custom exception with read_in_ruleset
338
        self.read_in_ruleset.side_effect = OntologyCacheError("test exception")
339
340
        # call task. No retries with issues at EBI
341
        res = self.my_task.run(submission_id=self.submission_id)
342
343
        # assert a success with validation taks
344
        self.assertEqual(res, "success")
345
346
        # check submission status and message
347
        self.submission.refresh_from_db()
348
349
        # this is the message I want
350
        message = "Errors in EBI API endpoints. Please try again later"
351
352
        # check submission.status changed to LOADED
353
        self.assertEqual(self.submission.status, LOADED)
354
        self.assertIn(
355
            message,
356
            self.submission.message)
357
358
        # test email sent
359
        self.assertEqual(len(mail.outbox), 1)
360
361
        # read email
362
        email = mail.outbox[0]
363
364
        self.assertEqual(
365
            "Error in IMAGE Validation: %s" % message,
366
            email.subject)
367
368
        # asserting my mock objects
369
        self.assertTrue(self.read_in_ruleset.called)
370
        self.assertFalse(self.check_ruleset.called)
371
        self.assertFalse(self.validate_retry.called)
372
373
        self.check_async_called(
374
            'Loaded',
375
            'Errors in EBI API endpoints. Please try again later')
376
377
    def test_issues_with_ruleset(self):
378
        """Test errors with ruleset"""
379
380
        # return a custom exception with read_in_ruleset
381
        self.read_in_ruleset.side_effect = RulesetError(["test exception"])
382
383
        # call task. No retries with issues at EBI
384
        res = self.my_task.run(submission_id=self.submission_id)
385
386
        # assert a success with validation taks
387
        self.assertEqual(res, "success")
388
389
        # check submission status and message
390
        self.submission.refresh_from_db()
391
392
        # this is the message I want
393
        message = (
394
            "Error in IMAGE-metadata ruleset. Please inform InjectTool team")
395
396
        # check submission.status changed to ERROR
397
        self.assertEqual(self.submission.status, ERROR)
398
        self.assertIn(
399
            message,
400
            self.submission.message)
401
402
        # test email sent
403
        self.assertGreater(len(mail.outbox), 1)
404
405
        # read email
406
        email = mail.outbox[0]
407
408
        self.assertEqual(
409
            "Error in IMAGE Validation: %s" % message,
410
            email.subject)
411
412
        # asserting my mock objects
413
        self.assertTrue(self.read_in_ruleset.called)
414
        self.assertFalse(self.check_ruleset.called)
415
        self.assertFalse(self.validate_retry.called)
416
417
        self.check_async_called(
418
            'Error',
419
            'Error in IMAGE-metadata ruleset. Please inform InjectTool team')
420
421
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
422
    @patch("validation.tasks.MetaDataValidation.validate")
423
    def test_validate_submission(
424
            self, my_validate, my_check):
425
        """Test a valid submission. Simulate image_validation result and
426
        status changes"""
427
428
        # setting check_usi_structure result. now is a ValidateResultRecord
429
        result = PickableMock()
430
        result.get_overall_status.return_value = "Pass"
431
        result.get_messages.return_value = []
432
        my_check.return_value = result
433
434
        # setting a return value for check_with_ruleset
435
        validation_result = Mock()
436
        validation_result.get_overall_status.return_value = "Pass"
437
        validation_result.get_messages.return_value = ["A message"]
438
        result_set = Mock()
439
        result_set.get_comparable_str.return_value = "A message"
440
        validation_result.result_set = [result_set]
441
        my_validate.return_value = validation_result
442
443
        # NOTE that I'm calling the function directly, without delay
444
        # (AsyncResult). I've patched the time consuming task
445
        res = self.my_task.run(submission_id=self.submission_id)
446
447
        # assert a success with validation taks
448
        self.assertEqual(res, "success")
449
450
        # check submission status and message
451
        self.submission.refresh_from_db()
452
453
        # check submission.state changed
454
        self.assertEqual(self.submission.status, READY)
455
        self.assertEqual(
456
            self.submission.message,
457
            "Submission validated with success")
458
459
        # check Names (they are all ok)
460
        for name in self.name_qs:
461
            self.assertEqual(name.status, READY)
462
463
            # test for model message (usi_results)
464
            self.assertEqual(
465
                name.validationresult.messages, ["A message"])
466
            self.assertEqual(name.validationresult.status, "Pass")
467
468
        # assert validation functions called
469
        self.assertTrue(my_check.called)
470
        self.assertTrue(my_validate.called)
471
472
        # asserting my mock objects
473
        self.assertTrue(self.read_in_ruleset.called)
474
        self.assertTrue(self.check_ruleset.called)
475
        self.assertFalse(self.validate_retry.called)
476
477
        # no unknown and sample with issues
478
        validation_message = {
479
            'animals': self.n_animals,
480
            'samples': self.n_samples,
481
            'animal_unkn': 0, 'sample_unkn': 0,
482
            'animal_issues': 0, 'sample_issues': 0}
483
484
        self.check_async_called(
485
            'Ready',
486
            'Submission validated with success',
487
            validation_message=validation_message
488
        )
489
490
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
491
    @patch("validation.tasks.MetaDataValidation.validate")
492
    def test_validate_submission_wrong_json(
493
            self, my_validate, my_check):
494
        """Test an error in JSON format"""
495
496
        # setting check_usi_structure result. now is a ValidateResultRecord
497
        messages = [
498
            ('Wrong JSON structure: no title field for record with '
499
             'alias as animal_1'),
500
            ('Wrong JSON structure: the values for attribute Person '
501
             'role needs to be in an array for record animal_1')
502
        ]
503
504
        usi_result = ValidationResultRecord("animal_1")
505
        usi_result.add_validation_result_column(
506
            ValidationResultColumn(
507
                "error",
508
                messages[0],
509
                "animal_1",
510
                "")
511
        )
512
        usi_result.add_validation_result_column(
513
            ValidationResultColumn(
514
                "error",
515
                messages[1],
516
                "animal_1",
517
                "")
518
        )
519
520
        my_check.return_value = usi_result
521
522
        # setting a return value for check_with_ruleset
523
        rule_result = Mock()
524
        rule_result.get_overall_status.return_value = "Pass"
525
        my_validate.return_value = rule_result
526
527
        # call task
528
        res = self.my_task.run(submission_id=self.submission_id)
529
530
        # assert a success with validation taks
531
        self.assertEqual(res, "success")
532
533
        # check submission status and message
534
        self.submission.refresh_from_db()
535
536
        # check submission.state changed
537
        self.assertEqual(self.submission.status, NEED_REVISION)
538
        self.assertIn(
539
            "Wrong JSON structure",
540
            self.submission.message)
541
542
        # check Names (they require revisions)
543
        for name in self.name_qs:
544
            self.assertEqual(name.status, NEED_REVISION)
545
546
            # test for model message (usi_results)
547
            self.assertEqual(
548
                name.validationresult.messages, usi_result.get_messages())
549
            self.assertEqual(
550
                name.validationresult.status, usi_result.get_overall_status())
551
552
        # if JSON is not valid, I don't check for ruleset
553
        self.assertTrue(my_check.called)
554
        self.assertFalse(my_validate.called)
555
556
        # asserting my mock objects
557
        self.assertTrue(self.read_in_ruleset.called)
558
        self.assertTrue(self.check_ruleset.called)
559
        self.assertFalse(self.validate_retry.called)
560
561
        # all sample and animals have issues
562
        self.check_async_called(
563
            'Need Revision',
564
            'Validation got errors: Wrong JSON structure',
565
            {'animals': self.n_animals, 'samples': self.n_samples,
566
             'animal_unkn': 0, 'sample_unkn': 0,
567
             'animal_issues': self.n_animals,
568
             'sample_issues': self.n_samples},
569
            1)
570
571
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
572
    @patch("validation.tasks.MetaDataValidation.validate")
573
    def test_unsupported_status(
574
            self, my_validate, my_check):
575
        """This test will ensure that image_validation ValidationResultRecord
576
        still support the same statuses"""
577
578
        # setting check_usi_structure result. now is a ValidateResultRecord
579
        result = PickableMock()
580
        result.get_overall_status.return_value = "Pass"
581
        result.get_messages.return_value = []
582
        my_check.return_value = result
583
584
        # setting a return value for check_with_ruleset
585
        rule_result = PickableMock()
586
        rule_result.get_overall_status.return_value = "A fake status"
587
        rule_result.get_messages.return_value = ["A fake message", ]
588
589
        result_set = Mock()
590
        result_set.get_comparable_str.return_value = "A fake message"
591
        rule_result.result_set = [result_set]
592
593
        my_validate.return_value = rule_result
594
595
        # call task
596
        self.assertRaisesRegex(
597
            ValidationError,
598
            "Unsupported validation status for submission",
599
            self.my_task.run,
600
            submission_id=self.submission_id)
601
602
        # check submission status and message
603
        self.submission.refresh_from_db()
604
605
        # check submission.state changed
606
        self.assertEqual(self.submission.status, ERROR)
607
        self.assertIn(
608
            "Unsupported validation status for submission",
609
            self.submission.message)
610
611
        # if JSON is not valid, I don't check for ruleset
612
        self.assertTrue(my_check.called)
613
        self.assertTrue(my_validate.called)
614
615
        # asserting my mock objects
616
        self.assertTrue(self.read_in_ruleset.called)
617
        self.assertTrue(self.check_ruleset.called)
618
        self.assertFalse(self.validate_retry.called)
619
620
        self.check_async_called(
621
            message='Error',
622
            notification_message=(
623
                "Validation got errors: Unsupported validation status "
624
                "for submission Cryoweb (United Kingdom, test)"),
625
            validation_message={
626
                'animals': self.n_animals, 'samples': self.n_samples,
627
                'animal_unkn': 0, 'sample_unkn': 0,
628
                'animal_issues': self.n_animals,
629
                'sample_issues': self.n_samples},
630
            pk=1)
631
632
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
633
    @patch("validation.tasks.MetaDataValidation.validate")
634
    def test_validate_submission_warnings(
635
            self, my_validate, my_check):
636
        """A submission with warnings is a READY submission"""
637
638
        # setting check_usi_structure result. now is a ValidateResultRecord
639
        result = PickableMock()
640
        result.get_overall_status.return_value = "Pass"
641
        result.get_messages.return_value = []
642
        my_check.return_value = result
643
644
        # setting a return value for check_with_ruleset
645
        result1 = ValidationResultRecord("animal_1")
646
        result1.add_validation_result_column(
647
            ValidationResultColumn(
648
                "warning",
649
                "warn message",
650
                "animal_1",
651
                "warn column")
652
        )
653
654
        result2 = ValidationResultRecord("animal_2")
655
        result2.add_validation_result_column(
656
            ValidationResultColumn(
657
                "pass",
658
                "a message",
659
                "animal_2",
660
                "")
661
        )
662
663
        result3 = ValidationResultRecord("animal_3")
664
        result3.add_validation_result_column(
665
            ValidationResultColumn(
666
                "pass",
667
                "a message",
668
                "animal_3",
669
                "")
670
        )
671
672
        result4 = ValidationResultRecord("sample_1")
673
        result4.add_validation_result_column(
674
            ValidationResultColumn(
675
                "pass",
676
                "a message",
677
                "sample_1",
678
                "")
679
        )
680
681
        # add results to result set
682
        responses = [result1, result2, result3, result4]
683
        my_validate.side_effect = responses
684
685
        # call task
686
        res = self.my_task.run(submission_id=self.submission_id)
687
688
        # assert a success with validation taks
689
        self.assertEqual(res, "success")
690
691
        # check submission status and message
692
        self.submission.refresh_from_db()
693
694
        # check submission.state changed
695
        self.assertEqual(self.submission.status, READY)
696
        self.assertIn(
697
            "Submission validated with some warnings",
698
            self.submission.message)
699
700
        # check Names (they are all ok)
701
        for i, name in enumerate(self.name_qs):
702
            # get the appropriate ValidationResultRecord
703
            result = responses[i]
704
705
            # all objects are ready for submissions
706
            self.assertEqual(name.status, READY)
707
708
            self.assertEqual(
709
                name.validationresult.messages,
710
                result.get_messages())
711
712
            self.assertEqual(
713
                name.validationresult.status,
714
                result.get_overall_status())
715
716
        # test for my methods called
717
        self.assertTrue(my_check.called)
718
        self.assertTrue(my_validate.called)
719
720
        # asserting my mock objects
721
        self.assertTrue(self.read_in_ruleset.called)
722
        self.assertTrue(self.check_ruleset.called)
723
        self.assertFalse(self.validate_retry.called)
724
725
        self.check_async_called(
726
            message='Ready',
727
            notification_message='Submission validated with some warnings',
728
            validation_message={
729
                'animals': self.n_animals, 'samples': self.n_samples,
730
                'animal_unkn': 0, 'sample_unkn': 0,
731
                'animal_issues': 0, 'sample_issues': 0},
732
            pk=1)
733
734
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
735
    @patch("validation.tasks.MetaDataValidation.validate")
736
    def test_validate_submission_errors(
737
            self, my_validate, my_check):
738
        """A submission with errors is a NEED_REVISION submission"""
739
740
        # setting check_usi_structure result. now is a ValidateResultRecord
741
        result = PickableMock()
742
        result.get_overall_status.return_value = "Pass"
743
        result.get_messages.return_value = []
744
        my_check.return_value = result
745
746
        # setting a return value for check_with_ruleset
747
        result1 = ValidationResultRecord("animal_1")
748
        result1.add_validation_result_column(
749
            ValidationResultColumn(
750
                "warning",
751
                "warn message",
752
                "animal_1",
753
                "warn column")
754
        )
755
756
        result2 = ValidationResultRecord("animal_2")
757
        result2.add_validation_result_column(
758
            ValidationResultColumn(
759
                "pass",
760
                "a message",
761
                "animal_2",
762
                "")
763
        )
764
765
        result3 = ValidationResultRecord("animal_3")
766
        result3.add_validation_result_column(
767
            ValidationResultColumn(
768
                "pass",
769
                "a message",
770
                "animal_3",
771
                "")
772
        )
773
774
        result4 = ValidationResultRecord("sample_1")
775
        result4.add_validation_result_column(
776
            ValidationResultColumn(
777
                "error",
778
                "error message",
779
                "sample_1",
780
                "error column")
781
        )
782
783
        # add results to result set
784
        responses = [result1, result2, result3, result4]
785
        my_validate.side_effect = responses
786
787
        # call task
788
        res = self.my_task.run(submission_id=self.submission_id)
789
790
        # assert a success with validation taks
791
        self.assertEqual(res, "success")
792
793
        # check submission status and message
794
        self.submission.refresh_from_db()
795
796
        # check submission.state changed
797
        self.assertEqual(self.submission.status, NEED_REVISION)
798
        self.assertIn(
799
            "Error in metadata",
800
            self.submission.message)
801
802
        # check Names (they are all ok, except 1 - sample)
803
        for i, name in enumerate(self.name_qs):
804
            # get the appropriate ValidationResultRecord
805
            result = responses[i]
806
807
            if hasattr(name, "animal"):
808
                self.assertEqual(name.status, READY)
809
810
            else:
811
                # sample has errors
812
                self.assertEqual(name.status, NEED_REVISION)
813
814
            self.assertEqual(
815
                name.validationresult.messages,
816
                result.get_messages())
817
818
            self.assertEqual(
819
                name.validationresult.status,
820
                result.get_overall_status())
821
822
        # test for my methods called
823
        self.assertTrue(my_check.called)
824
        self.assertTrue(my_validate.called)
825
826
        # asserting my mock objects
827
        self.assertTrue(self.read_in_ruleset.called)
828
        self.assertTrue(self.check_ruleset.called)
829
        self.assertFalse(self.validate_retry.called)
830
831
        self.check_async_called(
832
            message='Need Revision',
833
            notification_message=(
834
                'Validation got errors: Error in '
835
                'metadata. Need revisions before submit'),
836
            validation_message={
837
                'animals': self.n_animals, 'samples': self.n_samples,
838
                'animal_unkn': 0, 'sample_unkn': 0,
839
                'animal_issues': 0, 'sample_issues': 1},
840
            pk=1)
841
842
843
class ValidateSubmissionStatusTest(ValidateSubmissionMixin, TestCase):
844
    """Check database statuses after calling validation"""
845
846
    def setUp(self):
847
        # calling base setup
848
        super().setUp()
849
850
        # get a submission data object (with no ruleset)
851
        self.submission_data = ValidateSubmission(
852
            self.submission, ruleset=None)
853
854
        # track an animal for testing
855
        self.animal = Animal.objects.get(pk=1)
856
        self.animal_name = self.animal.name
857
858
    def check_status(self, status, messages, name_status):
859
        """Test if I can update status for a model that pass validation"""
860
861
        result = PickableMock()
862
        result.get_overall_status.return_value = status
863
        result.get_messages.return_value = messages
864
        result_set = Mock()
865
        result_set.get_comparable_str.return_value = "A message"
866
        result.result_set = [result_set]
867
868
        submission_statuses = Counter(
869
            {'Pass': 0,
870
             'Warning': 0,
871
             'Error': 0,
872
             'JSON': 0})
873
874
        # calling update statuses
875
        self.submission_data.update_statuses(
876
            submission_statuses, self.animal, result)
877
878
        # Test for animal status
879
        self.animal_name.refresh_from_db()
880
        self.assertEqual(self.animal_name.status, name_status)
881
882
        # get validation result object
883
        validationresult = self.animal_name.validationresult
884
        self.assertEqual(validationresult.status, status)
885
        self.assertEqual(validationresult.messages, messages)
886
887
        # test submission status
888
        self.assertEqual(submission_statuses[status], 1)
889
890
    def test_update_status_pass(self):
891
        status = 'Pass'
892
        messages = ['Passed all tests']
893
        name_status = READY
894
895
        self.check_status(status, messages, name_status)
896
897
    def test_update_status_warning(self):
898
        status = 'Warning'
899
        messages = ['issued a warning']
900
        name_status = READY
901
902
        self.check_status(status, messages, name_status)
903
904
    def test_update_status_error(self):
905
        status = 'Error'
906
        messages = ['issued an error']
907
        name_status = NEED_REVISION
908
909
        self.check_status(status, messages, name_status)
910
911
912
class ValidateUpdatedSubmissionStatusTest(ValidateSubmissionMixin, TestCase):
913
    """Check database statuses after calling validation for an updated
914
    submission (a submission completed and submitted to biosample in which
915
    I want to modify a thing)"""
916
917
    def setUp(self):
918
        # call base method
919
        super().setUp()
920
921
        # get a submission data object (with no ruleset)
922
        self.submission_data = ValidateSubmission(
923
            self.submission, ruleset=None)
924
925
        # take all names and set them to completed, as after a successful
926
        # submission:
927
        self.name_qs.update(status=COMPLETED)
928
929
        # take the animal name I want to update
930
        self.animal_name = Name.objects.get(pk=3)
931
932
        # update submission status. Simulate a completed submission in which
933
        # I want to update something
934
        self.submission.status = NEED_REVISION
935
        self.submission.save()
936
937
        # update name objects. In this case, animal was modified
938
        self.animal_name.status = NEED_REVISION
939
        self.animal_name.save()
940
941
    def update_status(self, status, messages, name_status):
942
        """Test if I can update status for a model that pass validation"""
943
944
        # modelling validation same result for every object
945
        result = PickableMock()
946
        result.get_overall_status.return_value = status
947
        result.get_messages.return_value = messages
948
949
        result_set = Mock()
950
        result_set.get_comparable_str.return_value = "A message"
951
        result.result_set = [result_set]
952
953
        submission_statuses = Counter(
954
            {'Pass': 0,
955
             'Warning': 0,
956
             'Error': 0,
957
             'JSON': 0})
958
959
        # calling update statuses on name objects
960
        for name in self.name_qs:
961
            if hasattr(name, "animal"):
962
                self.submission_data.update_statuses(
963
                    submission_statuses,
964
                    name.animal,
965
                    result)
966
            else:
967
                self.submission_data.update_statuses(
968
                    submission_statuses,
969
                    name.sample,
970
                    result)
971
972
        # refreshing data from db
973
        self.animal_name.refresh_from_db()
974
975
    def test_update_status_pass(self):
976
        status = 'Pass'
977
        messages = ['Passed all tests']
978
        name_status = READY
979
980
        self.update_status(status, messages, name_status)
981
982
        # asserting status change for animal
983
        self.assertEqual(self.animal_name.status, name_status)
984
985
        # validationresult is tested outside this class
986
987
        # other statuses are unchanged
988
        for name in self.name_qs.exclude(pk=self.animal_name.pk):
989
            self.assertEqual(name.status, COMPLETED)
990
991
    def test_update_status_warning(self):
992
        status = 'Warning'
993
        messages = ['issued a warning']
994
        name_status = READY
995
996
        self.update_status(status, messages, name_status)
997
998
        # asserting status change for animal
999
        self.assertEqual(self.animal_name.status, name_status)
1000
1001
        # other statuses are unchanged
1002
        for name in self.name_qs.exclude(pk=self.animal_name.pk):
1003
            self.assertEqual(name.status, COMPLETED)
1004
1005
    def test_update_status_error(self):
1006
        status = 'Error'
1007
        messages = ['issued an error']
1008
        name_status = NEED_REVISION
1009
1010
        self.update_status(status, messages, name_status)
1011
1012
        # all statuses are changed (and need revisions)
1013
        for name in self.name_qs:
1014
            self.assertEqual(name.status, NEED_REVISION)
1015