Completed
Push — develop ( c3c8a7...44d282 )
by Jace
13s
created

TestPublisher.test_publish_empty_tree()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
"""Tests for the doorstop.core package."""
2
3
import unittest
4
from unittest.mock import patch, Mock
5
6
import os
7
import csv
8
import tempfile
9
import shutil
10
import pprint
11
import logging
12
import warnings
13
14
import yaml
15
import openpyxl
16
17
from doorstop import common
18
from doorstop.common import DoorstopError, DoorstopWarning, DoorstopInfo
19
from doorstop import core
20
from doorstop.core.builder import _get_tree, _clear_tree
21
from doorstop.core.vcs import mockvcs
22
23
from doorstop.core.test import ENV, REASON, ROOT, FILES, EMPTY, SYS
24
from doorstop.core.test import DocumentNoSkip
25
26
# Whenever the export format is changed:
27
#  1. set CHECK_EXPORTED_CONTENT to False
28
#  2. re-run all tests
29
#  3. manually verify the newly exported content is correct
30
#  4. set CHECK_EXPORTED_CONTENT to True
31
CHECK_EXPORTED_CONTENT = True
32
33
# Whenever the publish format is changed:
34
#  1. set CHECK_PUBLISHED_CONTENT to False
35
#  2. re-run all tests
36
#  3. manually verify the newly published content is correct
37
#  4. set CHECK_PUBLISHED_CONTENT to True
38
CHECK_PUBLISHED_CONTENT = True
39
40
41
class TestItem(unittest.TestCase):
42
    """Integration tests for the Item class."""
43
44
    def setUp(self):
45
        self.path = os.path.join(FILES, 'REQ001.yml')
46
        self.backup = common.read_text(self.path)
47
        self.item = core.Item(self.path)
48
        self.item.tree = Mock()
49
        self.item.tree.vcs = mockvcs.WorkingCopy(EMPTY)
50
51
    def tearDown(self):
52
        common.write_text(self.backup, self.path)
53
54
    def test_save_load(self):
55
        """Verify an item can be saved and loaded from a file."""
56
        self.item.level = '1.2.3'
57
        self.item.text = "Hello, world!"
58
        self.item.links = ['SYS001', 'SYS002']
59
        item2 = core.Item(os.path.join(FILES, 'REQ001.yml'))
60
        self.assertEqual((1, 2, 3), item2.level)
61
        self.assertEqual("Hello, world!", item2.text)
62
        self.assertEqual(['SYS001', 'SYS002'], item2.links)
63
64
    @unittest.skipUnless(os.getenv(ENV), REASON)
65
    def test_find_ref(self):
66
        """Verify an item's external reference can be found."""
67
        item = core.Item(os.path.join(FILES, 'REQ003.yml'))
68
        item.tree = Mock()
69
        item.tree.vcs = mockvcs.WorkingCopy(ROOT)
70
        path, line = item.find_ref()
71
        relpath = os.path.relpath(os.path.join(FILES, 'external', 'text.txt'),
72
                                  ROOT)
73
        self.assertEqual(relpath, path)
74
        self.assertEqual(3, line)
75
76
    def test_find_ref_error(self):
77
        """Verify an error occurs when no external reference found."""
78
        self.item.ref = "not" "found"  # space avoids self match
79
        self.assertRaises(DoorstopError, self.item.find_ref)
80
81
82
class TestDocument(unittest.TestCase):
83
    """Integration tests for the Document class."""
84
85
    def setUp(self):
86
        self.document = core.Document(FILES, root=ROOT)
87
88
    def tearDown(self):
89
        """Clean up temporary files."""
90
        for filename in os.listdir(EMPTY):
91
            path = os.path.join(EMPTY, filename)
92
            common.delete(path)
93
94
    def test_load(self):
95
        """Verify a document can be loaded from a directory."""
96
        doc = core.Document(FILES)
97
        self.assertEqual('REQ', doc.prefix)
98
        self.assertEqual(2, doc.digits)
99
        self.assertEqual(5, len(doc.items))
100
101
    def test_new(self):
102
        """Verify a new document can be created."""
103
        document = core.Document.new(None,
104
                                     EMPTY, FILES,
105
                                     prefix='SYS', digits=4)
106
        self.assertEqual('SYS', document.prefix)
107
        self.assertEqual(4, document.digits)
108
        self.assertEqual(0, len(document.items))
109
110
    @patch('doorstop.settings.REORDER', False)
111
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
112
    def test_validate(self):
113
        """Verify a document can be validated."""
114
        self.assertTrue(self.document.validate())
115
116
    @patch('doorstop.settings.REORDER', False)
117
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
118
    def test_issues_count(self):
119
        """Verify a number of issues are found in a document."""
120
        issues = self.document.issues
121
        for issue in self.document.issues:
122
            logging.info(repr(issue))
123
        self.assertEqual(12, len(issues))
124
125
    @patch('doorstop.settings.REORDER', False)
126
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
127
    def test_issues_duplicate_level(self):
128
        """Verify duplicate item levels are detected."""
129
        expect = DoorstopWarning("duplicate level: 2.1 (REQ002, REQ2-001)")
130
        for issue in self.document.issues:
131
            logging.info(repr(issue))
132
            if type(issue) == type(expect) and issue.args == expect.args:
133
                break
134
        else:
135
            self.fail("issue not found: {}".format(expect))
136
137
    @patch('doorstop.settings.REORDER', False)
138
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
139
    def test_issues_skipped_level(self):
140
        """Verify skipped item levels are detected."""
141
        expect = DoorstopInfo("skipped level: 1.4 (REQ003), 1.6 (REQ004)")
142
        for issue in self.document.issues:
143
            logging.info(repr(issue))
144
            if type(issue) == type(expect) and issue.args == expect.args:
145
                break
146
        else:
147
            self.fail("issue not found: {}".format(expect))
148
149
    def test_add_item_with_reordering(self):
150
        """Verify an item can be inserted into a document."""
151
        document = core.Document.new(None,
152
                                     EMPTY, FILES,
153
                                     prefix='TMP')
154
        item_1_0 = document.add_item()
155
        item_1_2 = document.add_item()  # will get displaced
156
        item_1_1 = document.add_item(level='1.1')
157
        self.assertEqual((1, 0), item_1_0.level)
158
        self.assertEqual((1, 1), item_1_1.level)
159
        self.assertEqual((1, 2), item_1_2.level)
160
161
    def test_remove_item_with_reordering(self):
162
        """Verify an item can be removed from a document."""
163
        document = core.Document.new(None,
164
                                     EMPTY, FILES,
165
                                     prefix='TMP')
166
        item_1_0 = document.add_item()
167
        item_1_2 = document.add_item()  # to be removed
168
        item_1_1 = document.add_item(level='1.1')  # will get relocated
169
        document.remove_item(item_1_2)
170
        self.assertEqual((1, 0), item_1_0.level)
171
        self.assertEqual((1, 1), item_1_1.level)
172
173 View Code Duplication
    def test_reorder(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
174
        """Verify a document's order can be corrected."""
175
        document = core.Document.new(None,
176
                                     EMPTY, FILES,
177
                                     prefix='TMP')
178
        document.add_item(level='2.0', reorder=False)
179
        document.add_item(level='2.1', reorder=False)
180
        document.add_item(level='2.1', reorder=False)
181
        document.add_item(level='2.5', reorder=False)
182
        document.add_item(level='4.5', reorder=False)
183
        document.add_item(level='4.7', reorder=False)
184
        document.reorder()
185
        expected = [(2, 0), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2)]
186
        actual = [item.level for item in document.items]
187
        self.assertListEqual(expected, actual)
188
189
    def test_reorder_with_keep(self):
190
        """Verify a document's order can be corrected with a kept level."""
191
        document = core.Document.new(None,
192
                                     EMPTY, FILES,
193
                                     prefix='TMP')
194
        document.add_item(level='1.0', reorder=False)
195
        item = document.add_item(level='1.0', reorder=False)
196
        document.add_item(level='1.0', reorder=False)
197
        document.reorder(keep=item)
198
        expected = [(1, 0), (2, 0), (3, 0)]
199
        actual = [i.level for i in document.items]
200
        self.assertListEqual(expected, actual)
201
        self.assertEqual((1, 0), item.level)
202
203 View Code Duplication
    def test_reorder_with_start(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
204
        """Verify a document's order can be corrected with a given start."""
205
        document = core.Document.new(None,
206
                                     EMPTY, FILES,
207
                                     prefix='TMP')
208
        document.add_item(level='2.0', reorder=False)
209
        document.add_item(level='2.1', reorder=False)
210
        document.add_item(level='2.1', reorder=False)
211
        document.add_item(level='2.5', reorder=False)
212
        document.add_item(level='4.0', reorder=False)
213
        document.add_item(level='4.7', reorder=False)
214
        document.reorder(start=(1, 0))
215
        expected = [(1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1)]
216
        actual = [item.level for item in document.items]
217
        self.assertListEqual(expected, actual)
218
219 View Code Duplication
    @patch('doorstop.settings.REORDER', True)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
220
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
221
    def test_validate_with_reordering(self):
222
        """Verify a document's order is corrected during validation."""
223
        document = core.Document.new(None,
224
                                     EMPTY, FILES,
225
                                     prefix='TMP')
226
        document.add_item(level='1.0', reorder=False)
227
        document.add_item(level='1.1', reorder=False)
228
        document.add_item(level='1.2.0', reorder=False)
229
        document.add_item(level='1.2.5', reorder=False)
230
        document.add_item(level='3.2.1', reorder=False)
231
        document.add_item(level='3.3', reorder=False)
232
        self.assertTrue(document.validate())
233
        expected = [(1, 0), (1, 1), (1, 2, 0), (1, 2, 1), (2, 1, 1), (2, 2)]
234
        actual = [item.level for item in document.items]
235
        self.assertListEqual(expected, actual)
236
237
238
class TestTree(unittest.TestCase):
239
    """Integration tests for the core.Tree class."""
240
241
    def setUp(self):
242
        self.path = os.path.join(FILES, 'REQ001.yml')
243
        self.backup = common.read_text(self.path)
244
        self.item = core.Item(self.path)
245
        self.tree = core.Tree(core.Document(SYS))
246
        self.tree._place(core.Document(FILES))  # pylint: disable=W0212
247
248
    def tearDown(self):
249
        common.write_text(self.backup, self.path)
250
251
    @patch('doorstop.settings.REORDER', False)
252
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
253
    @patch('doorstop.settings.STAMP_NEW_LINKS', False)
254
    @patch('doorstop.settings.CHECK_REF', False)
255
    def test_issues_count(self):
256
        """Verify a number of issues are found in a tree."""
257
        issues = self.tree.issues
258
        for issue in self.tree.issues:
259
            logging.info(repr(issue))
260
        self.assertEqual(14, len(issues))
261
262
    @patch('doorstop.settings.REORDER', False)
263
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
264
    @patch('doorstop.settings.STAMP_NEW_LINKS', False)
265
    @patch('doorstop.settings.CHECK_REF', False)
266
    def test_issues_count_with_skips(self):
267
        """Verify a document can be skipped during validation."""
268
        issues = list(self.tree.get_issues(skip=['req']))
269
        for issue in self.tree.issues:
270
            logging.info(repr(issue))
271
        self.assertEqual(2, len(issues))
272
273
    @patch('doorstop.settings.REORDER', False)
274
    @patch('doorstop.settings.STAMP_NEW_LINKS', False)
275
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
276
    @patch('doorstop.core.document.Document', DocumentNoSkip)
277
    def test_validate_invalid_link(self):
278
        """Verify a tree is invalid with a bad link."""
279
        self.item.link('SYS003')
280
        tree = core.build(FILES, root=FILES)
281
        self.assertIsInstance(tree, core.Tree)
282
        self.assertFalse(tree.validate())
283
284
    @patch('doorstop.settings.REORDER', False)
285
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
286
    def test_validate_long(self):
287
        """Verify trees can be checked."""
288
        logging.info("tree: {}".format(self.tree))
0 ignored issues
show
introduced by
Use formatting in logging functions but pass the parameters as arguments
Loading history...
289
        self.assertTrue(self.tree.validate())
290
291
292
@unittest.skipUnless(os.getenv(ENV), REASON)
293
class TestEditor(unittest.TestCase):
294
    """Integrations tests for the editor module."""
295
296
297
class TestImporter(unittest.TestCase):
0 ignored issues
show
best-practice introduced by
Too many instance attributes (8/7)
Loading history...
298
    """Integrations tests for the importer module."""
299
300
    def setUp(self):
301
        # Create a temporary mock working copy
302
        self.cwd = os.getcwd()
303
        self.temp = tempfile.mkdtemp()
304
        os.chdir(self.temp)
305
        common.touch('.mockvcs')
306
        # Create default document attributes
307
        self.prefix = 'PREFIX'
308
        self.root = self.temp
309
        self.path = os.path.join(self.root, 'DIRECTORY')
310
        self.parent = 'PARENT_PREFIX'
311
        # Create default item attributes
312
        self.uid = 'PREFIX-00042'
313
        # Load an actual document
314
        self.document = core.Document(FILES, root=ROOT)
315
        # Ensure the tree is reloaded
316
        _clear_tree()
317
318
    def tearDown(self):
319
        os.chdir(self.cwd)
320
        shutil.rmtree(self.temp)
321
322
    def test_import_yml(self):
323
        """Verify items can be imported from a YAML file."""
324
        path = os.path.join(self.temp, 'exported.yml')
325
        core.exporter.export(self.document, path)
326
        _path = os.path.join(self.temp, 'imports', 'req')
327
        _tree = _get_tree()
328
        document = _tree.create_document(_path, 'REQ')
329
        # Act
330
        core.importer.import_file(path, document)
331
        # Assert
332
        expected = [item.data for item in self.document.items]
333
        actual = [item.data for item in document.items]
334
        log_data(expected, actual)
335
        self.assertListEqual(expected, actual)
336
337 View Code Duplication
    def test_import_csv(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
338
        """Verify items can be imported from a CSV file."""
339
        path = os.path.join(self.temp, 'exported.csv')
340
        core.exporter.export(self.document, path)
341
        _path = os.path.join(self.temp, 'imports', 'req')
342
        _tree = _get_tree()
343
        document = _tree.create_document(_path, 'REQ')
344
        # Act
345
        core.importer.import_file(path, document)
346
        # Assert
347
        expected = [item.data for item in self.document.items]
348
        actual = [item.data for item in document.items]
349
        log_data(expected, actual)
350
        self.assertListEqual(expected, actual)
351
352
    def test_import_tsv(self):
353
        """Verify items can be imported from a TSV file."""
354
        path = os.path.join(self.temp, 'exported.tsv')
355
        core.exporter.export(self.document, path)
356
        _path = os.path.join(self.temp, 'imports', 'req')
357
        _tree = _get_tree()
358
        document = _tree.create_document(_path, 'REQ')
359
        # Act
360
        core.importer.import_file(path, document)
361
        # Assert
362
        expected = [item.data for item in self.document.items]
363
        actual = [item.data for item in document.items]
364
        log_data(expected, actual)
365
        self.assertListEqual(expected, actual)
366
367 View Code Duplication
    @unittest.skipUnless(os.getenv(ENV), REASON)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
368
    def test_import_xlsx(self):
369
        """Verify items can be imported from an XLSX file."""
370
        path = os.path.join(self.temp, 'exported.xlsx')
371
        core.exporter.export(self.document, path)
372
        _path = os.path.join(self.temp, 'imports', 'req')
373
        _tree = _get_tree()
374
        document = _tree.create_document(_path, 'REQ')
375
        # Act
376
        core.importer.import_file(path, document)
377
        # Assert
378
        expected = [item.data for item in self.document.items]
379
        actual = [item.data for item in document.items]
380
        log_data(expected, actual)
381
        self.assertListEqual(expected, actual)
382
383
    # TODO: determine when this test should be run (if at all)
384
    # currently, 'TEST_LONG' isn't set under any condition
385
    @unittest.skipUnless(os.getenv(ENV), REASON)
386
    @unittest.skipUnless(os.getenv('TEST_LONG'), "this test takes too long")
387
    @unittest.skipIf(os.getenv('TRAVIS'), "this test takes too long")
388
    def test_import_xlsx_huge(self):
389
        """Verify huge XLSX files are handled."""
390
        path = os.path.join(FILES, 'exported-huge.xlsx')
391
        _path = os.path.join(self.temp, 'imports', 'req')
392
        _tree = _get_tree()
393
        document = _tree.create_document(_path, 'REQ')
394
        # Act
395
        with warnings.catch_warnings(record=True) as warns:
396
            core.importer.import_file(path, document)
397
            # Assert
398
        self.assertEqual(1, len(warns))
399
        self.assertIn("maximum number of rows", str(warns[-1].message))
400
        expected = []
401
        actual = [item.data for item in document.items]
402
        log_data(expected, actual)
403
        self.assertListEqual(expected, actual)
404
405
    def test_create_document(self):
406
        """Verify a new document can be created to import items."""
407
        document = core.importer.create_document(self.prefix, self.path)
408
        self.assertEqual(self.prefix, document.prefix)
409
        self.assertEqual(self.path, document.path)
410
411
    def test_create_document_with_unknown_parent(self):
412
        """Verify a new document can be created with an unknown parent."""
413
        # Verify the document does not already exist
414
        self.assertRaises(DoorstopError, core.find_document, self.prefix)
415
        # Import a document
416
        document = core.importer.create_document(self.prefix, self.path,
417
                                                 parent=self.parent)
418
        # Verify the imported document's attributes are correct
419
        self.assertEqual(self.prefix, document.prefix)
420
        self.assertEqual(self.path, document.path)
421
        self.assertEqual(self.parent, document.parent)
422
        # Verify the imported document can be found
423
        document2 = core.find_document(self.prefix)
424
        self.assertIs(document, document2)
425
426
    def test_create_document_already_exists(self):
427
        """Verify non-parent exceptions are re-raised."""
428
        # Create a document
429
        core.importer.create_document(self.prefix, self.path)
430
        # Attempt to create the same document
431
        self.assertRaises(DoorstopError, core.importer.create_document,
432
                          self.prefix, self.path)
433
434
    def test_add_item(self):
435
        """Verify an item can be imported into a document."""
436
        # Create a document
437
        core.importer.create_document(self.prefix, self.path)
438
        # Verify the item does not already exist
439
        self.assertRaises(DoorstopError, core.find_item, self.uid)
440
        # Import an item
441
        item = core.importer.add_item(self.prefix, self.uid)
442
        # Verify the item's attributes are correct
443
        self.assertEqual(self.uid, item.uid)
444
        # Verify the item can be found
445
        item2 = core.find_item(self.uid)
446
        self.assertIs(item, item2)
447
        # Verify the item is contained in the document
448
        document = core.find_document(self.prefix)
449
        self.assertIn(item, document.items)
450
451
    def test_add_item_with_attrs(self):
452
        """Verify an item with attributes can be imported into a document."""
453
        # Create a document
454
        core.importer.create_document(self.prefix, self.path)
455
        # Import an item
456
        attrs = {'text': "Item text", 'ext1': "Extended 1"}
457
        item = core.importer.add_item(self.prefix, self.uid,
458
                                      attrs=attrs)
459
        # Verify the item is correct
460
        self.assertEqual(self.uid, item.uid)
461
        self.assertEqual(attrs['text'], item.text)
462
        self.assertEqual(attrs['ext1'], item.get('ext1'))
463
464
465
class TestExporter(unittest.TestCase):
466
    """Integration tests for the doorstop.core.exporter module."""
467
468
    maxDiff = None
469
470
    def setUp(self):
471
        self.document = core.Document(FILES, root=ROOT)
472
        self.temp = tempfile.mkdtemp()
473
474
    def tearDown(self):
475
        shutil.rmtree(self.temp)
476
477
    def test_export_yml(self):
478
        """Verify a document can be exported as a YAML file."""
479
        path = os.path.join(FILES, 'exported.yml')
480
        temp = os.path.join(self.temp, 'exported.yml')
481
        expected = read_yml(path)
482
        # Act
483
        path2 = core.exporter.export(self.document, temp)
484
        # Assert
485
        self.assertIs(temp, path2)
486
        if CHECK_EXPORTED_CONTENT:
487
            actual = read_yml(temp)
488
            self.assertEqual(expected, actual)
489
        move_file(temp, path)
490
491
    def test_export_csv(self):
492
        """Verify a document can be exported as a CSV file."""
493
        path = os.path.join(FILES, 'exported.csv')
494
        temp = os.path.join(self.temp, 'exported.csv')
495
        expected = read_csv(path)
496
        # Act
497
        path2 = core.exporter.export(self.document, temp)
498
        # Assert
499
        self.assertIs(temp, path2)
500
        if CHECK_EXPORTED_CONTENT:
501
            actual = read_csv(temp)
502
            self.assertEqual(expected, actual)
503
        move_file(temp, path)
504
505
    @patch('doorstop.settings.REVIEW_NEW_ITEMS', False)
506
    def test_export_tsv(self):
507
        """Verify a document can be exported as a TSV file."""
508
        path = os.path.join(FILES, 'exported.tsv')
509
        temp = os.path.join(self.temp, 'exported.tsv')
510
        expected = read_csv(path, delimiter='\t')
511
        # Act
512
        path2 = core.exporter.export(self.document, temp)
513
        # Assert
514
        self.assertIs(temp, path2)
515
        if CHECK_EXPORTED_CONTENT:
516
            actual = read_csv(temp, delimiter='\t')
517
            self.assertEqual(expected, actual)
518
        move_file(temp, path)
519
520
    @unittest.skipUnless(os.getenv(ENV) or not CHECK_EXPORTED_CONTENT, REASON)
521
    def test_export_xlsx(self):
522
        """Verify a document can be exported as an XLSX file."""
523
        path = os.path.join(FILES, 'exported.xlsx')
524
        temp = os.path.join(self.temp, 'exported.xlsx')
525
        expected = read_xlsx(path)
526
        # Act
527
        path2 = core.exporter.export(self.document, temp)
528
        # Assert
529
        self.assertIs(temp, path2)
530
        if CHECK_EXPORTED_CONTENT:
531
            actual = read_xlsx(temp)
532
            self.assertEqual(expected, actual)
533
        else:  # binary file always changes, only copy when not checking
534
            move_file(temp, path)
535
536
537
class TestPublisher(unittest.TestCase):
538
    """Integration tests for the doorstop.core.publisher module."""
539
540
    maxDiff = None
541
542
    @patch('doorstop.core.document.Document', DocumentNoSkip)
543
    def setUp(self):
544
        self.tree = core.build(cwd=FILES, root=FILES)
545
        # self.document = core.Document(FILES, root=ROOT)
546
        self.document = self.tree.find_document('REQ')
547
        self.temp = tempfile.mkdtemp()
548
549
    def tearDown(self):
550
        if os.path.exists(self.temp):
551
            shutil.rmtree(self.temp)
552
553
    def test_publish_html(self):
554
        """Verify an HTML file can be created."""
555
        path = os.path.join(self.temp, 'published.html')
556
        # Act
557
        path2 = core.publisher.publish(self.document, path, '.html')
558
        # Assert
559
        self.assertIs(path, path2)
560
        self.assertTrue(os.path.isfile(path))
561
562
    def test_publish_bad_link(self):
563
        """Verify a tree can be published with bad links."""
564
        item = self.document.add_item()
565
        try:
566
            item.link('badlink')
567
            dirpath = os.path.join(self.temp, 'html')
568
            # Act
569
            dirpath2 = core.publisher.publish(self.tree, dirpath)
570
            # Assert
571
            self.assertIs(dirpath, dirpath2)
572
        finally:
573
            item.delete()
574
575
    def test_lines_text_document(self):
576
        """Verify text can be published from a document."""
577
        path = os.path.join(FILES, 'published.txt')
578
        expected = common.read_text(path)
579
        # Act
580
        lines = core.publisher.publish_lines(self.document, '.txt')
581
        text = ''.join(line + '\n' for line in lines)
582
        # Assert
583
        if CHECK_PUBLISHED_CONTENT:
584
            self.assertEqual(expected, text)
585
        common.write_text(text, path)
586
587
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
588
    def test_lines_text_document_without_child_links(self):
589
        """Verify text can be published from a document w/o child links."""
590
        path = os.path.join(FILES, 'published2.txt')
591
        expected = common.read_text(path)
592
        # Act
593
        lines = core.publisher.publish_lines(self.document, '.txt')
594
        text = ''.join(line + '\n' for line in lines)
595
        # Assert
596
        if CHECK_PUBLISHED_CONTENT:
597
            self.assertEqual(expected, text)
598
        common.write_text(text, path)
599
600
    def test_lines_markdown_document(self):
601
        """Verify Markdown can be published from a document."""
602
        path = os.path.join(FILES, 'published.md')
603
        expected = common.read_text(path)
604
        # Act
605
        lines = core.publisher.publish_lines(self.document, '.md')
606
        text = ''.join(line + '\n' for line in lines)
607
        # Assert
608
        if CHECK_PUBLISHED_CONTENT:
609
            self.assertEqual(expected, text)
610
        common.write_text(text, path)
611
612
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
613
    def test_lines_markdown_document_without_child_links(self):
614
        """Verify Markdown can be published from a document w/o child links."""
615
        path = os.path.join(FILES, 'published2.md')
616
        expected = common.read_text(path)
617
        # Act
618
        lines = core.publisher.publish_lines(self.document, '.md')
619
        text = ''.join(line + '\n' for line in lines)
620
        # Assert
621
        if CHECK_PUBLISHED_CONTENT:
622
            self.assertEqual(expected, text)
623
        common.write_text(text, path)
624
625
    def test_lines_html_document_linkify(self):
626
        """Verify HTML can be published from a document."""
627
        path = os.path.join(FILES, 'published.html')
628
        expected = common.read_text(path)
629
        # Act
630
        lines = core.publisher.publish_lines(self.document, '.html',
631
                                             linkify=True)
632
        text = ''.join(line + '\n' for line in lines)
633
        # Assert
634
        if CHECK_PUBLISHED_CONTENT:
635
            self.assertEqual(expected, text)
636
        common.write_text(text, path)
637
638
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
639
    def test_lines_html_document_without_child_links(self):
640
        """Verify HTML can be published from a document w/o child links."""
641
        path = os.path.join(FILES, 'published2.html')
642
        expected = common.read_text(path)
643
        # Act
644
        lines = core.publisher.publish_lines(self.document, '.html')
645
        text = ''.join(line + '\n' for line in lines)
646
        # Assert
647
        if CHECK_PUBLISHED_CONTENT:
648
            self.assertEqual(expected, text)
649
        common.write_text(text, path)
650
651
652
class TestModule(unittest.TestCase):
653
    """Integration tests for the doorstop.core module."""
654
655
    def setUp(self):
656
        """Reset the internal tree."""
657
        _clear_tree()
658
659
    def test_find_document(self):
660
        """Verify documents can be found using a convenience function."""
661
        # Cache miss
662
        document = core.find_document('req')
663
        self.assertIsNot(None, document)
664
        # Cache hit
665
        document2 = core.find_document('req')
666
        self.assertIs(document2, document)
667
668
    def test_find_item(self):
669
        """Verify items can be found using a convenience function."""
670
        # Cache miss
671
        item = core.find_item('req1')
672
        self.assertIsNot(None, item)
673
        # Cache hit
674
        item2 = core.find_item('req1')
675
        self.assertIs(item2, item)
676
677
678
# helper functions ###########################################################
679
680
681
def log_data(expected, actual):
682
    """Log list values."""
683
    for index, (evalue, avalue) in enumerate(zip(expected, actual)):
684
        logging.debug("\n{i} expected:\n{e}\n{i} actual:\n{a}".format(
0 ignored issues
show
introduced by
Use formatting in logging functions but pass the parameters as arguments
Loading history...
685
            i=index,
686
            e=pprint.pformat(evalue),
687
            a=pprint.pformat(avalue)))
688
689
690
def read_yml(path):
691
    """Return a dictionary of items from a YAML file."""
692
    text = common.read_text(path)
693
    data = yaml.load(text)
694
    return data
695
696
697
def read_csv(path, delimiter=','):
698
    """Return a list of rows from a CSV file."""
699
    rows = []
700
    try:
701
        with open(path, 'r', newline='', encoding='utf-8') as stream:
702
            reader = csv.reader(stream, delimiter=delimiter)
703
            for row in reader:
704
                rows.append(row)
705
    except FileNotFoundError:
706
        logging.warning("file not found: {}".format(path))
0 ignored issues
show
introduced by
Use formatting in logging functions but pass the parameters as arguments
Loading history...
707
    return rows
708
709
710
def read_xlsx(path):
711
    """Return a list of workbook data from an XLSX file."""
712
    data = []
713
714
    try:
715
        workbook = openpyxl.load_workbook(path)
716
    except openpyxl.exceptions.InvalidFileException:
717
        logging.warning("file not found: {}".format(path))
0 ignored issues
show
introduced by
Use formatting in logging functions but pass the parameters as arguments
Loading history...
718
    else:
719
        worksheet = workbook.active
720
        for row in worksheet.rows:
721
            for cell in row:
722
                values = (cell.value,
723
                          cell.style,
724
                          worksheet.column_dimensions[cell.column].width)
725
                data.append(values)
726
        data.append(worksheet.auto_filter.ref)
727
728
    return data
729
730
731
def move_file(src, dst):
732
    """Move a file from one path to another."""
733
    common.delete(dst)
734
    shutil.move(src, dst)
735