| 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 |