ValidateUpdatedSubmissionStatusTest.test_update_status_warning()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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