Completed
Pull Request — devel (#76)
by Paolo
08:44
created

excel.tests.test_helpers   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 527
Duplicated Lines 13.28 %

Importance

Changes 0
Metric Value
wmc 24
eloc 278
dl 70
loc 527
rs 10
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A ExcelTemplateReaderTestCase.setUp() 0 11 1
A UploadTemplateTestCase.test_upload_template_errors_with_sex() 0 13 1
A ExcelTemplateReaderTestCase.check_generator() 0 3 1
A UploadTemplateTestCase.test_upload_template_errors_with_accuracies() 0 13 1
A UploadTemplateTestCase.test_upload_template_errors_with_species_in_animal() 0 12 1
A ExcelTemplateReaderTestCase.test_get_animal_records() 0 5 1
A ExcelTemplateReaderTestCase.test_check_sheets() 0 15 1
A UploadTemplateTestCase.test_issue_animal_age_at_collection() 0 19 1
A ExcelTemplateReaderTestCase.test_species_in_animal_and_breeds_differ() 0 24 1
A ExcelTemplateReaderTestCase.test_check_columns() 0 7 1
A ExcelTemplateReaderTestCase.test_check_columns_issue() 0 53 1
A ExcelTemplateReaderTestCase.test_check_accuracies() 0 7 1
A ExcelTemplateReaderTestCase.test_get_sample_records() 0 5 1
A UploadTemplateTestCase.test_upload_template_errors_with_countries() 0 12 1
A UploadTemplateTestCase.test_issue_sampling_to_preparation_interval() 0 19 1
A ExcelTemplateReaderTestCase.test_get_breed_records() 0 5 1
A UploadTemplateTestCase.test_upload_template_errors_with_species() 0 13 1
A ExcelTemplateReaderTestCase.test_column_not_found() 0 13 1
A ExcelTemplateReaderTestCase.test_check_accuracies_issue() 0 63 1
A ExcelMixin.test_upload_template() 0 17 1
A ExcelMixin.check_errors() 0 7 1
A UpdateTemplateTestCase.setUp() 21 21 1
A UpdateTemplateTestCase.test_upload_template() 35 35 1
A UploadTemplateTestCase.test_issue_with_relationship() 0 38 1

How to fix   Duplicated Code   

Duplicated Code

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:

1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
"""
4
Created on Tue Jul  2 10:58:42 2019
5
6
@author: Paolo Cozzi <[email protected]>
7
"""
8
9
import types
10
from collections import defaultdict, namedtuple
11
from unittest.mock import patch, Mock
12
13
from django.test import TestCase
14
15
from common.tests import (
16
    WebSocketMixin, DataSourceMixinTestCase as CommonDataSourceMixinTest)
17
from uid.models import Animal, Sample, Submission
18
from uid.tests.mixins import (
19
    DataSourceMixinTestCase, FileReaderMixinTestCase)
20
21
from ..helpers import (
22
    ExcelTemplateReader, upload_template, TEMPLATE_COLUMNS, ExcelImportError)
23
from .common import BaseExcelMixin
24
25
26
class ExcelTemplateReaderTestCase(
27
        FileReaderMixinTestCase, DataSourceMixinTestCase, BaseExcelMixin,
28
        TestCase):
29
    """Test excel class upload"""
30
31
    def setUp(self):
32
        # calling my base class setup
33
        super().setUp()
34
35
        self.maxDiff = None
36
37
        # crate a Excel Template object
38
        self.reader = ExcelTemplateReader()
39
40
        # get filenames for DataSourceMixinTestCase.dst_path
41
        self.reader.read_file(self.dst_path)
42
43
    def test_check_sheets(self):
44
        # test check sheets method
45
        status, not_found = self.reader.check_sheets()
46
47
        self.assertTrue(status)
48
        self.assertEqual(not_found, [])
49
50
        # override sheet names
51
        self.reader.sheet_names = []
52
53
        # check method
54
        status, not_found = self.reader.check_sheets()
55
56
        self.assertFalse(status)
57
        self.assertEqual(not_found, ['breed', 'animal', 'sample'])
58
59
    def test_check_columns(self):
60
        # test check sheets method
61
        status, not_found = self.reader.check_columns()
62
63
        self.assertTrue(status)
64
        self.assertIsInstance(not_found, defaultdict)
65
        self.assertEqual(len(not_found), 0)
66
67
    @patch('xlrd.open_workbook')
68
    def test_check_columns_issue(self, mock_open):
69
        """Test a file with columns issues"""
70
71
        # creating a mock excel book
72
        mock_book = Mock()
73
74
        # customizing the mock object
75
        mock_book.sheet_names.return_value = ['breed', 'animal', 'sample']
76
77
        # creating a mock sheet for breed
78
        breed_sheet = Mock()
79
        breed_sheet.nrows = 1
80
81
        # now setting rows to a fake sheet
82
        breed_sheet.row_values.return_value = []
83
84
        # creating a mock sheet for animal
85
        animal_sheet = Mock()
86
        animal_sheet.nrows = 1
87
88
        # now setting rows to a fake sheet
89
        animal_sheet.row_values.return_value = []
90
91
        # creating a mock sheet for sample
92
        sample_sheet = Mock()
93
        sample_sheet.nrows = 1
94
95
        # now setting rows to a fake sheet
96
        sample_sheet.row_values.return_value = []
97
98
        # finally setting sheet to the fabe excel object
99
        mock_book.sheet_by_name.side_effect = [
100
            breed_sheet, animal_sheet, sample_sheet]
101
102
        # returning the mock object when opening a workbook
103
        mock_open.return_value = mock_book
104
105
        # now calling methods
106
        reader = ExcelTemplateReader()
107
        reader.read_file("fake file")
108
109
        # create the reference error output
110
        reference = defaultdict(list)
111
        reference['breed'] += TEMPLATE_COLUMNS['breed']
112
        reference['animal'] += TEMPLATE_COLUMNS['animal']
113
        reference['sample'] += TEMPLATE_COLUMNS['sample']
114
115
        status, test = reader.check_columns()
116
117
        self.assertFalse(status)
118
        self.assertIsInstance(test, defaultdict)
119
        self.assertDictEqual(reference, test)
120
121
    def check_generator(self, records, length):
122
        self.assertIsInstance(records, types.GeneratorType)
123
        self.assertEqual(len(list(records)), length)
124
125
    def test_get_breed_records(self):
126
        """get_breed_records returns an iterator"""
127
128
        breeds = self.reader.get_breed_records()
129
        self.check_generator(breeds, 2)
130
131
    def test_get_animal_records(self):
132
        """get_animal_records returns an iterator"""
133
134
        animals = self.reader.get_animal_records()
135
        self.check_generator(animals, 3)
136
137
    def test_get_sample_records(self):
138
        """get_sample_records returns an iterator"""
139
140
        samples = self.reader.get_sample_records()
141
        self.check_generator(samples, 3)
142
143
    def test_check_accuracies(self):
144
        """Test check accuracies method"""
145
146
        check, not_found = self.reader.check_accuracies()
147
148
        self.assertTrue(check)
149
        self.assertEqual(len(not_found), 0)
150
151
    @patch.dict("excel.helpers.exceltemplate.TEMPLATE_COLUMNS",
152
                {'breed': ["a column"]})
153
    def test_column_not_found(self):
154
        """Test a column not found raise an informativa exception"""
155
156
        # get a generator object
157
        generator = self.reader.get_sheet_records('breed')
158
159
        self.assertRaisesRegex(
160
            ExcelImportError,
161
            "Column 'a column' not found in 'breed' sheet",
162
            list,
163
            generator)
164
165
    @patch('xlrd.open_workbook')
166
    def test_check_accuracies_issue(self, mock_open):
167
        """Checking issues with accuracy in excels data"""
168
169
        # creating a mock excel book
170
        mock_book = Mock()
171
172
        # customizing the mock object
173
        mock_book.sheet_names.return_value = ['breed', 'animal', 'sample']
174
175
        # creating a mock sheet for animal
176
        animal_sheet = Mock()
177
        animal_sheet.nrows = 2
178
179
        # creating a fake row of data
180
        fake_row = ["" for col in TEMPLATE_COLUMNS['animal']]
181
182
        # get birth location accuracy index
183
        accuracy_idx = TEMPLATE_COLUMNS['animal'].index(
184
            "Birth location accuracy")
185
186
        # set a fake accuracy item
187
        fake_row[accuracy_idx] = "Fake"
188
189
        # now setting rows to a fake sheet
190
        animal_sheet.row_values.side_effect = [
191
            TEMPLATE_COLUMNS['animal'],
192
            fake_row]
193
194
        # creating a mock sheet for sample
195
        sample_sheet = Mock()
196
        sample_sheet.nrows = 2
197
198
        # creating a fake row of data
199
        fake_row = ["" for col in TEMPLATE_COLUMNS['sample']]
200
201
        # get birth location accuracy index
202
        accuracy_idx = TEMPLATE_COLUMNS['sample'].index(
203
            "Collection place accuracy")
204
205
        # set a fake accuracy item
206
        fake_row[accuracy_idx] = "Fake"
207
208
        # now setting rows to a fake sheet
209
        sample_sheet.row_values.side_effect = [
210
            TEMPLATE_COLUMNS['sample'],
211
            fake_row]
212
213
        # finally setting sheet to the fabe excel object
214
        mock_book.sheet_by_name.side_effect = [animal_sheet, sample_sheet]
215
216
        # returning the mock object when opening a workbook
217
        mock_open.return_value = mock_book
218
219
        # now calling methods
220
        reader = ExcelTemplateReader()
221
        reader.read_file("fake file")
222
223
        # define the expected value
224
        reference = (False, set(["Fake"]))
225
        test = reader.check_accuracies()
226
227
        self.assertEqual(reference, test)
228
229
    @patch('excel.helpers.ExcelTemplateReader.get_animal_records')
230
    def test_species_in_animal_and_breeds_differ(self, my_animal):
231
        # creating a fake row of data
232
        fake_row = ["" for col in TEMPLATE_COLUMNS['animal']]
233
234
        # get birth location accuracy index
235
        specie_idx = TEMPLATE_COLUMNS['animal'].index(
236
            "Species")
237
238
        # set a fake specie item
239
        fake_row[specie_idx] = "Fake"
240
241
        # create a namedtuple object
242
        columns = [col.lower().replace(" ", "_")
243
                   for col in TEMPLATE_COLUMNS['animal']]
244
        Record = namedtuple("Animal", columns)
245
        record = Record._make(fake_row)
246
        my_animal.return_value = [record]
247
248
        # define the expected value
249
        reference = (False, ["Fake"])
250
        test = self.reader.check_species_in_animal_sheet()
251
252
        self.assertEqual(reference, test)
253
254
255
class ExcelMixin(DataSourceMixinTestCase, WebSocketMixin, BaseExcelMixin):
256
    """Common tests for Excel classes"""
257
258
    # define the method to upload data from. Since the function is now inside
259
    # a class it becomes a method, specifically a bound method and is supposed
260
    # to receive the self attribute by default. If we don't want to get the
261
    # self attribute, we have to declare function as a staticmetho
262
    # https://stackoverflow.com/a/35322635/4385116
263
    upload_method = staticmethod(upload_template)
264
265
    def test_upload_template(self):
266
        """Testing uploading and importing data from excel template to UID"""
267
268
        # test data loaded
269
        message = "Template import completed for submission"
270
        self.upload_datasource(message)
271
272
        # check async message called
273
        notification_message = (
274
            'Template import completed for submission: 1')
275
        validation_message = {
276
            'animals': 3, 'samples': 3,
277
            'animal_unkn': 3, 'sample_unkn': 3,
278
            'animal_issues': 0, 'sample_issues': 0}
279
280
        # check async message called using WebSocketMixin.check_message
281
        self.check_message('Loaded', notification_message, validation_message)
282
283
    def check_errors(self, my_check, message, notification_message):
284
        """Common stuff for error in excel template loading"""
285
286
        super().check_errors(my_check, message)
287
288
        # check async message called using WebSocketMixin.check_message
289
        self.check_message('Error', notification_message)
290
291
292
class UploadTemplateTestCase(ExcelMixin, TestCase):
293
    """Test uploading data for Template excel path"""
294
295
    @patch("excel.helpers.ExcelTemplateReader.check_species",
296
           return_value=[False, 'Rainbow trout'])
297
    def test_upload_template_errors_with_species(self, my_check):
298
        """Testing importing with data into UID with errors in species"""
299
300
        message = "species are not loaded into database"
301
        notification_message = (
302
            "Error in importing data: Some species "
303
            "are not loaded into database: check "
304
            "for 'Rainbow trout' in your dataset")
305
306
        # check template import fails
307
        self.check_errors(my_check, message, notification_message)
308
309
    @patch("excel.helpers.ExcelTemplateReader.check_species_in_animal_sheet",
310
           return_value=[False, 'Rainbow trout'])
311
    def test_upload_template_errors_with_species_in_animal(self, my_check):
312
        """Testing importing with data into UID with errors animal"""
313
314
        message = "Some species are not defined in breed sheet"
315
        notification_message = (
316
            "Error in importing data: %s: check for 'Rainbow trout' "
317
            "in your dataset" % message)
318
319
        # check template import fails
320
        self.check_errors(my_check, message, notification_message)
321
322
    @patch("excel.helpers.ExcelTemplateReader.check_sex",
323
           return_value=[False, 'unknown'])
324
    def test_upload_template_errors_with_sex(self, my_check):
325
        """Testing importing with data into UID with errors"""
326
327
        message = "Not all Sex terms are loaded into database"
328
        notification_message = (
329
            "Error in importing data: Not all Sex "
330
            "terms are loaded into database: check "
331
            "for 'unknown' in your dataset")
332
333
        # check template import fails
334
        self.check_errors(my_check, message, notification_message)
335
336
    @patch("excel.helpers.ExcelTemplateReader.check_accuracies",
337
           return_value=(False, set(["Fake"])))
338
    def test_upload_template_errors_with_accuracies(self, my_check):
339
        """Testing importing with data into UID with errors"""
340
341
        message = "Not all accuracy levels are defined in database"
342
        notification_message = (
343
            "Error in importing data: Not all accuracy "
344
            "levels are defined in database: check "
345
            "for '{'Fake'}' in your dataset")
346
347
        # check template import fails
348
        self.check_errors(my_check, message, notification_message)
349
350
    @patch("excel.helpers.ExcelTemplateReader.check_countries",
351
           return_value=(False, set(["Fake"])))
352
    def test_upload_template_errors_with_countries(self, my_check):
353
        """Testing importing with data into UID with errors"""
354
355
        message = "Those countries are not loaded in database"
356
        notification_message = (
357
            "Error in importing data: %s: check for '{'Fake'}' "
358
            "in your dataset" % message)
359
360
        # check template import fails
361
        self.check_errors(my_check, message, notification_message)
362
363
    @patch("excel.helpers.fill_uid.image_timedelta",
364
           side_effect=ValueError("message"))
365
    def test_issue_animal_age_at_collection(self, my_check):
366
        """Test an issue in animal_age_at_collection column"""
367
368
        message = "message"
369
370
        notification_message = (
371
            "Error in importing data: Error for Sample '%s' at "
372
            "animal_age_at_collection column: %s" % (
373
                "CS05_1999_IBBACNR_PTP_02.06.1999", message))
374
375
        # check template import fails, calling a parent method since
376
        # database is filled and the base ExcelMixin doesn't work
377
        CommonDataSourceMixinTest.check_errors(
378
            self, my_check, message)
379
380
        # check async message called using WebSocketMixin.check_message
381
        self.check_message('Error', notification_message)
382
383
    @patch("excel.helpers.fill_uid.parse_image_timedelta",
384
           side_effect=[[None, None], ValueError("message")])
385
    def test_issue_sampling_to_preparation_interval(self, my_check):
386
        """Test an issue in animal_age_at_collection column"""
387
388
        message = "message"
389
390
        notification_message = (
391
            "Error in importing data: Error for Sample '%s' at sampling_to_"
392
            "preparation_interval column: %s" % (
393
                "VERCH1539971_2010_RegLomBank_PTPLodi_03.09.2011", message))
394
395
        # check template import fails, calling a parent method since
396
        # database is filled and the base ExcelMixin doesn't work
397
        CommonDataSourceMixinTest.check_errors(
398
            self, my_check, message)
399
400
        # check async message called using WebSocketMixin.check_message
401
        self.check_message('Error', notification_message)
402
403
    @patch('excel.helpers.ExcelTemplateReader.get_animal_records')
404
    def test_issue_with_relationship(self, my_check):
405
        """Test a relationship that don't exists"""
406
407
        # create a fake record with wrong relationship for testing
408
        fake_data = [
409
            'ANIMAL:::ID:::CS05_1999',
410
            None,
411
            855,
412
            "fake father",
413
            "fake mother",
414
            'Cinta Senese',
415
            'Pig',
416
            'male',
417
            None,
418
            None,
419
            None,
420
            None,
421
            'missing geographic information']
422
423
        Animal = namedtuple(
424
            'Animal',
425
            [column.lower().replace(" ", "_")
426
             for column in TEMPLATE_COLUMNS['animal']]
427
        )
428
429
        record = Animal._make(fake_data)
430
431
        my_check.return_value = [record]
432
433
        # ok now test method
434
        message = "Unknown parent '%s'" % (fake_data[3])
435
        notification_message = (
436
            "Error in importing data: %s: check animal '%s' "
437
            "in your dataset" % (message, fake_data[0]))
438
439
        # check template import fails
440
        self.check_errors(my_check, message, notification_message)
441
442
443
class ReloadTemplateTestCase(ExcelMixin, TestCase):
444
    """Simulate a template reload case. Load data as in
445
    UploadTemplateTestCase, then call test which reload the same data"""
446
447
    # override used fixtures
448
    fixtures = [
449
        'crbanim/auth',
450
        'excel/dictspecie',
451
        'excel/uid',
452
        'excel/submission',
453
        'excel/speciesynonym'
454
    ]
455
456
457 View Code Duplication
class UpdateTemplateTestCase(ExcelMixin, TestCase):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
458
    """Simulate an excel update with the same dataset. Data already
459
    present will be ignored."""
460
461
    # override used fixtures
462
    fixtures = [
463
        'crbanim/auth',
464
        'excel/dictspecie',
465
        'excel/uid',
466
        'excel/submission',
467
        'excel/speciesynonym'
468
    ]
469
470
    def setUp(self):
471
        # calling my base class setup
472
        super().setUp()
473
474
        # track old submission
475
        self.old_submission = Submission.objects.get(pk=1)
476
477
        # generate a new submission from old submission object
478
        submission = self.submission
479
        submission.pk = None
480
        submission.title = "Updated database"
481
        submission.datasource_version = "Updated database"
482
        submission.save()
483
484
        # track the new submission
485
        self.submission = submission
486
487
        # remove items from database
488
        sample = Sample.objects.get(pk=6)
489
        animal = sample.animal
490
        animal.delete()
491
492
    def test_upload_template(self):
493
        """Testing uploading and importing data from excel template to UID"""
494
495
        # test data loaded
496
        message = "Template import completed for submission"
497
        self.upload_datasource(message)
498
499
        # check animal and sample
500
        queryset = Animal.objects.all()
501
        self.assertEqual(len(queryset), 3, msg="check animal load")
502
503
        queryset = Sample.objects.all()
504
        self.assertEqual(len(queryset), 3, msg="check sample load")
505
506
        # assert data are in the proper submission
507
        self.assertEqual(self.old_submission.animal_set.count(), 2)
508
        self.assertEqual(self.old_submission.sample_set.count(), 2)
509
510
        self.assertEqual(self.submission.animal_set.count(), 1)
511
        self.assertEqual(self.submission.sample_set.count(), 1)
512
513
        # check async message called
514
        notification_message = (
515
            'Template import completed for submission: 2')
516
        validation_message = {
517
            'animals': 1, 'samples': 1,
518
            'animal_unkn': 1, 'sample_unkn': 1,
519
            'animal_issues': 0, 'sample_issues': 0}
520
521
        # check async message called using WebSocketMixin.check_message
522
        self.check_message(
523
            'Loaded',
524
            notification_message,
525
            validation_message,
526
            pk=self.submission.id)
527