Completed
Push — develop ( 40f9fa...90b933 )
by Jace
02:51
created

test_publish_document_no_body_or_heading_levels()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
"""Integration tests for the doorstop.cli package."""
2
3
import unittest
4
from unittest.mock import patch, Mock
5
6
import os
7
import tempfile
8
import shutil
9
10
from doorstop.cli.main import main
11
from doorstop import common
12
from doorstop.core.builder import _clear_tree
13
from doorstop import settings
14
15
from doorstop.cli.test import ENV, REASON, ROOT, FILES, REQS, TUTORIAL
16
from doorstop.cli.test import SettingsTestCase
17
18
REQ_COUNT = 14
19
ALL_COUNT = 46
20
21
22
class TempTestCase(unittest.TestCase):
23
    """Base test case class with a temporary directory."""
24
25
    def setUp(self):
26
        self.cwd = os.getcwd()
27
        self.temp = tempfile.mkdtemp()
28
29
    def tearDown(self):
30
        os.chdir(self.cwd)
31
        if os.path.exists(self.temp):
32
            shutil.rmtree(self.temp)
33
34
35
class MockTestCase(TempTestCase):
36
    """Base test case class for a temporary mock working copy."""
37
38
    def setUp(self):
39
        super().setUp()
40
        os.chdir(self.temp)
41
        common.touch('.mockvcs')
42
        _clear_tree()
43
44
45
@unittest.skipUnless(os.getenv(ENV), REASON)
46
@patch('doorstop.settings.ADDREMOVE_FILES', False)
47
class TestMain(SettingsTestCase):
48
    """Integration tests for the 'doorstop' command."""
49
50
    def setUp(self):
51
        super().setUp()
52
        self.cwd = os.getcwd()
53
        self.temp = tempfile.mkdtemp()
54
55
    def tearDown(self):
56
        super().tearDown()
57
        os.chdir(self.cwd)
58
        shutil.rmtree(self.temp)
59
60
    def test_main(self):
61
        """Verify 'doorstop' can be called."""
62
        self.assertIs(None, main([]))
63
64
    def test_main_error(self):
65
        """Verify 'doorstop' returns an error in an empty directory."""
66
        os.chdir(self.temp)
67
        self.assertRaises(SystemExit, main, [])
68
69
    def test_main_custom_root(self):
70
        """Verify 'doorstop' can be provided a custom root path."""
71
        os.chdir(self.temp)
72
        self.assertIs(None, main(['--project', '.']))
73
74
75
@unittest.skipUnless(os.getenv(ENV), REASON)
76
class TestCreate(TempTestCase):
77
    """Integration tests for the 'doorstop create' command."""
78
79
    def test_create(self):
80
        """Verify 'doorstop create' can be called."""
81
        self.assertIs(None, main(['create', '_TEMP', self.temp, '-p', 'REQ']))
82
83
    def test_create_error_unknwon_parent(self):
84
        """Verify 'doorstop create' returns an error with an unknown parent."""
85
        self.assertRaises(SystemExit, main,
86
                          ['create', '_TEMP', self.temp, '-p', 'UNKNOWN'])
87
88
    def test_create_error_reserved_prefix(self):
89
        """Verify 'doorstop create' returns an error with a reserved prefix."""
90
        self.assertRaises(SystemExit, main,
91
                          ['create', 'ALL', self.temp, '-p', 'REQ'])
92
93
94
@unittest.skipUnless(os.getenv(ENV), REASON)
95
class TestDelete(MockTestCase):
96
    """Integration tests for the 'doorstop delete' command."""
97
98
    def test_delete(self):
99
        """Verify 'doorstop delete' can be called."""
100
        main(['create', 'PREFIX', 'prefix'])
101
        self.assertIs(None, main(['delete', 'PREFIX']))
102
103
    def test_delete_error(self):
104
        """Verify 'doorstop delete' returns an error on unknown document."""
105
        self.assertRaises(SystemExit, main, ['delete', 'UNKNOWN'])
106
107
108
def get_next_number():
109
    """Helper function to get the next document number."""
110
    last = None
111
    for last in sorted(os.listdir(TUTORIAL), reverse=True):
112
        if "index" not in last:
113
            break
114
    assert last
115
    number = int(last.replace('TUT', '').replace('.yml', '')) + 1
116
    return number
117
118
119
@unittest.skipUnless(os.getenv(ENV), REASON)
120
@patch('doorstop.settings.SERVER_HOST', None)
121
@patch('doorstop.settings.ADDREMOVE_FILES', False)
122
class TestAdd(unittest.TestCase):
123
    """Integration tests for the 'doorstop add' command."""
124
125
    @classmethod
126
    def setUpClass(cls):
127
        number = get_next_number()
128
        filename = "TUT{}.yml".format(str(number).zfill(3))
129
        cls.path = os.path.join(TUTORIAL, filename)
130
131
    def tearDown(self):
132
        common.delete(self.path)
133
134
    def test_add(self):
135
        """Verify 'doorstop add' can be called."""
136
        self.assertIs(None, main(['add', 'TUT']))
137
        self.assertTrue(os.path.isfile(self.path))
138
139
    def test_add_multiple(self):
140
        """Verify 'doorstop add' can be called with a given positive count"""
141
        number = get_next_number()
142
        numbers = (number, number + 1, number + 2)
143
        self.assertIs(None, main(['add', 'TUT', '--count', '3']))
144
        filenames = ("TUT{}.yml".format(str(x).zfill(3)) for x in numbers)
145
        paths = [os.path.join(TUTORIAL, f) for f in filenames]
146
        self.assertTrue(os.path.isfile(paths[0]))
147
        self.assertTrue(os.path.isfile(paths[1]))
148
        self.assertTrue(os.path.isfile(paths[2]))
149
        os.remove(paths[1])
150
        os.remove(paths[2])
151
152
    def test_add_multiple_non_positive(self):
153
        """Verify 'doorstop add' rejects non-positive integers for counts."""
154
        self.assertRaises(SystemExit, main, ['add', 'TUT', '--count', '-1'])
155
156
    def test_add_specific_level(self):
157
        """Verify 'doorstop add' can be called with a specific level."""
158
        self.assertIs(None, main(['add', 'TUT', '--level', '1.42']))
159
        self.assertTrue(os.path.isfile(self.path))
160
161
    def test_add_error(self):
162
        """Verify 'doorstop add' returns an error with an unknown prefix."""
163
        self.assertRaises(SystemExit, main, ['add', 'UNKNOWN'])
164
165
166
@unittest.skipUnless(os.getenv(ENV), REASON)
167
@patch('doorstop.settings.ADDREMOVE_FILES', False)
168
class TestAddServer(unittest.TestCase):
169
    """Integration tests for the 'doorstop add' command using a server."""
170
171
    @classmethod
172
    def setUpClass(cls):
173
        number = get_next_number()
174
        filename = "TUT{}.yml".format(str(number).zfill(3))
175
        cls.path = os.path.join(TUTORIAL, filename)
176
177
    def tearDown(self):
178
        common.delete(self.path)
179
180
    @patch('doorstop.settings.SERVER_HOST', '')
181
    def test_add(self):
182
        """Verify 'doorstop add' expects a server."""
183
        self.assertRaises(SystemExit, main, ['add', 'TUT'])
184
185
    @patch('doorstop.settings.SERVER_HOST', None)
186
    def test_add_no_server(self):
187
        """Verify 'doorstop add' can be called if there is no server."""
188
        self.assertIs(None, main(['add', 'TUT']))
189
190
    @patch('doorstop.server.check', Mock())
191
    @patch('doorstop.core.document.Document.add_item')
192
    def test_add_custom_server(self, mock_add_item):
193
        """Verify 'doorstop add' can be called with a custom server."""
194
        self.assertIs(None, main(['add', 'TUT', '--server', '1.2.3.4']))
195
        mock_add_item.assert_called_once_with(level=None)
196
197
    def test_add_force(self):
198
        """Verify 'doorstop add' can be called with a missing server."""
199
        self.assertIs(None, main(['add', 'TUT', '--force']))
200
201
202
@unittest.skipUnless(os.getenv(ENV), REASON)
203
@patch('doorstop.settings.ADDREMOVE_FILES', False)
204
class TestRemove(unittest.TestCase):
205
    """Integration tests for the 'doorstop remove' command."""
206
207
    ITEM = os.path.join(TUTORIAL, 'TUT003.yml')
208
209
    def setUp(self):
210
        self.backup = common.read_text(self.ITEM)
211
212
    def tearDown(self):
213
        common.write_text(self.backup, self.ITEM)
214
215
    def test_remove(self):
216
        """Verify 'doorstop remove' can be called."""
217
        self.assertIs(None, main(['remove', 'tut3']))
218
        self.assertFalse(os.path.exists(self.ITEM))
219
220
    def test_remove_error(self):
221
        """Verify 'doorstop remove' returns an error on unknown item UIDs."""
222
        self.assertRaises(SystemExit, main, ['remove', 'tut9999'])
223
224
225
@unittest.skipUnless(os.getenv(ENV), REASON)
226
@patch('doorstop.settings.ADDREMOVE_FILES', False)
227
class TestReorder(unittest.TestCase):
228
    """Integration tests for the 'doorstop reorder' command."""
229
230
    @classmethod
231
    def setUpClass(cls):
232
        cls.prefix = 'tut'
233
        cls.path = os.path.join('reqs', 'tutorial', 'index.yml')
234
235
    def tearDown(self):
236
        common.delete(self.path)
237
238
    @patch('doorstop.core.editor.launch')
239
    @patch('builtins.input', Mock(return_value='yes'))
240
    def test_reorder_document_yes(self, mock_launch):
241
        """Verify 'doorstop reorder' can be called with a document (yes)."""
242
        self.assertIs(None, main(['reorder', self.prefix]))
243
        mock_launch.assert_called_once_with(self.path, tool=None)
244
        self.assertFalse(os.path.exists(self.path))
245
246
    @patch('doorstop.core.editor.launch')
247
    @patch('builtins.input', Mock(return_value='no'))
248
    def test_reorder_document_no(self, mock_launch):
249
        """Verify 'doorstop reorder' can be called with a document (no)."""
250
        self.assertIs(None, main(['reorder', self.prefix]))
251
        mock_launch.assert_called_once_with(self.path, tool=None)
252
        self.assertFalse(os.path.exists(self.path))
253
254
    @patch('doorstop.core.editor.launch')
255
    def test_reorder_document_auto(self, mock_launch):
256
        """Verify 'doorstop reorder' can be called with a document (auto)."""
257
        self.assertIs(None, main(['reorder', self.prefix, '--auto']))
258
        self.assertEqual(0, mock_launch.call_count)
259
260
    @patch('doorstop.core.document.Document._reorder_automatic')
261
    @patch('doorstop.core.editor.launch')
262
    @patch('builtins.input', Mock(return_value='no'))
263
    def test_reorder_document_manual(self, mock_launch, mock_reorder_auto):
264
        """Verify 'doorstop reorder' can be called with a document (manual)."""
265
        self.assertIs(None, main(['reorder', self.prefix, '--manual']))
266
        mock_launch.assert_called_once_with(self.path, tool=None)
267
        self.assertEqual(0, mock_reorder_auto.call_count)
268
        self.assertFalse(os.path.exists(self.path))
269
270
    @patch('builtins.input', Mock(return_value='yes'))
271
    def test_reorder_document_error(self):
272
        """Verify 'doorstop reorder' can handle invalid YAML."""
273
274
        def bad_yaml_edit(path, **_):
275
            """Simulate adding invalid YAML to the index."""
276
            common.write_text("%bad", path)
277
278
        with patch('doorstop.core.editor.launch', bad_yaml_edit):
279
            self.assertRaises(SystemExit, main, ['reorder', self.prefix])
280
281
        self.assertTrue(os.path.exists(self.path))
282
283
    def test_reorder_document_unknown(self):
284
        """Verify 'doorstop reorder' returns an error on an unknown prefix."""
285
        self.assertRaises(SystemExit, main, ['reorder', 'FAKE'])
286
287
288
@unittest.skipUnless(os.getenv(ENV), REASON)
289
@patch('doorstop.settings.SERVER_HOST', None)
290
@patch('doorstop.settings.ADDREMOVE_FILES', False)
291
class TestEdit(unittest.TestCase):
292
    """Integration tests for the 'doorstop edit' command."""
293
294
    @patch('doorstop.core.editor.launch')
295
    def test_edit_item(self, mock_launch):
296
        """Verify 'doorstop edit' can be called with an item."""
297
        self.assertIs(None, main(['edit', 'tut2']))
298
        path = os.path.join(TUTORIAL, 'TUT002.yml')
299
        mock_launch.assert_called_once_with(os.path.normpath(path), tool=None)
300
301
    def test_edit_item_unknown(self):
302
        """Verify 'doorstop edit' returns an error on an unknown item."""
303
        self.assertRaises(SystemExit, main, ['edit', '--item', 'FAKE001'])
304
305
    @patch('time.time', Mock(return_value=123))
306
    @patch('doorstop.core.editor.launch')
307
    @patch('builtins.input', Mock(return_value='yes'))
308
    def test_edit_document_yes_yes(self, mock_launch):
309
        """Verify 'doorstop edit' can be called with a document (yes, yes)."""
310
        path = "TUT-123.yml"
311
        self.assertIs(None, main(['edit', 'tut']))
312
        mock_launch.assert_called_once_with(os.path.normpath(path), tool=None)
313
314
    @patch('time.time', Mock(return_value=456))
315
    @patch('doorstop.core.editor.launch')
316
    @patch('builtins.input', Mock(return_value='no'))
317
    def test_edit_document_no_no(self, mock_launch):
318
        """Verify 'doorstop edit' can be called with a document (no, no)."""
319
        path = "TUT-456.yml"
320
        self.assertIs(None, main(['edit', 'tut']))
321
        common.delete(path)
322
        mock_launch.assert_called_once_with(os.path.normpath(path), tool=None)
323
324
    @patch('time.time', Mock(return_value=789))
325
    @patch('doorstop.core.editor.launch')
326
    @patch('builtins.input', Mock(side_effect=['no', 'yes']))
327
    def test_edit_document_no_yes(self, mock_launch):
328
        """Verify 'doorstop edit' can be called with a document (no, yes)."""
329
        path = "TUT-789.yml"
330
        self.assertIs(None, main(['edit', 'tut']))
331
        mock_launch.assert_called_once_with(os.path.normpath(path), tool=None)
332
333
    def test_edit_document_unknown(self):
334
        """Verify 'doorstop edit' returns an error on an unknown document."""
335
        self.assertRaises(SystemExit, main, ['edit', '--document', 'FAKE'])
336
337
    def test_edit_error(self):
338
        """Verify 'doorstop edit' returns an error with an unknown UID."""
339
        self.assertRaises(SystemExit, main, ['edit', 'req9999'])
340
341
342 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...
343
@patch('doorstop.settings.ADDREMOVE_FILES', False)
344
class TestLink(unittest.TestCase):
345
    """Integration tests for the 'doorstop link' command."""
346
347
    ITEM = os.path.join(TUTORIAL, 'TUT003.yml')
348
349
    def setUp(self):
350
        self.backup = common.read_text(self.ITEM)
351
352
    def tearDown(self):
353
        common.write_text(self.backup, self.ITEM)
354
355
    def test_link(self):
356
        """Verify 'doorstop link' can be called."""
357
        self.assertIs(None, main(['link', 'tut3', 'req2']))
358
359
    def test_link_unknown_child(self):
360
        """Verify 'doorstop link' returns an error with an unknown child."""
361
        self.assertRaises(SystemExit, main, ['link', 'unknown3', 'req2'])
362
        self.assertRaises(SystemExit, main, ['link', 'tut9999', 'req2'])
363
364
    def test_link_unknown_parent(self):
365
        """Verify 'doorstop link' returns an error with an unknown parent."""
366
        self.assertRaises(SystemExit, main, ['link', 'tut3', 'unknown2'])
367
        self.assertRaises(SystemExit, main, ['link', 'tut3', 'req9999'])
368
369
370 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...
371
@patch('doorstop.settings.ADDREMOVE_FILES', False)
372
class TestUnlink(unittest.TestCase):
373
    """Integration tests for the 'doorstop unlink' command."""
374
375
    ITEM = os.path.join(TUTORIAL, 'TUT003.yml')
376
377
    def setUp(self):
378
        self.backup = common.read_text(self.ITEM)
379
        with patch('doorstop.settings.ADDREMOVE_FILES', False):
380
            main(['link', 'tut3', 'req2'])  # create a temporary link
381
382
    def tearDown(self):
383
        common.write_text(self.backup, self.ITEM)
384
385
    def test_unlink(self):
386
        """Verify 'doorstop unlink' can be called."""
387
        self.assertIs(None, main(['unlink', 'tut3', 'req2']))
388
389
    def test_unlink_unknown_child(self):
390
        """Verify 'doorstop unlink' returns an error with an unknown child."""
391
        self.assertRaises(SystemExit, main, ['unlink', 'unknown3', 'req2'])
392
        self.assertRaises(SystemExit, main, ['link', 'tut9999', 'req2'])
393
394
    def test_unlink_unknown_parent(self):
395
        """Verify 'doorstop unlink' returns an error with an unknown parent."""
396
        self.assertRaises(SystemExit, main, ['unlink', 'tut3', 'unknown2'])
397
        self.assertRaises(SystemExit, main, ['unlink', 'tut3', 'req9999'])
398
399
400 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...
401
class TestClear(unittest.TestCase):
402
    """Integration tests for the 'doorstop clear' command."""
403
404
    @patch('doorstop.core.item.Item.clear')
405
    def test_clear_item(self, mock_clear):
406
        """Verify 'doorstop clear' can be called with an item."""
407
        self.assertIs(None, main(['clear', 'tut2']))
408
        self.assertEqual(1, mock_clear.call_count)
409
410
    def test_clear_item_unknown(self):
411
        """Verify 'doorstop clear' returns an error on an unknown item."""
412
        self.assertRaises(SystemExit, main, ['clear', '--item', 'FAKE001'])
413
414
    @patch('doorstop.core.item.Item.clear')
415
    def test_clear_document(self, mock_clear):
416
        """Verify 'doorstop clear' can be called with a document"""
417
        self.assertIs(None, main(['clear', 'tut']))
418
        self.assertEqual(REQ_COUNT, mock_clear.call_count)
419
420
    def test_clear_document_unknown(self):
421
        """Verify 'doorstop clear' returns an error on an unknown document."""
422
        self.assertRaises(SystemExit, main, ['clear', '--document', 'FAKE'])
423
424
    @patch('doorstop.core.item.Item.clear')
425
    def test_clear_tree(self, mock_clear):
426
        """Verify 'doorstop clear' can be called with a tree"""
427
        self.assertIs(None, main(['clear', 'all']))
428
        self.assertEqual(ALL_COUNT, mock_clear.call_count)
429
430
    def test_clear_tree_item(self):
431
        """Verify 'doorstop clear' returns an error with tree and item."""
432
        self.assertRaises(SystemExit, main, ['clear', '--item', 'all'])
433
434
    def test_clear_tree_document(self):
435
        """Verify 'doorstop clear' returns an error with tree and document."""
436
        self.assertRaises(SystemExit, main, ['clear', '--document', 'all'])
437
438
    def test_clear_error(self):
439
        """Verify 'doorstop clear' returns an error with an unknown UID."""
440
        self.assertRaises(SystemExit, main, ['clear', 'req9999'])
441
442
443 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...
444
class TestReview(unittest.TestCase):
445
    """Integration tests for the 'doorstop review' command."""
446
447
    @patch('doorstop.core.item.Item.review')
448
    def test_review_item(self, mock_review):
449
        """Verify 'doorstop review' can be called with an item."""
450
        self.assertIs(None, main(['review', 'tut2']))
451
        self.assertEqual(1, mock_review.call_count)
452
453
    def test_review_item_unknown(self):
454
        """Verify 'doorstop review' returns an error on an unknown item."""
455
        self.assertRaises(SystemExit, main, ['review', '--item', 'FAKE001'])
456
457
    @patch('doorstop.core.item.Item.review')
458
    def test_review_document(self, mock_review):
459
        """Verify 'doorstop review' can be called with a document"""
460
        self.assertIs(None, main(['review', 'tut']))
461
        self.assertEqual(REQ_COUNT, mock_review.call_count)
462
463
    def test_review_document_unknown(self):
464
        """Verify 'doorstop review' returns an error on an unknown document."""
465
        self.assertRaises(SystemExit, main, ['review', '--document', 'FAKE'])
466
467
    @patch('doorstop.core.item.Item.review')
468
    def test_review_tree(self, mock_review):
469
        """Verify 'doorstop review' can be called with a tree"""
470
        self.assertIs(None, main(['review', 'all']))
471
        self.assertEqual(ALL_COUNT, mock_review.call_count)
472
473
    def test_review_tree_item(self):
474
        """Verify 'doorstop review' returns an error with tree and item."""
475
        self.assertRaises(SystemExit, main, ['review', '--item', 'all'])
476
477
    def test_review_tree_document(self):
478
        """Verify 'doorstop review' returns an error with tree and document."""
479
        self.assertRaises(SystemExit, main, ['review', '--document', 'all'])
480
481
    def test_review_error(self):
482
        """Verify 'doorstop review' returns an error with an unknown UID."""
483
        self.assertRaises(SystemExit, main, ['review', 'req9999'])
484
485
486
@unittest.skipUnless(os.getenv(ENV), REASON)
487
@patch('doorstop.settings.SERVER_HOST', None)
488
@patch('doorstop.settings.ADDREMOVE_FILES', False)
489
class TestImport(unittest.TestCase):
490
    """Integration tests for the 'doorstop import' command."""
491
492
    def tearDown(self):
493
        common.delete(os.path.join(ROOT, 'tmp'))
494
        common.delete(os.path.join(REQS, 'REQ099.yml'))
495
496
    def test_import_document(self):
497
        """Verify 'doorstop import' can import a document."""
498
        self.assertRaises(SystemExit,
499
                          main, ['import', '--document', 'TMP', 'tmp'])
500
501
    def test_import_document_with_parent(self):
502
        """Verify 'doorstop import' can import a document with a parent."""
503
        self.assertIs(None, main(['import', '--document', 'TMP', 'tmp',
504
                                  '--parent', 'REQ']))
505
506
    def test_import_item(self):
507
        """Verify 'doorstop import' can import an item.."""
508
        self.assertIs(None, main(['import', '--item', 'REQ', 'REQ099']))
509
510
    def test_import_item_with_attrs(self):
511
        """Verify 'doorstop import' can import an item with attributes."""
512
        self.assertIs(None, main(['import', '--item', 'REQ', 'REQ099',
513
                                  '--attrs', "{'text': 'The item text.'}"]))
514
515
    def test_import_error(self):
516
        """Verify 'doorstop import' requires a document or item."""
517
        self.assertRaises(SystemExit, main, ['import', '--attr', "{}"])
518
519
520
@unittest.skipUnless(os.getenv(ENV), REASON)
521
@patch('doorstop.settings.SERVER_HOST', None)
522
class TestImportFile(MockTestCase):
523
    """Integration tests for the 'doorstop import' command."""
524
525
    def test_import_file_missing_prefix(self):
526
        """Verify 'doorstop import' returns an error with a missing prefix."""
527
        path = os.path.join(FILES, 'exported.xlsx')
528
        self.assertRaises(SystemExit, main, ['import', path])
529
530
    def test_import_file_extra_flags(self):
531
        """Verify 'doorstop import' returns an error with extra flags."""
532
        path = os.path.join(FILES, 'exported.xlsx')
533
        self.assertRaises(SystemExit,
534
                          main, ['import', path, 'PREFIX', '-d', '_', '_'])
535
        self.assertRaises(SystemExit,
536
                          main, ['import', path, 'PREFIX', '-i', '_', '_'])
537
538
    def test_import_file_to_document_unknown(self):
539
        """Verify 'doorstop import' returns an error for unknown documents."""
540
        path = os.path.join(FILES, 'exported.xlsx')
541
        self.assertRaises(SystemExit, main, ['import', path, 'PREFIX'])
542
543
    def test_import_file_with_map(self):
544
        """Verify 'doorstop import' can import a file using a custom map."""
545
        path = os.path.join(FILES, 'exported-map.csv')
546
        dirpath = os.path.join(self.temp, 'imported', 'prefix')
547
        main(['create', 'PREFIX', dirpath])
548
        # Act
549
        self.assertIs(None, main(['import', path, 'PREFIX',
550
                                  '--map', "{'mylevel': 'level'}"]))
551
        # Assert
552
        path = os.path.join(dirpath, 'REQ001.yml')
553
        self.assertTrue(os.path.isfile(path))
554
        text = common.read_text(path)
555
        self.assertIn('\nlevel: 1.2.3', text)
556
557
    def test_import_file_with_map_invalid(self):
558
        """Verify 'doorstop import' returns an error with an invalid map."""
559
        path = os.path.join(FILES, 'exported.csv')
560
        self.assertRaises(SystemExit,
561
                          main, ['import', path, 'PREFIX', '--map', "{'my"])
562
563
    def test_import_csv_to_document_existing(self):
564
        """Verify 'doorstop import' can import CSV to an existing document."""
565
        path = os.path.join(FILES, 'exported.csv')
566
        dirpath = os.path.join(self.temp, 'imported', 'prefix')
567
        main(['create', 'PREFIX', dirpath])
568
        # Act
569
        self.assertIs(None, main(['import', path, 'PREFIX']))
570
        # Assert
571
        path = os.path.join(dirpath, 'REQ001.yml')
572
        self.assertTrue(os.path.isfile(path))
573
574
    def test_import_tsv_to_document_existing(self):
575
        """Verify 'doorstop import' can import TSV to an existing document."""
576
        path = os.path.join(FILES, 'exported.tsv')
577
        dirpath = os.path.join(self.temp, 'imported', 'prefix')
578
        main(['create', 'PREFIX', dirpath])
579
        # Act
580
        self.assertIs(None, main(['import', path, 'PREFIX']))
581
        # Assert
582
        path = os.path.join(dirpath, 'REQ001.yml')
583
        self.assertTrue(os.path.isfile(path))
584
585
    def test_import_xlsx_to_document_existing(self):
586
        """Verify 'doorstop import' can import XLSX to an existing document."""
587
        path = os.path.join(FILES, 'exported.xlsx')
588
        dirpath = os.path.join(self.temp, 'imported', 'prefix')
589
        main(['create', 'PREFIX', dirpath])
590
        # Act
591
        self.assertIs(None, main(['import', path, 'PREFIX']))
592
        # Assert
593
        path = os.path.join(dirpath, 'REQ001.yml')
594
        self.assertTrue(os.path.isfile(path))
595
596
597
@unittest.skipUnless(os.getenv(ENV), REASON)
598
@patch('doorstop.settings.ADDREMOVE_FILES', False)
599
class TestImportServer(unittest.TestCase):
600
    """Integration tests for the 'doorstop import' command using a server."""
601
602
    def tearDown(self):
603
        common.delete(os.path.join(ROOT, 'tmp'))
604
        common.delete(os.path.join(REQS, 'REQ099.yml'))
605
606
    def test_import_item_force(self):
607
        """Verify 'doorstop import' can import an item without a server."""
608
        self.assertIs(None,
609
                      main(['import', '--item', 'REQ', 'REQ099', '--force']))
610
611
612
@unittest.skipUnless(os.getenv(ENV), REASON)
613
class TestExport(TempTestCase):
614
    """Integration tests for the 'doorstop export' command."""
615
616
    def test_export_document_error_unknown(self):
617
        """Verify 'doorstop export' returns an error for an unknown format."""
618
        self.assertRaises(SystemExit, main, ['export', 'req', 'req.fake'])
619
620
    def test_export_document_error_directory(self):
621
        """Verify 'doorstop publish' returns an error with a directory."""
622
        self.assertRaises(SystemExit, main, ['export', 'req', self.temp])
623
624
    def test_export_document_error_no_extension(self):
625
        """Verify 'doorstop publish' returns an error with no extension."""
626
        path = os.path.join(self.temp, 'req')
627
        self.assertRaises(SystemExit, main, ['export', 'req', path])
628
629
    def test_export_document_stdout(self):
630
        """Verify 'doorstop export' can create output."""
631
        self.assertIs(None, main(['export', 'tut']))
632
633
    def test_export_document_stdout_width(self):
634
        """Verify 'doorstop export' can create output."""
635
        self.assertIs(None, main(['export', 'tut', '--width', '72']))
636
637
    def test_export_document_yaml(self):
638
        """Verify 'doorstop export' can create a YAML file."""
639
        path = os.path.join(self.temp, 'tut.yml')
640
        self.assertIs(None, main(['export', 'tut', path]))
641
        self.assertTrue(os.path.isfile(path))
642
643
    def test_export_document_xlsx(self):
644
        """Verify 'doorstop export' can create an XLSX file."""
645
        path = os.path.join(self.temp, 'tut.xlsx')
646
        self.assertIs(None, main(['export', 'tut', path]))
647
        self.assertTrue(os.path.isfile(path))
648
649
    @patch('openpyxl.Workbook.save', Mock(side_effect=PermissionError))
650
    def test_export_document_xlsx_error(self):
651
        """Verify 'doorstop export' can handle IO errors."""
652
        path = os.path.join(self.temp, 'tut.xlsx')
653
        self.assertRaises(SystemExit, main, ['export', 'tut', path])
654
        self.assertFalse(os.path.isfile(path))
655
656
    def test_export_tree_xlsx(self):
657
        """Verify 'doorstop export' can create an XLSX directory."""
658
        path = os.path.join(self.temp, 'all')
659
        self.assertIs(None, main(['export', 'all', path, '--xlsx']))
660
        self.assertTrue(os.path.isdir(path))
661
662
    def test_export_tree_no_path(self):
663
        """Verify 'doorstop export' returns an error with no path."""
664
        self.assertRaises(SystemExit, main, ['export', 'all'])
665
666
667
@unittest.skipUnless(os.getenv(ENV), REASON)
668
class TestPublish(TempTestCase):
669
    """Integration tests for the 'doorstop publish' command."""
670
671
    def setUp(self):
672
        super().setUp()
673
        self.backup = (settings.PUBLISH_CHILD_LINKS,
674
                       settings.PUBLISH_BODY_LEVELS)
675
676
    def tearDown(self):
677
        super().tearDown()
678
        (settings.PUBLISH_CHILD_LINKS,
679
         settings.PUBLISH_BODY_LEVELS) = self.backup
680
681
    def test_publish_unknown(self):
682
        """Verify 'doorstop publish' returns an error for an unknown format."""
683
        self.assertRaises(SystemExit, main, ['publish', 'req', 'req.fake'])
684
685
    def test_publish_document(self):
686
        """Verify 'doorstop publish' can create output."""
687
        self.assertIs(None, main(['publish', 'tut']))
688
        self.assertTrue(settings.PUBLISH_CHILD_LINKS)
689
690
    def test_publish_document_with_child_links(self):
691
        """Verify 'doorstop publish' can create output with child links."""
692
        self.assertIs(None, main(['publish', 'tut']))
693
        self.assertTrue(settings.PUBLISH_CHILD_LINKS)
694
695
    def test_publish_document_without_child_links(self):
696
        """Verify 'doorstop publish' can create output without child links."""
697
        self.assertIs(None, main(['publish', 'tut', '--no-child-links']))
698
        self.assertFalse(settings.PUBLISH_CHILD_LINKS)
699
700
    def test_publish_document_without_body_levels(self):
701
        """Verify 'doorstop publish' can create output without body levels."""
702
        self.assertIs(None, main(['publish', 'tut', '--no-body-levels']))
703
        self.assertFalse(settings.PUBLISH_BODY_LEVELS)
704
705
    def test_publish_document_no_body_levels(self):
706
        """Verify 'doorstop publish' can create output without body levels."""
707
        self.assertIs(None, main(['publish', 'tut', '--no-levels=body']))
708
        self.assertFalse(settings.PUBLISH_BODY_LEVELS)
709
710
    def test_publish_document_no_body_or_heading_levels(self):
711
        """Verify 'doorstop publish' can create output without heading or body levels."""
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (89/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
712
        self.assertIs(None, main(['publish', 'tut', '--no-levels=all']))
713
        self.assertFalse(settings.PUBLISH_BODY_LEVELS)
714
        self.assertFalse(settings.PUBLISH_HEADING_LEVELS)
715
716
    def test_publish_document_error_empty(self):
717
        """Verify 'doorstop publish' returns an error in an empty folder."""
718
        os.chdir(self.temp)
719
        self.assertRaises(SystemExit, main, ['publish', 'req'])
720
721
    def test_publish_document_error_directory(self):
722
        """Verify 'doorstop publish' returns an error with a directory."""
723
        self.assertRaises(SystemExit, main, ['publish', 'req', self.temp])
724
725
    def test_publish_document_error_no_extension(self):
726
        """Verify 'doorstop publish' returns an error with no extension."""
727
        path = os.path.join(self.temp, 'req')
728
        self.assertRaises(SystemExit, main, ['publish', 'req', path])
729
730
    def test_publish_document_text(self):
731
        """Verify 'doorstop publish' can create text output."""
732
        self.assertIs(None, main(['publish', 'tut', '--width', '75']))
733
734
    def test_publish_document_text_file(self):
735
        """Verify 'doorstop publish' can create a text file."""
736
        path = os.path.join(self.temp, 'req.txt')
737
        self.assertIs(None, main(['publish', 'req', path]))
738
        self.assertTrue(os.path.isfile(path))
739
740
    def test_publish_document_markdown(self):
741
        """Verify 'doorstop publish' can create Markdown output."""
742
        self.assertIs(None, main(['publish', 'req', '--markdown']))
743
744
    def test_publish_document_markdown_file(self):
745
        """Verify 'doorstop publish' can create a Markdown file."""
746
        path = os.path.join(self.temp, 'req.md')
747
        self.assertIs(None, main(['publish', 'req', path]))
748
        self.assertTrue(os.path.isfile(path))
749
750
    def test_publish_document_html(self):
751
        """Verify 'doorstop publish' can create HTML output."""
752
        self.assertIs(None, main(['publish', 'hlt', '--html']))
753
754
    def test_publish_document_html_file(self):
755
        """Verify 'doorstop publish' can create an HTML file."""
756
        path = os.path.join(self.temp, 'req.html')
757
        self.assertIs(None, main(['publish', 'req', path]))
758
        self.assertTrue(os.path.isfile(path))
759
760
    def test_publish_tree_html(self):
761
        """Verify 'doorstop publish' can create an HTML directory."""
762
        path = os.path.join(self.temp, 'all')
763
        self.assertIs(None, main(['publish', 'all', path]))
764
        self.assertTrue(os.path.isdir(path))
765
        self.assertTrue(os.path.isfile(os.path.join(path, 'index.html')))
766
767
    def test_publish_tree_text(self):
768
        """Verify 'doorstop publish' can create a text directory."""
769
        path = os.path.join(self.temp, 'all')
770
        self.assertIs(None, main(['publish', 'all', path, '--text']))
771
        self.assertTrue(os.path.isdir(path))
772
        self.assertFalse(os.path.isfile(os.path.join(path, 'index.html')))
773
774
    def test_publish_tree_no_path(self):
775
        """Verify 'doorstop publish' returns an error with no path."""
776
        self.assertRaises(SystemExit, main, ['publish', 'all'])
777
778
779
@patch('doorstop.cli.commands.run', Mock(return_value=True))
780
class TestLogging(unittest.TestCase):
781
    """Integration tests for the Doorstop CLI logging."""
782
783
    def test_verbose_0(self):
784
        """Verify verbose level 0 can be set."""
785
        self.assertIs(None, main([]))
786
787
    def test_verbose_1(self):
788
        """Verify verbose level 1 can be set."""
789
        self.assertIs(None, main(['-v']))
790
791
    def test_verbose_2(self):
792
        """Verify verbose level 2 can be set."""
793
        self.assertIs(None, main(['-vv']))
794
795
    def test_verbose_3(self):
796
        """Verify verbose level 3 can be set."""
797
        self.assertIs(None, main(['-vvv']))
798
799
    def test_verbose_4(self):
800
        """Verify verbose level 4 can be set."""
801
        self.assertIs(None, main(['-vvvv']))
802
803
    def test_verbose_5(self):
804
        """Verify verbose level 5 cannot be set."""
805
        self.assertIs(None, main(['-vvvvv']))
806
        self.assertEqual(4, common.verbosity)
807
808
    def test_verbose_quiet(self):
809
        """Verify verbose level -1 can be set."""
810
        self.assertIs(None, main(['-q']))
811
        self.assertEqual(-1, common.verbosity)
812