Total Complexity | 40 |
Total Lines | 1005 |
Duplicated Lines | 3.38 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like validation.tests.test_tasks often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
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): |
|
|
|||
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 |