Passed
Pull Request — master (#30)
by Paolo
01:17
created

ValidateSubmissionTest.test_unsupported_status()   B

Complexity

Conditions 1

Size

Total Lines 64
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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