Completed
Pull Request — master (#44)
by Paolo
06:24
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
        self.submission_data.animals_offending_columns['test error'] = \
183
            'test_error'
184
        self.submission_data.samples_offending_columns['test warning'] = \
185
            'test_warning'
186
187
        # call function
188
        self.submission_data.create_validation_summary()
189
190
        # assert I have two objects
191
        self.assertTrue(summary_qs.count(), 2)
192
193
        # get animal vs
194
        animal_summary = summary_qs.get(type="animal")
195
        self.assertEqual(animal_summary.all_count, self.n_animals)
196
197
        sample_summary = summary_qs.get(type="sample")
198
        self.assertEqual(sample_summary.all_count, self.n_samples)
199
200
201
class ValidateTaskTest(
202
        CustomWebSocketMixin, ValidateSubmissionMixin, TestCase):
203
204
    def setUp(self):
205
        # calling base methods
206
        super().setUp()
207
208
        # mocking task retry
209
        self.validate_retry_patcher = patch(
210
            "validation.tasks.ValidateTask.retry")
211
        self.validate_retry = self.validate_retry_patcher.start()
212
213
    def tearDown(self):
214
        # stopping my mock objects
215
        self.validate_retry_patcher.stop()
216
217
        # calling base methods
218
        super().tearDown()
219
220 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...
221
        """Testing on failure methods"""
222
223
        exc = Exception("Test")
224
        task_id = "test_task_id"
225
        args = [self.submission_id]
226
        kwargs = {}
227
        einfo = ExceptionInfo
228
229
        # call on_failure method
230
        self.my_task.on_failure(exc, task_id, args, kwargs, einfo)
231
232
        # check submission status and message
233
        submission = Submission.objects.get(pk=self.submission_id)
234
235
        # check submission.state changed
236
        self.assertEqual(submission.status, ERROR)
237
        self.assertEqual(
238
            submission.message,
239
            "Unknown error in validation - Test")
240
241
        # test email sent
242
        self.assertGreater(len(mail.outbox), 1)
243
244
        # read email
245
        email = mail.outbox[0]
246
247
        self.assertEqual(
248
            "Error in IMAGE Validation: Unknown error in validation - Test",
249
            email.subject)
250
251
        self.check_message(
252
            'Error',
253
            'Unknown error in validation - Test')
254
255
    @patch("validation.tasks.ValidateSubmission.validate_model")
256
    def test_validate_retry(self, my_validate):
257
        """Test validation with retry"""
258
259
        # Set a side effect on the patched methods
260
        # so that they raise the errors we want.
261
        self.validate_retry.side_effect = Retry()
262
263
        my_validate.side_effect = Exception()
264
265
        with raises(Retry):
266
            self.my_task.run(submission_id=self.submission_id)
267
268
        # asserting mock validate_model called
269
        self.assertTrue(my_validate.called)
270
271
        # asserting my mock objects
272
        self.assertTrue(self.read_in_ruleset.called)
273
        self.assertTrue(self.check_ruleset.called)
274
        self.assertTrue(self.validate_retry.called)
275
276
        # asserting django channels not called
277
        self.check_message_not_called()
278
279
    @patch("validation.tasks.ValidateSubmission.validate_model")
280
    def test_issues_with_api(self, my_validate):
281
        """Test errors with validation API"""
282
283
        my_validate.side_effect = json.decoder.JSONDecodeError(
284
            msg="test", doc="test", pos=1)
285
286
        # call task. No retries with issues at EBI
287
        res = self.my_task.run(submission_id=self.submission_id)
288
289
        # assert a success with validation taks
290
        self.assertEqual(res, "success")
291
292
        # check submission status and message
293
        self.submission.refresh_from_db()
294
295
        # this is the message I want
296
        message = "Errors in EBI API endpoints. Please try again later"
297
298
        # check submission.status changed to NEED_REVISION
299
        self.assertEqual(self.submission.status, LOADED)
300
        self.assertIn(
301
            message,
302
            self.submission.message)
303
304
        # test email sent
305
        self.assertEqual(len(mail.outbox), 1)
306
307
        # read email
308
        email = mail.outbox[0]
309
310
        self.assertEqual(
311
            "Error in IMAGE Validation: %s" % message,
312
            email.subject)
313
314
        # asserting mock validate_model called
315
        self.assertTrue(my_validate.called)
316
317
        # asserting my mock objects
318
        self.assertTrue(self.read_in_ruleset.called)
319
        self.assertTrue(self.check_ruleset.called)
320
        self.assertFalse(self.validate_retry.called)
321
322
        self.check_message(
323
            'Loaded',
324
            'Errors in EBI API endpoints. Please try again later'
325
        )
326
327
    def test_issues_with_ontologychache(self):
328
        """Test errors with validation API when loading OntologyCache
329
        objects"""
330
331
        # return a custom exception with read_in_ruleset
332
        self.read_in_ruleset.side_effect = OntologyCacheError("test exception")
333
334
        # call task. No retries with issues at EBI
335
        res = self.my_task.run(submission_id=self.submission_id)
336
337
        # assert a success with validation taks
338
        self.assertEqual(res, "success")
339
340
        # check submission status and message
341
        self.submission.refresh_from_db()
342
343
        # this is the message I want
344
        message = "Errors in EBI API endpoints. Please try again later"
345
346
        # check submission.status changed to LOADED
347
        self.assertEqual(self.submission.status, LOADED)
348
        self.assertIn(
349
            message,
350
            self.submission.message)
351
352
        # test email sent
353
        self.assertEqual(len(mail.outbox), 1)
354
355
        # read email
356
        email = mail.outbox[0]
357
358
        self.assertEqual(
359
            "Error in IMAGE Validation: %s" % message,
360
            email.subject)
361
362
        # asserting my mock objects
363
        self.assertTrue(self.read_in_ruleset.called)
364
        self.assertFalse(self.check_ruleset.called)
365
        self.assertFalse(self.validate_retry.called)
366
367
        self.check_message(
368
            'Loaded',
369
            'Errors in EBI API endpoints. Please try again later')
370
371
    def test_issues_with_ruleset(self):
372
        """Test errors with ruleset"""
373
374
        # return a custom exception with read_in_ruleset
375
        self.read_in_ruleset.side_effect = RulesetError(["test exception"])
376
377
        # call task. No retries with issues at EBI
378
        res = self.my_task.run(submission_id=self.submission_id)
379
380
        # assert a success with validation taks
381
        self.assertEqual(res, "success")
382
383
        # check submission status and message
384
        self.submission.refresh_from_db()
385
386
        # this is the message I want
387
        message = (
388
            "Error in IMAGE-metadata ruleset. Please inform InjectTool team")
389
390
        # check submission.status changed to ERROR
391
        self.assertEqual(self.submission.status, ERROR)
392
        self.assertIn(
393
            message,
394
            self.submission.message)
395
396
        # test email sent
397
        self.assertGreater(len(mail.outbox), 1)
398
399
        # read email
400
        email = mail.outbox[0]
401
402
        self.assertEqual(
403
            "Error in IMAGE Validation: %s" % message,
404
            email.subject)
405
406
        # asserting my mock objects
407
        self.assertTrue(self.read_in_ruleset.called)
408
        self.assertFalse(self.check_ruleset.called)
409
        self.assertFalse(self.validate_retry.called)
410
411
        self.check_message(
412
            'Error',
413
            'Error in IMAGE-metadata ruleset. Please inform InjectTool team')
414
415
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
416
    @patch("validation.tasks.MetaDataValidation.validate")
417
    def test_validate_submission(
418
            self, my_validate, my_check):
419
        """Test a valid submission. Simulate image_validation result and
420
        status changes"""
421
422
        # setting check_usi_structure result. now is a ValidateResultRecord
423
        result = PickableMock()
424
        result.get_overall_status.return_value = "Pass"
425
        result.get_messages.return_value = []
426
        my_check.return_value = result
427
428
        # setting a return value for check_with_ruleset
429
        validation_result = Mock()
430
        validation_result.get_overall_status.return_value = "Pass"
431
        validation_result.get_messages.return_value = ["A message"]
432
        result_set = Mock()
433
        result_set.get_comparable_str.return_value = "A message"
434
        validation_result.result_set = [result_set]
435
        my_validate.return_value = validation_result
436
437
        # NOTE that I'm calling the function directly, without delay
438
        # (AsyncResult). I've patched the time consuming task
439
        res = self.my_task.run(submission_id=self.submission_id)
440
441
        # assert a success with validation taks
442
        self.assertEqual(res, "success")
443
444
        # check submission status and message
445
        self.submission.refresh_from_db()
446
447
        # check submission.state changed
448
        self.assertEqual(self.submission.status, READY)
449
        self.assertEqual(
450
            self.submission.message,
451
            "Submission validated with success")
452
453
        # check Names (they are all ok)
454
        for name in self.name_qs:
455
            self.assertEqual(name.status, READY)
456
457
            # test for model message (usi_results)
458
            self.assertEqual(
459
                name.validationresult.messages, ["A message"])
460
            self.assertEqual(name.validationresult.status, "Pass")
461
462
        # assert validation functions called
463
        self.assertTrue(my_check.called)
464
        self.assertTrue(my_validate.called)
465
466
        # asserting my mock objects
467
        self.assertTrue(self.read_in_ruleset.called)
468
        self.assertTrue(self.check_ruleset.called)
469
        self.assertFalse(self.validate_retry.called)
470
471
        # no unknown and sample with issues
472
        validation_message = {
473
            'animals': self.n_animals,
474
            'samples': self.n_samples,
475
            'animal_unkn': 0, 'sample_unkn': 0,
476
            'animal_issues': 0, 'sample_issues': 0}
477
478
        self.check_message(
479
            'Ready',
480
            'Submission validated with success',
481
            validation_message=validation_message
482
        )
483
484
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
485
    @patch("validation.tasks.MetaDataValidation.validate")
486
    def test_validate_submission_wrong_json(
487
            self, my_validate, my_check):
488
        """Test an error in JSON format"""
489
490
        # setting check_usi_structure result. now is a ValidateResultRecord
491
        messages = [
492
            ('Wrong JSON structure: no title field for record with '
493
             'alias as animal_1'),
494
            ('Wrong JSON structure: the values for attribute Person '
495
             'role needs to be in an array for record animal_1')
496
        ]
497
498
        usi_result = ValidationResultRecord("animal_1")
499
        usi_result.add_validation_result_column(
500
            ValidationResultColumn(
501
                "error",
502
                messages[0],
503
                "animal_1",
504
                "")
505
        )
506
        usi_result.add_validation_result_column(
507
            ValidationResultColumn(
508
                "error",
509
                messages[1],
510
                "animal_1",
511
                "")
512
        )
513
514
        my_check.return_value = usi_result
515
516
        # setting a return value for check_with_ruleset
517
        rule_result = Mock()
518
        rule_result.get_overall_status.return_value = "Pass"
519
        my_validate.return_value = rule_result
520
521
        # call task
522
        res = self.my_task.run(submission_id=self.submission_id)
523
524
        # assert a success with validation taks
525
        self.assertEqual(res, "success")
526
527
        # check submission status and message
528
        self.submission.refresh_from_db()
529
530
        # check submission.state changed
531
        self.assertEqual(self.submission.status, NEED_REVISION)
532
        self.assertIn(
533
            "Validation got errors",
534
            self.submission.message)
535
536
        # check Names (they require revisions)
537
        for name in self.name_qs:
538
            self.assertEqual(name.status, NEED_REVISION)
539
540
            # test for model message (usi_results)
541
            self.assertEqual(
542
                name.validationresult.messages, usi_result.get_messages())
543
            self.assertEqual(
544
                name.validationresult.status, usi_result.get_overall_status())
545
546
        # if JSON is not valid, I don't check for ruleset
547
        self.assertTrue(my_check.called)
548
        self.assertFalse(my_validate.called)
549
550
        # asserting my mock objects
551
        self.assertTrue(self.read_in_ruleset.called)
552
        self.assertTrue(self.check_ruleset.called)
553
        self.assertFalse(self.validate_retry.called)
554
555
        # all sample and animals have issues
556
        self.check_message(
557
            'Need Revision',
558
            ('Validation got errors: Error in metadata. '
559
             'Need revisions before submit'),
560
            {'animals': self.n_animals, 'samples': self.n_samples,
561
             'animal_unkn': 0, 'sample_unkn': 0,
562
             'animal_issues': self.n_animals,
563
             'sample_issues': self.n_samples},
564
            1)
565
566
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
567
    @patch("validation.tasks.MetaDataValidation.validate")
568
    def test_unsupported_status(
569
            self, my_validate, my_check):
570
        """This test will ensure that image_validation ValidationResultRecord
571
        still support the same statuses"""
572
573
        # setting check_usi_structure result. now is a ValidateResultRecord
574
        result = PickableMock()
575
        result.get_overall_status.return_value = "Pass"
576
        result.get_messages.return_value = []
577
        my_check.return_value = result
578
579
        # setting a return value for check_with_ruleset
580
        rule_result = PickableMock()
581
        rule_result.get_overall_status.return_value = "A fake status"
582
        rule_result.get_messages.return_value = ["A fake message", ]
583
584
        result_set = Mock()
585
        result_set.get_comparable_str.return_value = "A fake message"
586
        rule_result.result_set = [result_set]
587
588
        my_validate.return_value = rule_result
589
590
        # call task
591
        self.assertRaisesRegex(
592
            ValidationError,
593
            "Unsupported validation status for submission",
594
            self.my_task.run,
595
            submission_id=self.submission_id)
596
597
        # check submission status and message
598
        self.submission.refresh_from_db()
599
600
        # check submission.state changed
601
        self.assertEqual(self.submission.status, ERROR)
602
        self.assertIn(
603
            "Unsupported validation status for submission",
604
            self.submission.message)
605
606
        # if JSON is not valid, I don't check for ruleset
607
        self.assertTrue(my_check.called)
608
        self.assertTrue(my_validate.called)
609
610
        # asserting my mock objects
611
        self.assertTrue(self.read_in_ruleset.called)
612
        self.assertTrue(self.check_ruleset.called)
613
        self.assertFalse(self.validate_retry.called)
614
615
        self.check_message(
616
            message='Error',
617
            notification_message=(
618
                "Validation got errors: Unsupported validation status "
619
                "for submission Cryoweb (United Kingdom, test)"),
620
            validation_message={
621
                'animals': self.n_animals, 'samples': self.n_samples,
622
                'animal_unkn': 0, 'sample_unkn': 0,
623
                'animal_issues': self.n_animals,
624
                'sample_issues': self.n_samples},
625
            pk=1)
626
627
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
628
    @patch("validation.tasks.MetaDataValidation.validate")
629
    def test_validate_submission_warnings(
630
            self, my_validate, my_check):
631
        """A submission with warnings is a READY submission"""
632
633
        # setting check_usi_structure result. now is a ValidateResultRecord
634
        result = PickableMock()
635
        result.get_overall_status.return_value = "Pass"
636
        result.get_messages.return_value = []
637
        my_check.return_value = result
638
639
        # setting a return value for check_with_ruleset
640
        result1 = ValidationResultRecord("animal_1")
641
        result1.add_validation_result_column(
642
            ValidationResultColumn(
643
                "warning",
644
                "warn message",
645
                "animal_1",
646
                "warn column")
647
        )
648
649
        result2 = ValidationResultRecord("animal_2")
650
        result2.add_validation_result_column(
651
            ValidationResultColumn(
652
                "pass",
653
                "a message",
654
                "animal_2",
655
                "")
656
        )
657
658
        result3 = ValidationResultRecord("animal_3")
659
        result3.add_validation_result_column(
660
            ValidationResultColumn(
661
                "pass",
662
                "a message",
663
                "animal_3",
664
                "")
665
        )
666
667
        result4 = ValidationResultRecord("sample_1")
668
        result4.add_validation_result_column(
669
            ValidationResultColumn(
670
                "pass",
671
                "a message",
672
                "sample_1",
673
                "")
674
        )
675
676
        # add results to result set
677
        responses = [result1, result2, result3, result4]
678
        my_validate.side_effect = responses
679
680
        # call task
681
        res = self.my_task.run(submission_id=self.submission_id)
682
683
        # assert a success with validation taks
684
        self.assertEqual(res, "success")
685
686
        # check submission status and message
687
        self.submission.refresh_from_db()
688
689
        # check submission.state changed
690
        self.assertEqual(self.submission.status, READY)
691
        self.assertIn(
692
            "Submission validated with some warnings",
693
            self.submission.message)
694
695
        # check Names (they are all ok)
696
        for i, name in enumerate(self.name_qs):
697
            # get the appropriate ValidationResultRecord
698
            result = responses[i]
699
700
            # all objects are ready for submissions
701
            self.assertEqual(name.status, READY)
702
703
            self.assertEqual(
704
                name.validationresult.messages,
705
                result.get_messages())
706
707
            self.assertEqual(
708
                name.validationresult.status,
709
                result.get_overall_status())
710
711
        # test for my methods called
712
        self.assertTrue(my_check.called)
713
        self.assertTrue(my_validate.called)
714
715
        # asserting my mock objects
716
        self.assertTrue(self.read_in_ruleset.called)
717
        self.assertTrue(self.check_ruleset.called)
718
        self.assertFalse(self.validate_retry.called)
719
720
        self.check_message(
721
            message='Ready',
722
            notification_message='Submission validated with some warnings',
723
            validation_message={
724
                'animals': self.n_animals, 'samples': self.n_samples,
725
                'animal_unkn': 0, 'sample_unkn': 0,
726
                'animal_issues': 0, 'sample_issues': 0},
727
            pk=1)
728
729
    @patch("validation.tasks.MetaDataValidation.check_usi_structure")
730
    @patch("validation.tasks.MetaDataValidation.validate")
731
    def test_validate_submission_errors(
732
            self, my_validate, my_check):
733
        """A submission with errors is a NEED_REVISION submission"""
734
735
        # setting check_usi_structure result. now is a ValidateResultRecord
736
        result = PickableMock()
737
        result.get_overall_status.return_value = "Pass"
738
        result.get_messages.return_value = []
739
        my_check.return_value = result
740
741
        # setting a return value for check_with_ruleset
742
        result1 = ValidationResultRecord("animal_1")
743
        result1.add_validation_result_column(
744
            ValidationResultColumn(
745
                "warning",
746
                "warn message",
747
                "animal_1",
748
                "warn column")
749
        )
750
751
        result2 = ValidationResultRecord("animal_2")
752
        result2.add_validation_result_column(
753
            ValidationResultColumn(
754
                "pass",
755
                "a message",
756
                "animal_2",
757
                "")
758
        )
759
760
        result3 = ValidationResultRecord("animal_3")
761
        result3.add_validation_result_column(
762
            ValidationResultColumn(
763
                "pass",
764
                "a message",
765
                "animal_3",
766
                "")
767
        )
768
769
        result4 = ValidationResultRecord("sample_1")
770
        result4.add_validation_result_column(
771
            ValidationResultColumn(
772
                "error",
773
                "error message",
774
                "sample_1",
775
                "error column")
776
        )
777
778
        # add results to result set
779
        responses = [result1, result2, result3, result4]
780
        my_validate.side_effect = responses
781
782
        # call task
783
        res = self.my_task.run(submission_id=self.submission_id)
784
785
        # assert a success with validation taks
786
        self.assertEqual(res, "success")
787
788
        # check submission status and message
789
        self.submission.refresh_from_db()
790
791
        # check submission.state changed
792
        self.assertEqual(self.submission.status, NEED_REVISION)
793
        self.assertIn(
794
            "Error in metadata",
795
            self.submission.message)
796
797
        # check Names (they are all ok, except 1 - sample)
798
        for i, name in enumerate(self.name_qs):
799
            # get the appropriate ValidationResultRecord
800
            result = responses[i]
801
802
            if hasattr(name, "animal"):
803
                self.assertEqual(name.status, READY)
804
805
            else:
806
                # sample has errors
807
                self.assertEqual(name.status, NEED_REVISION)
808
809
            self.assertEqual(
810
                name.validationresult.messages,
811
                result.get_messages())
812
813
            self.assertEqual(
814
                name.validationresult.status,
815
                result.get_overall_status())
816
817
        # test for my methods called
818
        self.assertTrue(my_check.called)
819
        self.assertTrue(my_validate.called)
820
821
        # asserting my mock objects
822
        self.assertTrue(self.read_in_ruleset.called)
823
        self.assertTrue(self.check_ruleset.called)
824
        self.assertFalse(self.validate_retry.called)
825
826
        self.check_message(
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