Completed
Push — develop ( 3e9776...c3c8a7 )
by Jace
03:01
created

doorstop/core/test/test_publisher.py (4 issues)

1
"""Unit tests for the doorstop.core.publisher module."""
2
3
import unittest
4
from unittest.mock import patch, Mock, MagicMock
5
6
import os
7
8
from doorstop.common import DoorstopError
9
from doorstop.core import publisher
10
11
from doorstop.core.test import FILES, EMPTY, MockDataMixIn, MockItemAndVCS
12
13
14
class TestModule(MockDataMixIn, unittest.TestCase):
15
    """Unit tests for the doorstop.core.publisher module."""
16
17
    @patch('os.makedirs')
18
    @patch('builtins.open')
19
    def test_publish_document(self, mock_open, mock_makedirs):
20
        """Verify a document can be published."""
21
        dirpath = os.path.join('mock', 'directory')
22
        path = os.path.join(dirpath, 'published.html')
23
        self.document.items = []
24
        # Act
25
        path2 = publisher.publish(self.document, path)
26
        # Assert
27
        self.assertIs(path, path2)
28
        mock_makedirs.assert_called_once_with(dirpath)
29
        mock_open.assert_called_once_with(path, 'wb')
30
31
    @patch('os.makedirs')
32
    @patch('builtins.open')
33
    @patch('doorstop.core.publisher.publish_lines')
34
    def test_publish_document_html(self, mock_lines, mock_open, mock_makedirs):
35
        """Verify a (mock) HTML file can be created."""
36
        dirpath = os.path.join('mock', 'directory')
37
        path = os.path.join(dirpath, 'published.custom')
38
        # Act
39
        path2 = publisher.publish(self.document, path, '.html')
40
        # Assert
41
        self.assertIs(path, path2)
42
        mock_makedirs.assert_called_once_with(dirpath)
43
        mock_open.assert_called_once_with(path, 'wb')
44
        mock_lines.assert_called_once_with(self.document, '.html',
45
                                           linkify=False)
46
47
    def test_publish_document_unknown(self):
48
        """Verify an exception is raised when publishing unknown formats."""
49
        self.assertRaises(DoorstopError,
50
                          publisher.publish, self.document, 'a.a')
51
        self.assertRaises(DoorstopError,
52
                          publisher.publish, self.document, 'a.txt', '.a')
53
54
    @patch('doorstop.core.publisher._index')
55
    @patch('os.makedirs')
56
    @patch('builtins.open')
57
    def test_publish_tree(self, mock_open, mock_makedirs, mock_index):
58
        """Verify a tree can be published."""
59
        dirpath = os.path.join('mock', 'directory')
60
        # Act
61
        dirpath2 = publisher.publish(self.mock_tree, dirpath)
62
        # Assert
63
        self.assertIs(dirpath, dirpath2)
64
        self.assertEqual(1, mock_makedirs.call_count)
65
        self.assertEqual(2, mock_open.call_count)
66
        mock_index.assert_called_once_with(dirpath, tree=self.mock_tree)
67
68
    @patch('doorstop.core.publisher._index')
69
    @patch('os.makedirs')
70
    @patch('builtins.open')
71
    def test_publish_tree_no_index(self, mock_open, mock_makedirs, mock_index):
72
        """Verify a tree can be published."""
73
        dirpath = os.path.join('mock', 'directory')
74
        # Act
75
        dirpath2 = publisher.publish(self.mock_tree, dirpath, index=False)
76
        # Assert
77
        self.assertIs(dirpath, dirpath2)
78
        self.assertEqual(1, mock_makedirs.call_count)
79
        self.assertEqual(2, mock_open.call_count)
80
        self.assertEqual(0, mock_index.call_count)
81
82
    @patch('doorstop.core.publisher._index')
83
    def test_publish_tree_no_documents(self, mock_index):
84
        """Verify a tree can be published with no documents."""
85
        dirpath = os.path.join('mock', 'directory')
86
        mock_tree = MagicMock()
87
        mock_tree.documents = []
88
        # Act
89
        path2 = publisher.publish(mock_tree, dirpath, index=False)
90
        # Assert
91
        self.assertIs(None, path2)
92
        self.assertEqual(0, mock_index.call_count)
93
94
    def test_index(self):
95
        """Verify an HTML index can be created."""
96
        # Arrange
97
        path = os.path.join(FILES, 'index.html')
98
        # Act
99
        publisher._index(FILES)  # pylint: disable=W0212
100
        # Assert
101
        self.assertTrue(os.path.isfile(path))
102
103
    def test_index_no_files(self):
104
        """Verify an HTML index is only created when files exist."""
105
        path = os.path.join(EMPTY, 'index.html')
106
        # Act
107
        publisher._index(EMPTY)  # pylint: disable=W0212
108
        # Assert
109
        self.assertFalse(os.path.isfile(path))
110
111
    def test_index_tree(self):
112
        """Verify an HTML index can be created with a tree."""
113
        path = os.path.join(FILES, 'index2.html')
114
        mock_tree = MagicMock()
115
        mock_tree.documents = []
116
        for prefix in ('SYS', 'HLR', 'LLR', 'HLT', 'LLT'):
117
            mock_document = MagicMock()
118
            mock_document.prefix = prefix
119
            mock_tree.documents.append(mock_document)
120
        mock_tree.draw = lambda: "(mock tree structure)"
121
        mock_item = Mock()
122
        mock_item.uid = 'KNOWN-001'
123
        mock_item.document = Mock()
124
        mock_item.document.prefix = 'KNOWN'
125
        mock_item_unknown = Mock(spec=['uid'])
126
        mock_item_unknown.uid = 'UNKNOWN-002'
127
        mock_trace = [
128
            (None, mock_item, None, None, None),
129
            (None, None, None, mock_item_unknown, None),
130
            (None, None, None, None, None),
131
        ]
132
        mock_tree.get_traceability = lambda: mock_trace
133
        # Act
134
        publisher._index(FILES, index="index2.html", tree=mock_tree)  # pylint: disable=W0212
135
        # Assert
136
        self.assertTrue(os.path.isfile(path))
137
138
    def test_lines_text_item(self):
139
        """Verify text can be published from an item."""
140
        with patch.object(self.item5, 'find_ref',
141
                          Mock(return_value=('path/to/mock/file', 42))):
142
            lines = publisher.publish_lines(self.item5, '.txt')
143
            text = ''.join(line + '\n' for line in lines)
144
        self.assertIn("Reference: path/to/mock/file (line 42)", text)
145
146
    def test_lines_text_item_heading(self):
147
        """Verify text can be published from an item (heading)."""
148
        expected = "1.1     Heading\n\n"
149
        lines = publisher.publish_lines(self.item, '.txt')
150
        # Act
151
        text = ''.join(line + '\n' for line in lines)
152
        # Assert
153
        self.assertEqual(expected, text)
154
155
    @patch('doorstop.settings.PUBLISH_HEADING_LEVELS', False)
156
    def test_lines_text_item_heading_no_heading_levels(self):
157
        """Verify an item heading level can be ommitted."""
158
        expected = "Heading\n\n"
159
        lines = publisher.publish_lines(self.item, '.txt')
160
        # Act
161
        text = ''.join(line + '\n' for line in lines)
162
        # Assert
163
        self.assertEqual(expected, text)
164
165
    def test_single_line_heading_to_markdown(self):
166
        """A single line heading is published as a heading with an attribute equal to the item id"""
0 ignored issues
show
This line is too long as per the coding-style (100/80).

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

Loading history...
167
        expected = "## 1.1 Heading {#req3 }\n\n"
168
        lines = publisher.publish_lines(self.item, '.md', linkify=True)
169
        # Act
170
        text = ''.join(line + '\n' for line in lines)
171
        # Assert
172
        self.assertEqual(expected, text)
173
174
    def test_multi_line_heading_to_markdown(self):
175
        """A multi line heading is published as a heading with an attribute equal to the item id"""
0 ignored issues
show
This line is too long as per the coding-style (99/80).

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

Loading history...
176
        item = MockItemAndVCS('path/to/req3.yml',
177
                              _file=("links: [sys3]" + '\n'
178
                                     "text: 'Heading\n\nThis section describes publishing.'" + '\n'
0 ignored issues
show
This line is too long as per the coding-style (99/80).

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

Loading history...
179
                                     "level: 1.1.0" + '\n'
180
                                     "normative: false"))
181
        expected = "## 1.1 Heading {#req3 }\nThis section describes publishing.\n\n"
0 ignored issues
show
This line is too long as per the coding-style (84/80).

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

Loading history...
182
        lines = publisher.publish_lines(item, '.md', linkify=True)
183
        # Act
184
        text = ''.join(line + '\n' for line in lines)
185
        # Assert
186
        self.assertEqual(expected, text)
187
188
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
189
    def test_lines_text_item_normative(self):
190
        """Verify text can be published from an item (normative)."""
191
        expected = ("1.2     req4" + '\n\n'
192
                    "        This shall..." + '\n\n'
193
                    "        Reference: Doorstop.sublime-project" + '\n\n'
194
                    "        Links: sys4" + '\n\n')
195
        lines = publisher.publish_lines(self.item3, '.txt')
196
        # Act
197
        text = ''.join(line + '\n' for line in lines)
198
        # Assert
199
        self.assertEqual(expected, text)
200
201
    @patch('doorstop.settings.CHECK_REF', False)
202
    def test_lines_text_item_no_ref(self):
203
        """Verify text can be published without checking references."""
204
        lines = publisher.publish_lines(self.item5, '.txt')
205
        text = ''.join(line + '\n' for line in lines)
206
        self.assertIn("Reference: 'abc123'", text)
207
208
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', True)
209
    def test_lines_text_item_with_child_links(self):
210
        """Verify text can be published with child links."""
211
        # Act
212
        lines = publisher.publish_lines(self.item2, '.txt')
213
        text = ''.join(line + '\n' for line in lines)
214
        # Assert
215
        self.assertIn("Child links: tst1", text)
216
217
    def test_lines_markdown_item(self):
218
        """Verify Markdown can be published from an item."""
219
        with patch.object(self.item5, 'find_ref',
220
                          Mock(return_value=('path/to/mock/file', 42))):
221
            lines = publisher.publish_lines(self.item5, '.md')
222
            text = ''.join(line + '\n' for line in lines)
223
        self.assertIn("> `path/to/mock/file` (line 42)", text)
224
225
    def test_lines_markdown_item_heading(self):
226
        """Verify Markdown can be published from an item (heading)."""
227
        expected = "## 1.1 Heading {#req3 }\n\n"
228
        # Act
229
        lines = publisher.publish_lines(self.item, '.md', linkify=True)
230
        text = ''.join(line + '\n' for line in lines)
231
        # Assert
232
        self.assertEqual(expected, text)
233
234
    @patch('doorstop.settings.PUBLISH_HEADING_LEVELS', False)
235
    def test_lines_markdown_item_heading_no_heading_levels(self):
236
        """Verify an item heading level can be ommitted."""
237
        expected = "## Heading {#req3 }\n\n"
238
        # Act
239
        lines = publisher.publish_lines(self.item, '.md', linkify=True)
240
        text = ''.join(line + '\n' for line in lines)
241
        # Assert
242
        self.assertEqual(expected, text)
243
244
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
245
    def test_lines_markdown_item_normative(self):
246
        """Verify Markdown can be published from an item (normative)."""
247
        expected = ("## 1.2 req4" + '\n\n'
248
                    "This shall..." + '\n\n'
249
                    "> `Doorstop.sublime-project`" + '\n\n'
250
                    "*Links: sys4*" + '\n\n')
251
        # Act
252
        lines = publisher.publish_lines(self.item3, '.md', linkify=False)
253
        text = ''.join(line + '\n' for line in lines)
254
        # Assert
255
        self.assertEqual(expected, text)
256
257
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', True)
258
    def test_lines_markdown_item_with_child_links(self):
259
        """Verify Markdown can be published from an item w/ child links."""
260
        # Act
261
        lines = publisher.publish_lines(self.item2, '.md')
262
        text = ''.join(line + '\n' for line in lines)
263
        # Assert
264
        self.assertIn("Child links: tst1", text)
265
266
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
267
    def test_lines_markdown_item_without_child_links(self):
268
        """Verify Markdown can be published from an item w/o child links."""
269
        # Act
270
        lines = publisher.publish_lines(self.item2, '.md')
271
        text = ''.join(line + '\n' for line in lines)
272
        # Assert
273
        self.assertNotIn("Child links", text)
274
275
    @patch('doorstop.settings.PUBLISH_BODY_LEVELS', False)
276
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
277
    def test_lines_markdown_item_without_body_levels(self):
278
        """Verify Markdown can be published from an item (no body levels)."""
279
        expected = ("## req4" + '\n\n'
280
                    "This shall..." + '\n\n'
281
                    "> `Doorstop.sublime-project`" + '\n\n'
282
                    "*Links: sys4*" + '\n\n')
283
        # Act
284
        lines = publisher.publish_lines(self.item3, '.md', linkify=False)
285
        text = ''.join(line + '\n' for line in lines)
286
        # Assert
287
        self.assertEqual(expected, text)
288
289
    @patch('doorstop.settings.CHECK_REF', False)
290
    def test_lines_markdown_item_no_ref(self):
291
        """Verify Markdown can be published without checking references."""
292
        lines = publisher.publish_lines(self.item5, '.md')
293
        text = ''.join(line + '\n' for line in lines)
294
        self.assertIn("> 'abc123'", text)
295
296
    def test_lines_html_item(self):
297
        """Verify HTML can be published from an item."""
298
        expected = '<h2>1.1 Heading</h2>\n'
299
        # Act
300
        lines = publisher.publish_lines(self.item, '.html')
301
        text = ''.join(line + '\n' for line in lines)
302
        # Assert
303
        self.assertEqual(expected, text)
304
305
    @patch('doorstop.settings.PUBLISH_HEADING_LEVELS', False)
306
    def test_lines_html_item_no_heading_levels(self):
307
        """Verify an item heading level can be ommitted."""
308
        expected = '<h2>Heading</h2>\n'
309
        # Act
310
        lines = publisher.publish_lines(self.item, '.html')
311
        text = ''.join(line + '\n' for line in lines)
312
        # Assert
313
        self.assertEqual(expected, text)
314
315
    def test_lines_html_item_linkify(self):
316
        """Verify HTML (hyper) can be published from an item."""
317
        expected = '<h2 id="req3">1.1 Heading</h2>\n'
318
        # Act
319
        lines = publisher.publish_lines(self.item, '.html', linkify=True)
320
        text = ''.join(line + '\n' for line in lines)
321
        # Assert
322
        self.assertEqual(expected, text)
323
324
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', True)
325
    def test_lines_html_item_with_child_links(self):
326
        """Verify HTML can be published from an item w/ child links."""
327
        # Act
328
        lines = publisher.publish_lines(self.item2, '.html')
329
        text = ''.join(line + '\n' for line in lines)
330
        # Assert
331
        self.assertIn("Child links: tst1", text)
332
333
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
334
    def test_lines_html_item_without_child_links(self):
335
        """Verify HTML can be published from an item w/o child links."""
336
        # Act
337
        lines = publisher.publish_lines(self.item2, '.html')
338
        text = ''.join(line + '\n' for line in lines)
339
        # Assert
340
        self.assertNotIn("Child links", text)
341
342
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', True)
343
    def test_lines_html_item_with_child_links_linkify(self):
344
        """Verify HTML (hyper) can be published from an item w/ child links."""
345
        # Act
346
        lines = publisher.publish_lines(self.item2, '.html', linkify=True)
347
        text = ''.join(line + '\n' for line in lines)
348
        # Assert
349
        self.assertIn("Child links:", text)
350
        self.assertIn("tst.html#tst1", text)
351
352
    def test_lines_unknown(self):
353
        """Verify an exception is raised when iterating an unknown format."""
354
        # Act
355
        gen = publisher.publish_lines(self.document, '.a')
356
        # Assert
357
        self.assertRaises(DoorstopError, list, gen)
358