Passed
Pull Request — devel (#72)
by Paolo
06:50
created

ExcelTemplateReaderTestCase.test_column_not_found()   A

Complexity

Conditions 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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