Completed
Push — develop ( 00311a...62764c )
by Jace
10s
created

TestPublishCommand   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 19
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 19
rs 10
wmc 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
A test_publish_document_to_stdout() 0 6 1
A test_publish_document_template() 0 9 1
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
from doorstop.core.document import Document
15
16
from doorstop.cli.test import ENV, REASON, ROOT, FILES, REQS, TUTORIAL
17
from doorstop.cli.test import SettingsTestCase
18
19
REQ_COUNT = 17
20
ALL_COUNT = 49
21
22
23
class TempTestCase(unittest.TestCase):
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
    """Base test case class for a temporary mock working copy."""
38
39
    def setUp(self):
40
        super().setUp()
41
        os.chdir(self.temp)
42
        common.touch('.mockvcs')
43
        _clear_tree()
44
45
46
@unittest.skipUnless(os.getenv(ENV), REASON)
47
@patch('doorstop.settings.ADDREMOVE_FILES', False)
48
class TestMain(SettingsTestCase):
49
    """Integration tests for the 'doorstop' command."""
50
51
    def setUp(self):
52
        super().setUp()
53
        self.cwd = os.getcwd()
54
        self.temp = tempfile.mkdtemp()
55
56
    def tearDown(self):
57
        super().tearDown()
58
        os.chdir(self.cwd)
59
        shutil.rmtree(self.temp)
60
61
    def test_main(self):
62
        """Verify 'doorstop' can be called."""
63
        self.assertIs(None, main([]))
64
65
    def test_main_error(self):
66
        """Verify 'doorstop' returns an error in an empty directory."""
67
        os.chdir(self.temp)
68
        self.assertRaises(SystemExit, main, [])
69
70
    def test_main_custom_root(self):
71
        """Verify 'doorstop' can be provided a custom root path."""
72
        os.chdir(self.temp)
73
        self.assertIs(None, main(['--project', '.']))
74
75
76
@unittest.skipUnless(os.getenv(ENV), REASON)
77
class TestCreate(TempTestCase):
78
    """Integration tests for the 'doorstop create' command."""
79
80
    def test_create(self):
81
        """Verify 'doorstop create' can be called."""
82
        self.assertIs(None, main(['create', '_TEMP', self.temp, '-p', 'REQ']))
83
84
    def test_create_error_unknwon_parent(self):
85
        """Verify 'doorstop create' returns an error with an unknown parent."""
86
        self.assertRaises(SystemExit, main,
87
                          ['create', '_TEMP', self.temp, '-p', 'UNKNOWN'])
88
89
    def test_create_error_reserved_prefix(self):
90
        """Verify 'doorstop create' returns an error with a reserved prefix."""
91
        self.assertRaises(SystemExit, main,
92
                          ['create', 'ALL', self.temp, '-p', 'REQ'])
93
94
95
@unittest.skipUnless(os.getenv(ENV), REASON)
96
class TestDelete(MockTestCase):
97
    """Integration tests for the 'doorstop delete' command."""
98
99
    def test_delete(self):
100
        """Verify 'doorstop delete' can be called."""
101
        main(['create', 'PREFIX', 'prefix'])
102
        self.assertIs(None, main(['delete', 'PREFIX']))
103
104
    def test_delete_error(self):
105
        """Verify 'doorstop delete' returns an error on unknown document."""
106
        self.assertRaises(SystemExit, main, ['delete', 'UNKNOWN'])
107
108
109
def get_next_number():
110
    """Helper function to get the next document number."""
111
    last = None
112
    for last in sorted(os.listdir(TUTORIAL), reverse=True):
113
        if "index" not in last:
114
            break
115
    assert last
116
    number = int(last.replace('TUT', '').replace('.yml', '')) + 1
117
    return number
118
119
120
@unittest.skipUnless(os.getenv(ENV), REASON)
121
@patch('doorstop.settings.SERVER_HOST', None)
122
@patch('doorstop.settings.ADDREMOVE_FILES', False)
123
class TestAdd(unittest.TestCase):
124
    """Integration tests for the 'doorstop add' command."""
125
126
    @classmethod
127
    def setUpClass(cls):
128
        number = get_next_number()
129
        filename = "TUT{}.yml".format(str(number).zfill(3))
130
        cls.path = os.path.join(TUTORIAL, filename)
131
132
    def tearDown(self):
133
        common.delete(self.path)
134
135
    def test_add(self):
136
        """Verify 'doorstop add' can be called."""
137
        self.assertIs(None, main(['add', 'TUT']))
138
        self.assertTrue(os.path.isfile(self.path))
139
140
    def test_add_multiple(self):
141
        """Verify 'doorstop add' can be called with a given positive count"""
142
        number = get_next_number()
143
        numbers = (number, number + 1, number + 2)
144
        self.assertIs(None, main(['add', 'TUT', '--count', '3']))
145
        filenames = ("TUT{}.yml".format(str(x).zfill(3)) for x in numbers)
146
        paths = [os.path.join(TUTORIAL, f) for f in filenames]
147
        self.assertTrue(os.path.isfile(paths[0]))
148
        self.assertTrue(os.path.isfile(paths[1]))
149
        self.assertTrue(os.path.isfile(paths[2]))
150
        os.remove(paths[1])
151
        os.remove(paths[2])
152
153
    def test_add_multiple_non_positive(self):
154
        """Verify 'doorstop add' rejects non-positive integers for counts."""
155
        self.assertRaises(SystemExit, main, ['add', 'TUT', '--count', '-1'])
156
157
    def test_add_specific_level(self):
158
        """Verify 'doorstop add' can be called with a specific level."""
159
        self.assertIs(None, main(['add', 'TUT', '--level', '1.42']))
160
        self.assertTrue(os.path.isfile(self.path))
161
162
    def test_add_error(self):
163
        """Verify 'doorstop add' returns an error with an unknown prefix."""
164
        self.assertRaises(SystemExit, main, ['add', 'UNKNOWN'])
165
166
167
@unittest.skipUnless(os.getenv(ENV), REASON)
168
@patch('doorstop.settings.ADDREMOVE_FILES', False)
169
class TestAddServer(unittest.TestCase):
170
    """Integration tests for the 'doorstop add' command using a server."""
171
172
    @classmethod
173
    def setUpClass(cls):
174
        number = get_next_number()
175
        filename = "TUT{}.yml".format(str(number).zfill(3))
176
        cls.path = os.path.join(TUTORIAL, filename)
177
178
    def tearDown(self):
179
        common.delete(self.path)
180
181
    @patch('doorstop.settings.SERVER_HOST', '')
182
    def test_add(self):
183
        """Verify 'doorstop add' expects a server."""
184
        self.assertRaises(SystemExit, main, ['add', 'TUT'])
185
186
    @patch('doorstop.settings.SERVER_HOST', None)
187
    def test_add_no_server(self):
188
        """Verify 'doorstop add' can be called if there is no server."""
189
        self.assertIs(None, main(['add', 'TUT']))
190
191
    @patch('doorstop.server.check', Mock())
192
    @patch('doorstop.core.document.Document.add_item')
193
    def test_add_custom_server(self, mock_add_item):
194
        """Verify 'doorstop add' can be called with a custom server."""
195
        self.assertIs(None, main(['add', 'TUT', '--server', '1.2.3.4']))
196
        mock_add_item.assert_called_once_with(level=None)
197
198
    def test_add_force(self):
199
        """Verify 'doorstop add' can be called with a missing server."""
200
        self.assertIs(None, main(['add', 'TUT', '--force']))
201
202
203
@unittest.skipUnless(os.getenv(ENV), REASON)
204
@patch('doorstop.settings.ADDREMOVE_FILES', False)
205
class TestRemove(unittest.TestCase):
206
    """Integration tests for the 'doorstop remove' command."""
207
208
    ITEM = os.path.join(TUTORIAL, 'TUT003.yml')
209
210
    def setUp(self):
211
        self.backup = common.read_text(self.ITEM)
212
213
    def tearDown(self):
214
        common.write_text(self.backup, self.ITEM)
215
216
    def test_remove(self):
217
        """Verify 'doorstop remove' can be called."""
218
        self.assertIs(None, main(['remove', 'tut3']))
219
        self.assertFalse(os.path.exists(self.ITEM))
220
221
    def test_remove_error(self):
222
        """Verify 'doorstop remove' returns an error on unknown item UIDs."""
223
        self.assertRaises(SystemExit, main, ['remove', 'tut9999'])
224
225
226
@unittest.skipUnless(os.getenv(ENV), REASON)
227
@patch('doorstop.settings.ADDREMOVE_FILES', False)
228
class TestReorder(unittest.TestCase):
229
    """Integration tests for the 'doorstop reorder' command."""
230
231
    @classmethod
232
    def setUpClass(cls):
233
        cls.prefix = 'tut'
234
        cls.path = os.path.join('reqs', 'tutorial', 'index.yml')
235
236
    def tearDown(self):
237
        common.delete(self.path)
238
239
    @patch('doorstop.core.editor.launch')
240
    @patch('builtins.input', Mock(return_value='yes'))
241
    def test_reorder_document_yes(self, mock_launch):
242
        """Verify 'doorstop reorder' can be called with a document (yes)."""
243
        self.assertIs(None, main(['reorder', self.prefix]))
244
        mock_launch.assert_called_once_with(self.path, tool=None)
245
        self.assertFalse(os.path.exists(self.path))
246
247
    @patch('doorstop.core.editor.launch')
248
    @patch('builtins.input', Mock(return_value='no'))
249
    def test_reorder_document_no(self, mock_launch):
250
        """Verify 'doorstop reorder' can be called with a document (no)."""
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
    def test_reorder_document_auto(self, mock_launch):
257
        """Verify 'doorstop reorder' can be called with a document (auto)."""
258
        self.assertIs(None, main(['reorder', self.prefix, '--auto']))
259
        self.assertEqual(0, mock_launch.call_count)
260
261
    @patch('doorstop.core.document.Document._reorder_automatic')
262
    @patch('doorstop.core.editor.launch')
263
    @patch('builtins.input', Mock(return_value='no'))
264
    def test_reorder_document_manual(self, mock_launch, mock_reorder_auto):
265
        """Verify 'doorstop reorder' can be called with a document (manual)."""
266
        self.assertIs(None, main(['reorder', self.prefix, '--manual']))
267
        mock_launch.assert_called_once_with(self.path, tool=None)
268
        self.assertEqual(0, mock_reorder_auto.call_count)
269
        self.assertFalse(os.path.exists(self.path))
270
271
    @patch('builtins.input', Mock(return_value='yes'))
272
    def test_reorder_document_error(self):
273
        """Verify 'doorstop reorder' can handle invalid YAML."""
274
275
        def bad_yaml_edit(path, **_):
276
            """Simulate adding invalid YAML to the index."""
277
            common.write_text("%bad", path)
278
279
        with patch('doorstop.core.editor.launch', bad_yaml_edit):
280
            self.assertRaises(SystemExit, main, ['reorder', self.prefix])
281
282
        self.assertTrue(os.path.exists(self.path))
283
284
    def test_reorder_document_unknown(self):
285
        """Verify 'doorstop reorder' returns an error on an unknown prefix."""
286
        self.assertRaises(SystemExit, main, ['reorder', 'FAKE'])
287
288
289
@unittest.skipUnless(os.getenv(ENV), REASON)
290
@patch('doorstop.settings.SERVER_HOST', None)
291
@patch('doorstop.settings.ADDREMOVE_FILES', False)
292
class TestEdit(unittest.TestCase):
293
    """Integration tests for the 'doorstop edit' command."""
294
295
    @patch('doorstop.core.editor.launch')
296
    def test_edit_item(self, mock_launch):
297
        """Verify 'doorstop edit' can be called with an item."""
298
        self.assertIs(None, main(['edit', 'tut2', '-T', 'my_editor']))
299
        path = os.path.join(TUTORIAL, 'TUT002.yml')
300
        mock_launch.assert_called_once_with(os.path.normpath(path), tool='my_editor')
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (85/80).

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

Loading history...
301
302
    def test_edit_item_unknown(self):
303
        """Verify 'doorstop edit' returns an error on an unknown item."""
304
        self.assertRaises(SystemExit, main, ['edit', '--item', 'FAKE001'])
305
306
    @patch('time.time', Mock(return_value=123))
307
    @patch('doorstop.core.editor.launch')
308
    @patch('builtins.input', Mock(return_value='yes'))
309
    def test_edit_document_yes_yes(self, mock_launch):
310
        """Verify 'doorstop edit' can be called with a document (yes, yes)."""
311
        path = "TUT-123.yml"
312
        self.assertIs(None, main(['edit', 'tut', '-T', 'my_editor']))
313
        mock_launch.assert_called_once_with(os.path.normpath(path), tool='my_editor')
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (85/80).

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

Loading history...
314
315
    @patch('time.time', Mock(return_value=456))
316
    @patch('doorstop.core.editor.launch')
317
    @patch('builtins.input', Mock(return_value='no'))
318
    def test_edit_document_no_no(self, mock_launch):
319
        """Verify 'doorstop edit' can be called with a document (no, no)."""
320
        path = "TUT-456.yml"
321
        self.assertIs(None, main(['edit', 'tut', '-T', 'my_editor']))
322
        common.delete(path)
323
        mock_launch.assert_called_once_with(os.path.normpath(path), tool='my_editor')
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (85/80).

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

Loading history...
324
325
    @patch('time.time', Mock(return_value=789))
326
    @patch('doorstop.core.editor.launch')
327
    @patch('builtins.input', Mock(side_effect=['no', 'yes']))
328
    def test_edit_document_no_yes(self, mock_launch):
329
        """Verify 'doorstop edit' can be called with a document (no, yes)."""
330
        path = "TUT-789.yml"
331
        self.assertIs(None, main(['edit', 'tut', '-T', 'my_editor']))
332
        mock_launch.assert_called_once_with(os.path.normpath(path), tool='my_editor')
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (85/80).

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

Loading history...
333
334
    def test_edit_document_unknown(self):
335
        """Verify 'doorstop edit' returns an error on an unknown document."""
336
        self.assertRaises(SystemExit, main, ['edit', '--document', 'FAKE'])
337
338
    def test_edit_error(self):
339
        """Verify 'doorstop edit' returns an error with an unknown UID."""
340
        self.assertRaises(SystemExit, main, ['edit', 'req9999'])
341
342 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
343
@unittest.skipUnless(os.getenv(ENV), REASON)
344
@patch('doorstop.settings.ADDREMOVE_FILES', False)
345
class TestLink(unittest.TestCase):
346
    """Integration tests for the 'doorstop link' command."""
347
348
    ITEM = os.path.join(TUTORIAL, 'TUT003.yml')
349
350
    def setUp(self):
351
        self.backup = common.read_text(self.ITEM)
352
353
    def tearDown(self):
354
        common.write_text(self.backup, self.ITEM)
355
356
    def test_link(self):
357
        """Verify 'doorstop link' can be called."""
358
        self.assertIs(None, main(['link', 'tut3', 'req2']))
359
360
    def test_link_unknown_child(self):
361
        """Verify 'doorstop link' returns an error with an unknown child."""
362
        self.assertRaises(SystemExit, main, ['link', 'unknown3', 'req2'])
363
        self.assertRaises(SystemExit, main, ['link', 'tut9999', 'req2'])
364
365
    def test_link_unknown_parent(self):
366
        """Verify 'doorstop link' returns an error with an unknown parent."""
367
        self.assertRaises(SystemExit, main, ['link', 'tut3', 'unknown2'])
368
        self.assertRaises(SystemExit, main, ['link', 'tut3', 'req9999'])
369
370 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
371
@unittest.skipUnless(os.getenv(ENV), REASON)
372
@patch('doorstop.settings.ADDREMOVE_FILES', False)
373
class TestUnlink(unittest.TestCase):
374
    """Integration tests for the 'doorstop unlink' command."""
375
376
    ITEM = os.path.join(TUTORIAL, 'TUT003.yml')
377
378
    def setUp(self):
379
        self.backup = common.read_text(self.ITEM)
380
        with patch('doorstop.settings.ADDREMOVE_FILES', False):
381
            main(['link', 'tut3', 'req2'])  # create a temporary link
382
383
    def tearDown(self):
384
        common.write_text(self.backup, self.ITEM)
385
386
    def test_unlink(self):
387
        """Verify 'doorstop unlink' can be called."""
388
        self.assertIs(None, main(['unlink', 'tut3', 'req2']))
389
390
    def test_unlink_unknown_child(self):
391
        """Verify 'doorstop unlink' returns an error with an unknown child."""
392
        self.assertRaises(SystemExit, main, ['unlink', 'unknown3', 'req2'])
393
        self.assertRaises(SystemExit, main, ['link', 'tut9999', 'req2'])
394
395
    def test_unlink_unknown_parent(self):
396
        """Verify 'doorstop unlink' returns an error with an unknown parent."""
397
        self.assertRaises(SystemExit, main, ['unlink', 'tut3', 'unknown2'])
398
        self.assertRaises(SystemExit, main, ['unlink', 'tut3', 'req9999'])
399
400
401 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...
402
class TestClear(unittest.TestCase):
403
    """Integration tests for the 'doorstop clear' command."""
404
405
    @patch('doorstop.core.item.Item.clear')
406
    def test_clear_item(self, mock_clear):
407
        """Verify 'doorstop clear' can be called with an item."""
408
        self.assertIs(None, main(['clear', 'tut2']))
409
        self.assertEqual(1, mock_clear.call_count)
410
411
    def test_clear_item_unknown(self):
412
        """Verify 'doorstop clear' returns an error on an unknown item."""
413
        self.assertRaises(SystemExit, main, ['clear', '--item', 'FAKE001'])
414
415
    @patch('doorstop.core.item.Item.clear')
416
    def test_clear_document(self, mock_clear):
417
        """Verify 'doorstop clear' can be called with a document"""
418
        self.assertIs(None, main(['clear', 'tut']))
419
        self.assertEqual(REQ_COUNT, mock_clear.call_count)
420
421
    def test_clear_document_unknown(self):
422
        """Verify 'doorstop clear' returns an error on an unknown document."""
423
        self.assertRaises(SystemExit, main, ['clear', '--document', 'FAKE'])
424
425
    @patch('doorstop.core.item.Item.clear')
426
    def test_clear_tree(self, mock_clear):
427
        """Verify 'doorstop clear' can be called with a tree"""
428
        self.assertIs(None, main(['clear', 'all']))
429
        self.assertEqual(ALL_COUNT, mock_clear.call_count)
430
431
    def test_clear_tree_item(self):
432
        """Verify 'doorstop clear' returns an error with tree and item."""
433
        self.assertRaises(SystemExit, main, ['clear', '--item', 'all'])
434
435
    def test_clear_tree_document(self):
436
        """Verify 'doorstop clear' returns an error with tree and document."""
437
        self.assertRaises(SystemExit, main, ['clear', '--document', 'all'])
438
439
    def test_clear_error(self):
440
        """Verify 'doorstop clear' returns an error with an unknown UID."""
441
        self.assertRaises(SystemExit, main, ['clear', 'req9999'])
442
443
444 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...
445
class TestReview(unittest.TestCase):
446
    """Integration tests for the 'doorstop review' command."""
447
448
    @patch('doorstop.core.item.Item.review')
449
    def test_review_item(self, mock_review):
450
        """Verify 'doorstop review' can be called with an item."""
451
        self.assertIs(None, main(['review', 'tut2']))
452
        self.assertEqual(1, mock_review.call_count)
453
454
    def test_review_item_unknown(self):
455
        """Verify 'doorstop review' returns an error on an unknown item."""
456
        self.assertRaises(SystemExit, main, ['review', '--item', 'FAKE001'])
457
458
    @patch('doorstop.core.item.Item.review')
459
    def test_review_document(self, mock_review):
460
        """Verify 'doorstop review' can be called with a document"""
461
        self.assertIs(None, main(['review', 'tut']))
462
        self.assertEqual(REQ_COUNT, mock_review.call_count)
463
464
    def test_review_document_unknown(self):
465
        """Verify 'doorstop review' returns an error on an unknown document."""
466
        self.assertRaises(SystemExit, main, ['review', '--document', 'FAKE'])
467
468
    @patch('doorstop.core.item.Item.review')
469
    def test_review_tree(self, mock_review):
470
        """Verify 'doorstop review' can be called with a tree"""
471
        self.assertIs(None, main(['review', 'all']))
472
        self.assertEqual(ALL_COUNT, mock_review.call_count)
473
474
    def test_review_tree_item(self):
475
        """Verify 'doorstop review' returns an error with tree and item."""
476
        self.assertRaises(SystemExit, main, ['review', '--item', 'all'])
477
478
    def test_review_tree_document(self):
479
        """Verify 'doorstop review' returns an error with tree and document."""
480
        self.assertRaises(SystemExit, main, ['review', '--document', 'all'])
481
482
    def test_review_error(self):
483
        """Verify 'doorstop review' returns an error with an unknown UID."""
484
        self.assertRaises(SystemExit, main, ['review', 'req9999'])
485
486
487
@unittest.skipUnless(os.getenv(ENV), REASON)
488
@patch('doorstop.settings.SERVER_HOST', None)
489
@patch('doorstop.settings.ADDREMOVE_FILES', False)
490
class TestImport(unittest.TestCase):
491
    """Integration tests for the 'doorstop import' command."""
492
493
    def tearDown(self):
494
        common.delete(os.path.join(ROOT, 'tmp'))
495
        common.delete(os.path.join(REQS, 'REQ099.yml'))
496
497
    def test_import_document(self):
498
        """Verify 'doorstop import' can import a document."""
499
        self.assertRaises(SystemExit,
500
                          main, ['import', '--document', 'TMP', 'tmp'])
501
502
    def test_import_document_with_parent(self):
503
        """Verify 'doorstop import' can import a document with a parent."""
504
        self.assertIs(None, main(['import', '--document', 'TMP', 'tmp',
505
                                  '--parent', 'REQ']))
506
507
    def test_import_item(self):
508
        """Verify 'doorstop import' can import an item.."""
509
        self.assertIs(None, main(['import', '--item', 'REQ', 'REQ099']))
510
511
    def test_import_item_with_attrs(self):
512
        """Verify 'doorstop import' can import an item with attributes."""
513
        self.assertIs(None, main(['import', '--item', 'REQ', 'REQ099',
514
                                  '--attrs', "{'text': 'The item text.'}"]))
515
516
    def test_import_error(self):
517
        """Verify 'doorstop import' requires a document or item."""
518
        self.assertRaises(SystemExit, main, ['import', '--attr', "{}"])
519
520
521
@unittest.skipUnless(os.getenv(ENV), REASON)
522
@patch('doorstop.settings.SERVER_HOST', None)
523
class TestImportFile(MockTestCase):
524
    """Integration tests for the 'doorstop import' command."""
525
526
    def test_import_file_missing_prefix(self):
527
        """Verify 'doorstop import' returns an error with a missing prefix."""
528
        path = os.path.join(FILES, 'exported.xlsx')
529
        self.assertRaises(SystemExit, main, ['import', path])
530
531
    def test_import_file_extra_flags(self):
532
        """Verify 'doorstop import' returns an error with extra flags."""
533
        path = os.path.join(FILES, 'exported.xlsx')
534
        self.assertRaises(SystemExit,
535
                          main, ['import', path, 'PREFIX', '-d', '_', '_'])
536
        self.assertRaises(SystemExit,
537
                          main, ['import', path, 'PREFIX', '-i', '_', '_'])
538
539
    def test_import_file_to_document_unknown(self):
540
        """Verify 'doorstop import' returns an error for unknown documents."""
541
        path = os.path.join(FILES, 'exported.xlsx')
542
        self.assertRaises(SystemExit, main, ['import', path, 'PREFIX'])
543
544
    def test_import_file_with_map(self):
545
        """Verify 'doorstop import' can import a file using a custom map."""
546
        path = os.path.join(FILES, 'exported-map.csv')
547
        dirpath = os.path.join(self.temp, 'imported', 'prefix')
548
        main(['create', 'PREFIX', dirpath])
549
        # Act
550
        self.assertIs(None, main(['import', path, 'PREFIX',
551
                                  '--map', "{'mylevel': 'level'}"]))
552
        # Assert
553
        path = os.path.join(dirpath, 'REQ001.yml')
554
        self.assertTrue(os.path.isfile(path))
555
        text = common.read_text(path)
556
        self.assertIn('\nlevel: 1.2.3', text)
557
558
    def test_import_file_with_map_invalid(self):
559
        """Verify 'doorstop import' returns an error with an invalid map."""
560
        path = os.path.join(FILES, 'exported.csv')
561
        self.assertRaises(SystemExit,
562
                          main, ['import', path, 'PREFIX', '--map', "{'my"])
563
564
    def test_import_csv_to_document_existing(self):
565
        """Verify 'doorstop import' can import CSV to an existing document."""
566
        path = os.path.join(FILES, 'exported.csv')
567
        dirpath = os.path.join(self.temp, 'imported', 'prefix')
568
        main(['create', 'PREFIX', dirpath])
569
        # Act
570
        self.assertIs(None, main(['import', path, 'PREFIX']))
571
        # Assert
572
        path = os.path.join(dirpath, 'REQ001.yml')
573
        self.assertTrue(os.path.isfile(path))
574
575
    def test_import_tsv_to_document_existing(self):
576
        """Verify 'doorstop import' can import TSV to an existing document."""
577
        path = os.path.join(FILES, 'exported.tsv')
578
        dirpath = os.path.join(self.temp, 'imported', 'prefix')
579
        main(['create', 'PREFIX', dirpath])
580
        # Act
581
        self.assertIs(None, main(['import', path, 'PREFIX']))
582
        # Assert
583
        path = os.path.join(dirpath, 'REQ001.yml')
584
        self.assertTrue(os.path.isfile(path))
585
586
    def test_import_xlsx_to_document_existing(self):
587
        """Verify 'doorstop import' can import XLSX to an existing document."""
588
        path = os.path.join(FILES, 'exported.xlsx')
589
        dirpath = os.path.join(self.temp, 'imported', 'prefix')
590
        main(['create', 'PREFIX', dirpath])
591
        # Act
592
        self.assertIs(None, main(['import', path, 'PREFIX']))
593
        # Assert
594
        path = os.path.join(dirpath, 'REQ001.yml')
595
        self.assertTrue(os.path.isfile(path))
596
597
598
@unittest.skipUnless(os.getenv(ENV), REASON)
599
@patch('doorstop.settings.ADDREMOVE_FILES', False)
600
class TestImportServer(unittest.TestCase):
601
    """Integration tests for the 'doorstop import' command using a server."""
602
603
    def tearDown(self):
604
        common.delete(os.path.join(ROOT, 'tmp'))
605
        common.delete(os.path.join(REQS, 'REQ099.yml'))
606
607
    def test_import_item_force(self):
608
        """Verify 'doorstop import' can import an item without a server."""
609
        self.assertIs(None,
610
                      main(['import', '--item', 'REQ', 'REQ099', '--force']))
611
612
613
@unittest.skipUnless(os.getenv(ENV), REASON)
614
class TestExport(TempTestCase):
615
    """Integration tests for the 'doorstop export' command."""
616
617
    def test_export_document_error_unknown(self):
618
        """Verify 'doorstop export' returns an error for an unknown format."""
619
        self.assertRaises(SystemExit, main, ['export', 'req', 'req.fake'])
620
621
    def test_export_document_error_directory(self):
622
        """Verify 'doorstop publish' returns an error with a directory."""
623
        self.assertRaises(SystemExit, main, ['export', 'req', self.temp])
624
625
    def test_export_document_error_no_extension(self):
626
        """Verify 'doorstop publish' returns an error with no extension."""
627
        path = os.path.join(self.temp, 'req')
628
        self.assertRaises(SystemExit, main, ['export', 'req', path])
629
630
    def test_export_document_stdout(self):
631
        """Verify 'doorstop export' can create output."""
632
        self.assertIs(None, main(['export', 'tut']))
633
634
    def test_export_document_stdout_width(self):
635
        """Verify 'doorstop export' can create output."""
636
        self.assertIs(None, main(['export', 'tut', '--width', '72']))
637
638
    def test_export_document_yaml(self):
639
        """Verify 'doorstop export' can create a YAML file."""
640
        path = os.path.join(self.temp, 'tut.yml')
641
        self.assertIs(None, main(['export', 'tut', path]))
642
        self.assertTrue(os.path.isfile(path))
643
644
    def test_export_document_xlsx(self):
645
        """Verify 'doorstop export' can create an XLSX file."""
646
        path = os.path.join(self.temp, 'tut.xlsx')
647
        self.assertIs(None, main(['export', 'tut', path]))
648
        self.assertTrue(os.path.isfile(path))
649
650
    @patch('openpyxl.Workbook.save', Mock(side_effect=PermissionError))
651
    def test_export_document_xlsx_error(self):
652
        """Verify 'doorstop export' can handle IO errors."""
653
        path = os.path.join(self.temp, 'tut.xlsx')
654
        self.assertRaises(SystemExit, main, ['export', 'tut', path])
655
        self.assertFalse(os.path.isfile(path))
656
657
    def test_export_tree_xlsx(self):
658
        """Verify 'doorstop export' can create an XLSX directory."""
659
        path = os.path.join(self.temp, 'all')
660
        self.assertIs(None, main(['export', 'all', path, '--xlsx']))
661
        self.assertTrue(os.path.isdir(path))
662
663
    def test_export_tree_no_path(self):
664
        """Verify 'doorstop export' returns an error with no path."""
665
        self.assertRaises(SystemExit, main, ['export', 'all'])
666
667
668
@unittest.skipUnless(os.getenv(ENV), REASON)
669
class TestPublish(TempTestCase):
670
    """Integration tests for the 'doorstop publish' command."""
671
672
    def setUp(self):
673
        super().setUp()
674
        self.backup = (settings.PUBLISH_CHILD_LINKS,
675
                       settings.PUBLISH_BODY_LEVELS)
676
677
    def tearDown(self):
678
        super().tearDown()
679
        (settings.PUBLISH_CHILD_LINKS,
680
         settings.PUBLISH_BODY_LEVELS) = self.backup
681
682
    def test_publish_unknown(self):
683
        """Verify 'doorstop publish' returns an error for an unknown format."""
684
        self.assertRaises(SystemExit, main, ['publish', 'req', 'req.fake'])
685
686
    def test_publish_document(self):
687
        """Verify 'doorstop publish' can create output."""
688
        self.assertIs(None, main(['publish', 'tut']))
689
        self.assertTrue(settings.PUBLISH_CHILD_LINKS)
690
691
    def test_publish_document_with_child_links(self):
692
        """Verify 'doorstop publish' can create output with child links."""
693
        self.assertIs(None, main(['publish', 'tut']))
694
        self.assertTrue(settings.PUBLISH_CHILD_LINKS)
695
696
    def test_publish_document_without_child_links(self):
697
        """Verify 'doorstop publish' can create output without child links."""
698
        self.assertIs(None, main(['publish', 'tut', '--no-child-links']))
699
        self.assertFalse(settings.PUBLISH_CHILD_LINKS)
700
701
    def test_publish_document_without_body_levels(self):
702
        """Verify 'doorstop publish' can create output without body levels."""
703
        self.assertIs(None, main(['publish', 'tut', '--no-body-levels']))
704
        self.assertFalse(settings.PUBLISH_BODY_LEVELS)
705
706
    def test_publish_document_no_body_levels(self):
707
        """Verify 'doorstop publish' can create output without body levels."""
708
        self.assertIs(None, main(['publish', 'tut', '--no-levels=body']))
709
        self.assertFalse(settings.PUBLISH_BODY_LEVELS)
710
711
    def test_publish_document_no_body_or_heading_levels(self):
712
        """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...
713
        self.assertIs(None, main(['publish', 'tut', '--no-levels=all']))
714
        self.assertFalse(settings.PUBLISH_BODY_LEVELS)
715
        self.assertFalse(settings.PUBLISH_HEADING_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
class TestPublishCommand(TempTestCase):
781
    """Tests 'doorstop publish' options toc and template"""
782
783
    @patch('doorstop.core.publisher.publish')
784
    def test_publish_document_template(self, mock_publish):
785
        """Verify 'doorstop publish' is called with template."""
786
        path = os.path.join(self.temp, 'req.html')
787
        self.assertIs(None, main(['publish', '--template',
788
                                  'my_template.html', 'req', path]))
789
        mock_publish.assert_called_once_with(Document(os.path.abspath(REQS)),
790
                                             path, '.html',
791
                                             template='my_template.html')
792
793
    @patch('doorstop.core.publisher.publish_lines')
794
    def test_publish_document_to_stdout(self, mock_publish_lines):
795
        """Verify 'doorstop publish_lines' is called when no output path specified"""
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (85/80).

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

Loading history...
796
        self.assertIs(None, main(['publish', 'req']))
797
        mock_publish_lines.assert_called_once_with(Document(os.path.abspath(REQS)),
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (83/80).

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

Loading history...
798
                                                   '.txt')
799
800
801
@patch('doorstop.cli.commands.run', Mock(return_value=True))
802
class TestLogging(unittest.TestCase):
803
    """Integration tests for the Doorstop CLI logging."""
804
805
    def test_verbose_0(self):
806
        """Verify verbose level 0 can be set."""
807
        self.assertIs(None, main([]))
808
809
    def test_verbose_1(self):
810
        """Verify verbose level 1 can be set."""
811
        self.assertIs(None, main(['-v']))
812
813
    def test_verbose_2(self):
814
        """Verify verbose level 2 can be set."""
815
        self.assertIs(None, main(['-vv']))
816
817
    def test_verbose_3(self):
818
        """Verify verbose level 3 can be set."""
819
        self.assertIs(None, main(['-vvv']))
820
821
    def test_verbose_4(self):
822
        """Verify verbose level 4 can be set."""
823
        self.assertIs(None, main(['-vvvv']))
824
825
    def test_verbose_5(self):
826
        """Verify verbose level 5 cannot be set."""
827
        self.assertIs(None, main(['-vvvvv']))
828
        self.assertEqual(4, common.verbosity)
829
830
    def test_verbose_quiet(self):
831
        """Verify verbose level -1 can be set."""
832
        self.assertIs(None, main(['-q']))
833
        self.assertEqual(-1, common.verbosity)
834