Completed
Branch master (206998)
by Paolo
02:15 queued 15s
created

ValidateSubmissionTest.test_validate_submission()   B

Complexity

Conditions 2

Size

Total Lines 62
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 62
rs 8.92
c 0
b 0
f 0
cc 2
nop 5

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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