Passed
Pull Request — master (#44)
by Paolo
05:38
created

CustomWebSocketMixin.check_message()   A

Complexity

Conditions 2

Size

Total Lines 31
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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