Passed
Pull Request — master (#35)
by Paolo
02:08
created

ValidateSubmissionStatusTest.setUp()   A

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
"""
4
Created on 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 PersonMixinTestCase
24
from image_app.models import Submission, Person, Name, Animal, Sample
25
26
from ..tasks import ValidateTask, ValidationError, ValidateSubmission
27
from ..helpers import OntologyCacheError, RulesetError
28
from ..models import ValidationSummary
29
30
31
# https://github.com/testing-cabal/mock/issues/139#issuecomment-122128815
32
class PickableMock(Mock):
33
    """Provide a __reduce__ method to allow pickling mock objects"""
34
35
    def __reduce__(self):
36
        return (Mock, ())
37
38
39
class ValidateSubmissionMixin(PersonMixinTestCase):
40
    """A mixin to define common stuff for testing data validation"""
41
42
    # an attribute for PersonMixinTestCase
43
    person = Person
44
45
    fixtures = [
46
        'image_app/animal',
47
        'image_app/dictbreed',
48
        'image_app/dictcountry',
49
        'image_app/dictrole',
50
        'image_app/dictsex',
51
        'image_app/dictspecie',
52
        'image_app/dictstage',
53
        'image_app/dictuberon',
54
        'image_app/name',
55
        'image_app/ontology',
56
        'image_app/organization',
57
        'image_app/publication',
58
        'image_app/sample',
59
        'image_app/submission',
60
        'image_app/user',
61
        "validation/validationresult",
62
        'validation/validationsummary'
63
    ]
64
65
    def setUp(self):
66
        # calling base methods
67
        super().setUp()
68
69
        # get a submission object
70
        self.submission = Submission.objects.get(pk=1)
71
72
        # set a status which I can validate
73
        self.submission.status = LOADED
74
        self.submission.save()
75
76
        # track submission ID
77
        self.submission_id = self.submission.id
78
79
        # track names
80
        self.name_qs = Name.objects.exclude(name__contains="unknown")
81
82
        # track animal and samples
83
        self.animal_qs = Animal.objects.filter(
84
            name__submission=self.submission)
85
        self.sample_qs = Sample.objects.filter(
86
            name__submission=self.submission)
87
88
        # track animal and samples count
89
        self.n_animals = self.animal_qs.count()
90
        self.n_samples = self.sample_qs.count()
91
92
        # setting tasks
93
        self.my_task = ValidateTask()
94
95
96
class WebSocketMixin(object):
97
    """Override setUp to mock websocket objects"""
98
99
    def setUp(self):
100
        # calling my base class setup
101
        super().setUp()
102
103
        # setting channels methods
104
        self.asyncio_mock_patcher = patch(
105
            'asyncio.get_event_loop')
106
        self.asyncio_mock = self.asyncio_mock_patcher.start()
107
108
        # mocking asyncio return value
109
        self.run_until = self.asyncio_mock.return_value
110
        self.run_until.run_until_complete = Mock()
111
112
        # another patch
113
        self.send_msg_ws_patcher = patch(
114
            'validation.tasks.send_message_to_websocket')
115
        self.send_msg_ws = self.send_msg_ws_patcher.start()
116
117
    def tearDown(self):
118
        # stopping mock objects
119
        self.asyncio_mock_patcher.stop()
120
        self.send_msg_ws_patcher.stop()
121
122
        # calling base methods
123
        super().tearDown()
124
125
    def check_async_called(
126
            self, message, notification_message, validation_message=None,
127
            pk=1):
128
129
        """Check django channels async messages called"""
130
131
        # defining default validation message.
132
        if not validation_message:
133
            animal_qs = Animal.objects.filter(
134
                name__submission=self.submission)
135
            sample_qs = Sample.objects.filter(
136
                name__submission=self.submission)
137
138
            validation_message = {
139
                'animals': self.n_animals,
140
                'samples': self.n_samples,
141
                'animal_unkn': animal_qs.filter(
142
                    name__validationresult__isnull=True).count(),
143
                'sample_unkn': sample_qs.filter(
144
                    name__validationresult__isnull=True).count(),
145
                'animal_issues': 0,
146
                'sample_issues': 0
147
            }
148
149
        self.assertEqual(self.asyncio_mock.call_count, 1)
150
        self.assertEqual(self.run_until.run_until_complete.call_count, 1)
151
        self.assertEqual(self.send_msg_ws.call_count, 1)
152
        self.send_msg_ws.assert_called_with(
153
            {'message': message,
154
             'notification_message': notification_message,
155
             'validation_message': validation_message}, pk)
156
157
    def check_async_not_called(self):
158
        """Check django channels async messages not called"""
159
160
        self.assertEqual(self.asyncio_mock.call_count, 0)
161
        self.assertEqual(self.run_until.run_until_complete.call_count, 0)
162
        self.assertEqual(self.send_msg_ws.call_count, 0)
163
164
165
class ValidateSubmissionTest(ValidateSubmissionMixin, TestCase):
166
167
    def setUp(self):
168
        # calling base methods
169
        super().setUp()
170
171
        # get a submission data object (with no ruleset)
172
        self.submission_data = ValidateSubmission(
173
            self.submission, ruleset=None)
174
175
    def test_check_valid_statuses(self):
176
        """test validation supporting statuses"""
177
178
        self.assertTrue(self.submission_data.check_valid_statuses())
179
180
        # set a fake status
181
        self.submission_data.statuses_animals['foo'] = 1
182
        self.assertFalse(self.submission_data.check_valid_statuses())
183
184
        # reset and set sample status status
185
        self.submission_data.statuses_animals = Counter()
186
        self.submission_data.statuses_samples['foo'] = 1
187
        self.assertFalse(self.submission_data.check_valid_statuses())
188
189
    def test_has_keys(self):
190
        """Test has error, warning, JSON in validation tests"""
191
192
        self.assertFalse(self.submission_data.has_errors_in_rules())
193
        self.assertFalse(self.submission_data.has_warnings_in_rules())
194
        self.assertFalse(self.submission_data.has_errors_in_json())
195
196
        # set a fake status
197
        self.submission_data.statuses_animals['JSON'] = 1
198
        self.submission_data.statuses_animals['Error'] = 1
199
        self.submission_data.statuses_samples['Warning'] = 1
200
201
        self.assertTrue(self.submission_data.has_errors_in_rules())
202
        self.assertTrue(self.submission_data.has_warnings_in_rules())
203
        self.assertTrue(self.submission_data.has_errors_in_json())
204
205
    def test_create_validation_summary(self):
206
        """Create and set validation summary object"""
207
208
        # get valdiationsummary objects
209
        summary_qs = ValidationSummary.objects.filter(
210
            submission=self.submission)
211
212
        # wipe out validationsummary objects
213
        summary_qs.delete()
214
215
        # set up messages
216
        self.submission_data.statuses_animals['JSON'] = 1
217
        self.submission_data.statuses_animals['Error'] = 1
218
        self.submission_data.statuses_samples['Warning'] = 1
219
220
        self.submission_data.messages_animals['test json'] = 1
221
        self.submission_data.messages_animals['test error'] = 1
222
        self.submission_data.messages_samples['test warning'] = 1
223
224
        # call function
225
        self.submission_data.create_validation_summary()
226
227
        # assert I have two objects
228
        self.assertTrue(summary_qs.count(), 2)
229
230
        # get animal vs
231
        animal_summary = summary_qs.get(type="animal")
232
        self.assertEqual(animal_summary.all_count, self.n_animals)
233
234
        sample_summary = summary_qs.get(type="sample")
235
        self.assertEqual(sample_summary.all_count, self.n_samples)
236
237
238
class ValidateTaskTest(
239
        WebSocketMixin, ValidateSubmissionMixin, TestCase):
240
241
    def test_on_failure(self):
242
        """Testing on failure methods"""
243
244
        exc = Exception("Test")
245
        task_id = "test_task_id"
246
        args = [self.submission_id]
247
        kwargs = {}
248
        einfo = ExceptionInfo
249
250
        # call on_failure method
251
        self.my_task.on_failure(exc, task_id, args, kwargs, einfo)
252
253
        # check submission status and message
254
        submission = Submission.objects.get(pk=self.submission_id)
255
256
        # check submission.state changed
257
        self.assertEqual(submission.status, ERROR)
258
        self.assertEqual(
259
            submission.message,
260
            "Unknown error in validation - Test")
261
262
        # test email sent
263
        self.assertGreater(len(mail.outbox), 1)
264
265
        # read email
266
        email = mail.outbox[0]
267
268
        self.assertEqual(
269
            "Error in IMAGE Validation: Unknown error in validation - Test",
270
            email.subject)
271
272
        self.check_async_called(
273
            'Error',
274
            'Unknown error in validation - Test')
275
276
    @patch("validation.tasks.MetaDataValidation.read_in_ruleset")
277
    @patch("validation.helpers.validation.check_ruleset",
278
           return_value=[])
279
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
280
    @patch("validation.tasks.MetaDataValidation.validate")
281
    @patch("validation.tasks.ValidateTask.retry")
282
    def test_validate_retry(
283
            self, my_retry, my_validate, my_check, my_ruleset, my_read):
284
        """Test validation with retry"""
285
286
        # setting check_usi_structure result
287
        my_check.return_value = []
288
289
        # Set a side effect on the patched methods
290
        # so that they raise the errors we want.
291
        my_retry.side_effect = Retry()
292
        my_validate.side_effect = Exception()
293
294
        with raises(Retry):
295
            self.my_task.run(submission_id=self.submission_id)
296
297
        self.assertTrue(my_validate.called)
298
        self.assertTrue(my_retry.called)
299
        self.assertTrue(my_check.called)
300
        self.assertTrue(my_ruleset.called)
301
        self.assertTrue(my_read.called)
302
303
        # asserting django channels not called
304
        self.check_async_not_called()
305
306
    @patch("validation.tasks.MetaDataValidation.read_in_ruleset")
307
    @patch("validation.helpers.validation.check_ruleset",
308
           return_value=[])
309
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
310
    @patch("validation.tasks.MetaDataValidation.validate")
311
    @patch("validation.tasks.ValidateTask.retry")
312
    def test_issues_with_api(
313
            self, my_retry, my_validate, my_check, my_ruleset, my_read):
314
        """Test errors with validation API"""
315
316
        # Set a side effect on the patched methods
317
        # so that they raise the errors we want.
318
        my_retry.side_effect = Retry()
319
        my_check.return_value = []
320
        my_validate.side_effect = json.decoder.JSONDecodeError(
321
            msg="test", doc="test", pos=1)
322
323
        # call task. No retries with issues at EBI
324
        res = self.my_task.run(submission_id=self.submission_id)
325
326
        # assert a success with validation taks
327
        self.assertEqual(res, "success")
328
329
        # check submission status and message
330
        self.submission.refresh_from_db()
331
332
        # this is the message I want
333
        message = "Errors in EBI API endpoints. Please try again later"
334
335
        # check submission.status changed to NEED_REVISION
336
        self.assertEqual(self.submission.status, LOADED)
337
        self.assertIn(
338
            message,
339
            self.submission.message)
340
341
        # test email sent
342
        self.assertEqual(len(mail.outbox), 1)
343
344
        # read email
345
        email = mail.outbox[0]
346
347
        self.assertEqual(
348
            "Error in IMAGE Validation: %s" % message,
349
            email.subject)
350
351
        self.assertTrue(my_validate.called)
352
        self.assertFalse(my_retry.called)
353
        self.assertTrue(my_check.called)
354
        self.assertTrue(my_ruleset.called)
355
        self.assertTrue(my_read.called)
356
357
        self.check_async_called(
358
            'Loaded',
359
            'Errors in EBI API endpoints. Please try again later'
360
        )
361
362
    @patch("validation.helpers.validation.read_in_ruleset",
363
           side_effect=OntologyCacheError("test exception"))
364
    @patch("validation.tasks.ValidateTask.retry")
365
    def test_issues_with_ontologychache(
366
            self, my_retry, my_validate):
367
        """Test errors with validation API when loading OntologyCache
368
        objects"""
369
370
        # call task. No retries with issues at EBI
371
        res = self.my_task.run(submission_id=self.submission_id)
372
373
        # assert a success with validation taks
374
        self.assertEqual(res, "success")
375
376
        # check submission status and message
377
        self.submission.refresh_from_db()
378
379
        # this is the message I want
380
        message = "Errors in EBI API endpoints. Please try again later"
381
382
        # check submission.status changed to LOADED
383
        self.assertEqual(self.submission.status, LOADED)
384
        self.assertIn(
385
            message,
386
            self.submission.message)
387
388
        # test email sent
389
        self.assertEqual(len(mail.outbox), 1)
390
391
        # read email
392
        email = mail.outbox[0]
393
394
        self.assertEqual(
395
            "Error in IMAGE Validation: %s" % message,
396
            email.subject)
397
398
        self.assertTrue(my_validate.called)
399
        self.assertFalse(my_retry.called)
400
401
        self.check_async_called(
402
            'Loaded',
403
            'Errors in EBI API endpoints. Please try again later')
404
405
    @patch("validation.helpers.validation.read_in_ruleset",
406
           side_effect=RulesetError(["test exception"]))
407
    @patch("validation.tasks.ValidateTask.retry")
408
    def test_issues_with_ruleset(
409
            self, my_retry, my_validate):
410
        """Test errors with ruleset"""
411
412
        # call task. No retries with issues at EBI
413
        res = self.my_task.run(submission_id=self.submission_id)
414
415
        # assert a success with validation taks
416
        self.assertEqual(res, "success")
417
418
        # check submission status and message
419
        self.submission.refresh_from_db()
420
421
        # this is the message I want
422
        message = (
423
            "Error in IMAGE-metadata ruleset. Please inform InjectTool team")
424
425
        # check submission.status changed to ERROR
426
        self.assertEqual(self.submission.status, ERROR)
427
        self.assertIn(
428
            message,
429
            self.submission.message)
430
431
        # test email sent
432
        self.assertGreater(len(mail.outbox), 1)
433
434
        # read email
435
        email = mail.outbox[0]
436
437
        self.assertEqual(
438
            "Error in IMAGE Validation: %s" % message,
439
            email.subject)
440
441
        self.assertTrue(my_validate.called)
442
        self.assertFalse(my_retry.called)
443
444
        self.check_async_called(
445
            'Error',
446
            'Error in IMAGE-metadata ruleset. Please inform InjectTool team')
447
448
    @patch("validation.tasks.MetaDataValidation.read_in_ruleset")
449
    @patch("validation.helpers.validation.check_ruleset",
450
           return_value=[])
451
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
452
    @patch("validation.tasks.MetaDataValidation.validate")
453
    def test_validate_submission(
454
            self, my_validate, my_check, my_ruleset, my_read):
455
456
        # setting check_usi_structure result
457
        my_check.return_value = []
458
459
        # setting a return value for check_with_ruleset
460
        validation_result = Mock()
461
        validation_result.get_overall_status.return_value = "Pass"
462
        validation_result.get_messages.return_value = ["A message"]
463
        result_set = Mock()
464
        result_set.get_comparable_str.return_value = "A message"
465
        validation_result.result_set = [result_set]
466
        my_validate.return_value = validation_result
467
468
        # NOTE that I'm calling the function directly, without delay
469
        # (AsyncResult). I've patched the time consuming task
470
        res = self.my_task.run(submission_id=self.submission_id)
471
472
        # assert a success with validation taks
473
        self.assertEqual(res, "success")
474
475
        # check submission status and message
476
        self.submission.refresh_from_db()
477
478
        # check submission.state changed
479
        self.assertEqual(self.submission.status, READY)
480
        self.assertEqual(
481
            self.submission.message,
482
            "Submission validated with success")
483
484
        # check Names (they are all ok)
485
        for name in self.name_qs:
486
            self.assertEqual(name.status, READY)
487
488
            # test for model message (usi_results)
489
            self.assertEqual(
490
                name.validationresult.messages, ["A message"])
491
            self.assertEqual(name.validationresult.status, "Pass")
492
493
        # assert validation functions called
494
        self.assertTrue(my_check.called)
495
        self.assertTrue(my_ruleset.called)
496
        self.assertTrue(my_validate.called)
497
        self.assertTrue(my_read.called)
498
499
        # no unknown and sample with issues
500
        validation_message = {
501
            'animals': self.n_animals,
502
            'samples': self.n_samples,
503
            'animal_unkn': 0, 'sample_unkn': 0,
504
            'animal_issues': 0, 'sample_issues': 0}
505
506
        self.check_async_called(
507
            'Ready',
508
            'Submission validated with success',
509
            validation_message=validation_message
510
        )
511
512
    @patch("validation.tasks.MetaDataValidation.read_in_ruleset")
513
    @patch("validation.helpers.validation.check_ruleset",
514
           return_value=[])
515
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
516
    @patch("validation.tasks.MetaDataValidation.validate")
517
    def test_validate_submission_wrong_json(
518
            self, my_validate, my_check, my_ruleset, my_read):
519
520
        # assign a fake response for check_usi_structure
521
        usi_result = [
522
            ('Wrong JSON structure: no title field for record with '
523
             'alias as animal_1'),
524
            ('Wrong JSON structure: the values for attribute Person '
525
             'role needs to be in an array for record animal_1')
526
        ]
527
        my_check.return_value = usi_result
528
529
        # setting a return value for check_with_ruleset
530
        rule_result = Mock()
531
        rule_result.get_overall_status.return_value = "Pass"
532
        my_validate.return_value = rule_result
533
534
        # call task
535
        res = self.my_task.run(submission_id=self.submission_id)
536
537
        # assert a success with validation taks
538
        self.assertEqual(res, "success")
539
540
        # check submission status and message
541
        self.submission.refresh_from_db()
542
543
        # check submission.state changed
544
        self.assertEqual(self.submission.status, NEED_REVISION)
545
        self.assertIn(
546
            "Wrong JSON structure",
547
            self.submission.message)
548
549
        # check Names (they require revisions)
550
        for name in self.name_qs:
551
            self.assertEqual(name.status, NEED_REVISION)
552
553
            # test for model message (usi_results)
554
            self.assertEqual(
555
                name.validationresult.messages, usi_result)
556
            self.assertEqual(
557
                    name.validationresult.status, "Wrong JSON structure")
558
559
        # if JSON is not valid, I don't check for ruleset
560
        self.assertTrue(my_check.called)
561
        self.assertTrue(my_ruleset.called)
562
        self.assertFalse(my_validate.called)
563
        self.assertTrue(my_read.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.read_in_ruleset")
576
    @patch("validation.helpers.validation.check_ruleset",
577
           return_value=[])
578
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
579
    @patch("validation.tasks.MetaDataValidation.validate")
580
    def test_unsupported_status(
581
            self, my_validate, my_check, my_ruleset, my_read):
582
        """This test will ensure that image_validation ValidationResultRecord
583
        still support the same statuses"""
584
585
        # setting check_usi_structure result
586
        my_check.return_value = []
587
588
        # setting a return value for check_with_ruleset
589
        rule_result = PickableMock()
590
        rule_result.get_overall_status.return_value = "A fake status"
591
        rule_result.get_messages.return_value = ["A fake message", ]
592
593
        result_set = Mock()
594
        result_set.get_comparable_str.return_value = "A fake message"
595
        rule_result.result_set = [result_set]
596
597
        my_validate.return_value = rule_result
598
599
        # call task
600
        self.assertRaisesRegex(
601
            ValidationError,
602
            "Unsupported validation status for submission",
603
            self.my_task.run,
604
            submission_id=self.submission_id)
605
606
        # check submission status and message
607
        self.submission.refresh_from_db()
608
609
        # check submission.state changed
610
        self.assertEqual(self.submission.status, ERROR)
611
        self.assertIn(
612
            "Unsupported validation status for submission",
613
            self.submission.message)
614
615
        # if JSON is not valid, I don't check for ruleset
616
        self.assertTrue(my_check.called)
617
        self.assertTrue(my_ruleset.called)
618
        self.assertTrue(my_validate.called)
619
        self.assertTrue(my_read.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.read_in_ruleset")
634
    @patch("validation.helpers.validation.check_ruleset",
635
           return_value=[])
636
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
637
    @patch("validation.tasks.MetaDataValidation.validate")
638
    def test_validate_submission_warnings(
639
            self, my_validate, my_check, my_ruleset, my_read):
640
        """A submission with warnings is a READY submission"""
641
642
        # setting check_usi_structure result
643
        my_check.return_value = []
644
645
        # setting a return value for check_with_ruleset
646
        result1 = ValidationResultRecord("animal_1")
647
        result1.add_validation_result_column(
648
            ValidationResultColumn(
649
                "warning",
650
                "warn message",
651
                "animal_1",
652
                "warn column")
653
        )
654
655
        result2 = ValidationResultRecord("animal_2")
656
        result2.add_validation_result_column(
657
            ValidationResultColumn(
658
                "pass",
659
                "a message",
660
                "animal_2",
661
                "")
662
        )
663
664
        result3 = ValidationResultRecord("animal_3")
665
        result3.add_validation_result_column(
666
            ValidationResultColumn(
667
                "pass",
668
                "a message",
669
                "animal_3",
670
                "")
671
        )
672
673
        result4 = ValidationResultRecord("sample_1")
674
        result4.add_validation_result_column(
675
            ValidationResultColumn(
676
                "pass",
677
                "a message",
678
                "sample_1",
679
                "")
680
        )
681
682
        # add results to result set
683
        responses = [result1, result2, result3, result4]
684
        my_validate.side_effect = responses
685
686
        # call task
687
        res = self.my_task.run(submission_id=self.submission_id)
688
689
        # assert a success with validation taks
690
        self.assertEqual(res, "success")
691
692
        # check submission status and message
693
        self.submission.refresh_from_db()
694
695
        # check submission.state changed
696
        self.assertEqual(self.submission.status, READY)
697
        self.assertIn(
698
            "Submission validated with some warnings",
699
            self.submission.message)
700
701
        # check Names (they are all ok)
702
        for i, name in enumerate(self.name_qs):
703
            # get the appropriate ValidationResultRecord
704
            result = responses[i]
705
706
            # all objects are ready for submissions
707
            self.assertEqual(name.status, READY)
708
709
            self.assertEqual(
710
                name.validationresult.messages,
711
                result.get_messages())
712
713
            self.assertEqual(
714
                name.validationresult.status,
715
                result.get_overall_status())
716
717
        # test for my methods called
718
        self.assertTrue(my_check.called)
719
        self.assertTrue(my_ruleset.called)
720
        self.assertTrue(my_validate.called)
721
        self.assertTrue(my_read.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.read_in_ruleset")
733
    @patch("validation.helpers.validation.check_ruleset",
734
           return_value=[])
735
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
736
    @patch("validation.tasks.MetaDataValidation.validate")
737
    def test_validate_submission_errors(
738
            self, my_validate, my_check, my_ruleset, my_read):
739
        """A submission with errors is a NEED_REVISION submission"""
740
741
        # setting check_usi_structure result
742
        my_check.return_value = []
743
744
        # setting a return value for check_with_ruleset
745
        result1 = ValidationResultRecord("animal_1")
746
        result1.add_validation_result_column(
747
            ValidationResultColumn(
748
                "warning",
749
                "warn message",
750
                "animal_1",
751
                "warn column")
752
        )
753
754
        result2 = ValidationResultRecord("animal_2")
755
        result2.add_validation_result_column(
756
            ValidationResultColumn(
757
                "pass",
758
                "a message",
759
                "animal_2",
760
                "")
761
        )
762
763
        result3 = ValidationResultRecord("animal_3")
764
        result3.add_validation_result_column(
765
            ValidationResultColumn(
766
                "pass",
767
                "a message",
768
                "animal_3",
769
                "")
770
        )
771
772
        result4 = ValidationResultRecord("sample_1")
773
        result4.add_validation_result_column(
774
            ValidationResultColumn(
775
                "error",
776
                "error message",
777
                "sample_1",
778
                "error column")
779
        )
780
781
        # add results to result set
782
        responses = [result1, result2, result3, result4]
783
        my_validate.side_effect = responses
784
785
        # call task
786
        res = self.my_task.run(submission_id=self.submission_id)
787
788
        # assert a success with validation taks
789
        self.assertEqual(res, "success")
790
791
        # check submission status and message
792
        self.submission.refresh_from_db()
793
794
        # check submission.state changed
795
        self.assertEqual(self.submission.status, NEED_REVISION)
796
        self.assertIn(
797
            "Error in metadata",
798
            self.submission.message)
799
800
        # check Names (they are all ok, except 1 - sample)
801
        for i, name in enumerate(self.name_qs):
802
            # get the appropriate ValidationResultRecord
803
            result = responses[i]
804
805
            if hasattr(name, "animal"):
806
                self.assertEqual(name.status, READY)
807
808
            else:
809
                # sample has errors
810
                self.assertEqual(name.status, NEED_REVISION)
811
812
            self.assertEqual(
813
                name.validationresult.messages,
814
                result.get_messages())
815
816
            self.assertEqual(
817
                name.validationresult.status,
818
                result.get_overall_status())
819
820
        # test for my methods called
821
        self.assertTrue(my_check.called)
822
        self.assertTrue(my_ruleset.called)
823
        self.assertTrue(my_validate.called)
824
        self.assertTrue(my_read.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