Passed
Pull Request — master (#38)
by Paolo
03:00
created

validation.tests.test_tasks   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 1011
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 41
eloc 592
dl 0
loc 1011
rs 9.1199
c 0
b 0
f 0

29 Methods

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