Passed
Pull Request — develop (#458)
by
unknown
02:49
created

TestLogging.test_verbose_4()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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