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

TestModule.test_publish_document_copies_assets()   A

Complexity

Conditions 1

Size

Total Lines 16

Duplication

Lines 15
Ratio 93.75 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 15
loc 16
rs 9.4285
1
"""Unit tests for the doorstop.core.publisher module."""
2
3
import unittest
4
from unittest.mock import patch, Mock, MagicMock, call
5
6
import os
7
import shutil
0 ignored issues
show
Unused Code introduced by
The import shutil seems to be unused.
Loading history...
8
9
from doorstop.common import DoorstopError
10
from doorstop.core import publisher
11
from doorstop.core.document import Document
12
from doorstop.core.test import FILES, EMPTY, MockDataMixIn, MockItemAndVCS, MockDocument
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (88/80).

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

Loading history...
13
14
15
class TestModule(MockDataMixIn, unittest.TestCase):
16
    """Unit tests for the doorstop.core.publisher module."""
17
18 View Code Duplication
    @patch('os.path.isdir', Mock(return_value=False))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
19
    @patch('os.makedirs')
20
    @patch('builtins.open')
21
    def test_publish_document(self, mock_open, mock_makedirs):
22
        """Verify a document can be published."""
23
        dirpath = os.path.join('mock', 'directory')
24
        path = os.path.join(dirpath, 'published.html')
25
        self.document.items = []
26
        # Act
27
        path2 = publisher.publish(self.document, path)
28
        # Assert
29
        self.assertIs(path, path2)
30
        mock_makedirs.assert_called_once_with(os.path.join(dirpath, Document.ASSETS))
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...
31
        mock_open.assert_called_once_with(path, 'wb')
32
33 View Code Duplication
    @patch('os.path.isdir', Mock(return_value=False))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
34
    @patch('os.makedirs')
35
    @patch('builtins.open')
36
    @patch('doorstop.core.publisher.publish_lines')
37
    def test_publish_document_html(self, mock_lines, mock_open, mock_makedirs):
38
        """Verify a (mock) HTML file can be created."""
39
        dirpath = os.path.join('mock', 'directory')
40
        path = os.path.join(dirpath, 'published.custom')
41
        # Act
42
        path2 = publisher.publish(self.document, path, '.html')
43
        # Assert
44
        self.assertIs(path, path2)
45
        mock_makedirs.assert_called_once_with(os.path.join(dirpath, Document.ASSETS))
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...
46
        mock_open.assert_called_once_with(path, 'wb')
47
        mock_lines.assert_called_once_with(self.document, '.html',
48
                                           linkify=False)
49
50
    @patch('os.path.isdir', Mock(return_value=True))
51
    @patch('shutil.rmtree')
52
    @patch('glob.glob')
53
    @patch('builtins.open')
54
    @patch('doorstop.core.publisher.publish_lines')
55
    def test_publish_document_deletes_the_contents_of_assets_folder(self, mock_lines, mock_open, mock_glob, mock_rm):
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (117/80).

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

Loading history...
56
        """Verify that the contents of an assets directory next to the published file is deleted"""
0 ignored issues
show
Coding Style introduced by
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...
57
        dirpath = os.path.abspath(os.path.join('mock', 'directory'))
58
        path = os.path.join(dirpath, 'published.custom')
59
        assets = [os.path.join(dirpath, Document.ASSETS, dir) for dir in ['css', 'logo.png']]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (93/80).

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

Loading history...
60
        mock_glob.return_value = assets
61
        # Act
62
        path2 = publisher.publish(self.document, path, '.html')
63
        # Assert
64
        self.assertIs(path, path2)
65
        mock_open.assert_called_once_with(path, 'wb')
66
        mock_lines.assert_called_once_with(self.document, '.html',
67
                                           linkify=False)
68
        calls = [call(assets[0]), call(assets[1])]
69
        self.assertEquals(calls, mock_rm.call_args_list)
70 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
71
    @patch('os.path.isdir', Mock(return_value=False))
72
    @patch('doorstop.core.document.Document.copy_assets')
73
    @patch('os.makedirs')
74
    @patch('builtins.open')
75
    def test_publish_document_copies_assets(self, mock_open, mock_makedirs, mock_copyassets):
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (93/80).

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

Loading history...
Unused Code introduced by
The argument mock_open seems to be unused.
Loading history...
76
        """Verify that assets are published"""
77
        dirpath = os.path.join('mock', 'directory')
78
        assets_path = os.path.join(dirpath, 'assets')
79
        path = os.path.join(dirpath, 'published.custom')
80
        document = MockDocument('/some/path')
81
        # Act
82
        path2 = publisher.publish(document, path, '.html')
83
        # Assert
84
        self.assertIs(path, path2)
85
        mock_makedirs.assert_called_once_with(os.path.join(dirpath, Document.ASSETS))
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...
86
        mock_copyassets.assert_called_once_with(assets_path)
87
88
    def test_publish_document_unknown(self):
89
        """Verify an exception is raised when publishing unknown formats."""
90
        self.assertRaises(DoorstopError,
91
                          publisher.publish, self.document, 'a.a')
92
        self.assertRaises(DoorstopError,
93
                          publisher.publish, self.document, 'a.txt', '.a')
94
95
    @patch('doorstop.core.publisher._index')
96
    @patch('builtins.open')
97
    def test_publish_tree(self, mock_open, mock_index):
98
        """Verify a tree can be published."""
99
        dirpath = os.path.join('mock', 'directory')
100
        # Act
101
        dirpath2 = publisher.publish(self.mock_tree, dirpath)
102
        # Assert
103
        self.assertIs(dirpath, dirpath2)
104
        self.assertEqual(2, mock_open.call_count)
105
        mock_index.assert_called_once_with(dirpath, tree=self.mock_tree)
106
107
    @patch('os.path.isdir', Mock(return_value=True))
108
    @patch('doorstop.core.publisher._index')
109
    @patch('builtins.open')
110
    def test_publish_tree_no_index(self, mock_open, mock_index):
111
        """Verify a tree can be published."""
112
        dirpath = os.path.join('mock', 'directory')
113
        # Act
114
        dirpath2 = publisher.publish(self.mock_tree, dirpath, index=False)
115
        # Assert
116
        self.assertIs(dirpath, dirpath2)
117
        self.assertEqual(2, mock_open.call_count)
118
        self.assertEqual(0, mock_index.call_count)
119
120
    @patch('doorstop.core.publisher._index')
121
    def test_publish_tree_no_documents(self, mock_index):
122
        """Verify a tree can be published with no documents."""
123
        dirpath = os.path.join('mock', 'directory')
124
        mock_tree = MagicMock()
125
        mock_tree.documents = []
126
        # Act
127
        path2 = publisher.publish(mock_tree, dirpath, index=False)
128
        # Assert
129
        self.assertIs(None, path2)
130
        self.assertEqual(0, mock_index.call_count)
131
132
    def test_index(self):
133
        """Verify an HTML index can be created."""
134
        # Arrange
135
        path = os.path.join(FILES, 'index.html')
136
        # Act
137
        publisher._index(FILES)  # pylint: disable=W0212
138
        # Assert
139
        self.assertTrue(os.path.isfile(path))
140
141
    def test_index_no_files(self):
142
        """Verify an HTML index is only created when files exist."""
143
        path = os.path.join(EMPTY, 'index.html')
144
        # Act
145
        publisher._index(EMPTY)  # pylint: disable=W0212
146
        # Assert
147
        self.assertFalse(os.path.isfile(path))
148
149
    def test_index_tree(self):
150
        """Verify an HTML index can be created with a tree."""
151
        path = os.path.join(FILES, 'index2.html')
152
        mock_tree = MagicMock()
153
        mock_tree.documents = []
154
        for prefix in ('SYS', 'HLR', 'LLR', 'HLT', 'LLT'):
155
            mock_document = MagicMock()
156
            mock_document.prefix = prefix
157
            mock_tree.documents.append(mock_document)
158
        mock_tree.draw = lambda: "(mock tree structure)"
159
        mock_item = Mock()
160
        mock_item.uid = 'KNOWN-001'
161
        mock_item.document = Mock()
162
        mock_item.document.prefix = 'KNOWN'
163
        mock_item_unknown = Mock(spec=['uid'])
164
        mock_item_unknown.uid = 'UNKNOWN-002'
165
        mock_trace = [
166
            (None, mock_item, None, None, None),
167
            (None, None, None, mock_item_unknown, None),
168
            (None, None, None, None, None),
169
        ]
170
        mock_tree.get_traceability = lambda: mock_trace
171
        # Act
172
        publisher._index(FILES, index="index2.html", tree=mock_tree)  # pylint: disable=W0212
173
        # Assert
174
        self.assertTrue(os.path.isfile(path))
175
176
    def test_lines_text_item(self):
177
        """Verify text can be published from an item."""
178
        with patch.object(self.item5, 'find_ref',
179
                          Mock(return_value=('path/to/mock/file', 42))):
180
            lines = publisher.publish_lines(self.item5, '.txt')
181
            text = ''.join(line + '\n' for line in lines)
182
        self.assertIn("Reference: path/to/mock/file (line 42)", text)
183
184
    def test_lines_text_item_heading(self):
185
        """Verify text can be published from an item (heading)."""
186
        expected = "1.1     Heading\n\n"
187
        lines = publisher.publish_lines(self.item, '.txt')
188
        # Act
189
        text = ''.join(line + '\n' for line in lines)
190
        # Assert
191
        self.assertEqual(expected, text)
192
193
    @patch('doorstop.settings.PUBLISH_HEADING_LEVELS', False)
194
    def test_lines_text_item_heading_no_heading_levels(self):
195
        """Verify an item heading level can be ommitted."""
196
        expected = "Heading\n\n"
197
        lines = publisher.publish_lines(self.item, '.txt')
198
        # Act
199
        text = ''.join(line + '\n' for line in lines)
200
        # Assert
201
        self.assertEqual(expected, text)
202
203
    def test_single_line_heading_to_markdown(self):
204
        """A single line heading is published as a heading with an attribute equal to the item id"""
0 ignored issues
show
Coding Style introduced by
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...
205
        expected = "## 1.1 Heading {#req3 }\n\n"
206
        lines = publisher.publish_lines(self.item, '.md', linkify=True)
207
        # Act
208
        text = ''.join(line + '\n' for line in lines)
209
        # Assert
210
        self.assertEqual(expected, text)
211
212
    def test_multi_line_heading_to_markdown(self):
213
        """A multi line heading is published as a heading with an attribute equal to the item id"""
0 ignored issues
show
Coding Style introduced by
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...
214
        item = MockItemAndVCS('path/to/req3.yml',
215
                              _file=("links: [sys3]" + '\n'
216
                                     "text: 'Heading\n\nThis section describes publishing.'" + '\n'
0 ignored issues
show
Coding Style introduced by
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...
217
                                     "level: 1.1.0" + '\n'
218
                                     "normative: false"))
219
        expected = "## 1.1 Heading {#req3 }\nThis section describes publishing.\n\n"
0 ignored issues
show
Coding Style introduced by
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...
220
        lines = publisher.publish_lines(item, '.md', linkify=True)
221
        # Act
222
        text = ''.join(line + '\n' for line in lines)
223
        # Assert
224
        self.assertEqual(expected, text)
225
226
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
227
    def test_lines_text_item_normative(self):
228
        """Verify text can be published from an item (normative)."""
229
        expected = ("1.2     req4" + '\n\n'
230
                    "        This shall..." + '\n\n'
231
                    "        Reference: Doorstop.sublime-project" + '\n\n'
232
                    "        Links: sys4" + '\n\n')
233
        lines = publisher.publish_lines(self.item3, '.txt')
234
        # Act
235
        text = ''.join(line + '\n' for line in lines)
236
        # Assert
237
        self.assertEqual(expected, text)
238
239
    @patch('doorstop.settings.CHECK_REF', False)
240
    def test_lines_text_item_no_ref(self):
241
        """Verify text can be published without checking references."""
242
        lines = publisher.publish_lines(self.item5, '.txt')
243
        text = ''.join(line + '\n' for line in lines)
244
        self.assertIn("Reference: 'abc123'", text)
245
246
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', True)
247
    def test_lines_text_item_with_child_links(self):
248
        """Verify text can be published with child links."""
249
        # Act
250
        lines = publisher.publish_lines(self.item2, '.txt')
251
        text = ''.join(line + '\n' for line in lines)
252
        # Assert
253
        self.assertIn("Child links: tst1", text)
254
255
    def test_lines_markdown_item(self):
256
        """Verify Markdown can be published from an item."""
257
        with patch.object(self.item5, 'find_ref',
258
                          Mock(return_value=('path/to/mock/file', 42))):
259
            lines = publisher.publish_lines(self.item5, '.md')
260
            text = ''.join(line + '\n' for line in lines)
261
        self.assertIn("> `path/to/mock/file` (line 42)", text)
262
263
    def test_lines_markdown_item_heading(self):
264
        """Verify Markdown can be published from an item (heading)."""
265
        expected = "## 1.1 Heading {#req3 }\n\n"
266
        # Act
267
        lines = publisher.publish_lines(self.item, '.md', linkify=True)
268
        text = ''.join(line + '\n' for line in lines)
269
        # Assert
270
        self.assertEqual(expected, text)
271
272
    @patch('doorstop.settings.PUBLISH_HEADING_LEVELS', False)
273
    def test_lines_markdown_item_heading_no_heading_levels(self):
274
        """Verify an item heading level can be ommitted."""
275
        expected = "## Heading {#req3 }\n\n"
276
        # Act
277
        lines = publisher.publish_lines(self.item, '.md', linkify=True)
278
        text = ''.join(line + '\n' for line in lines)
279
        # Assert
280
        self.assertEqual(expected, text)
281
282
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
283
    def test_lines_markdown_item_normative(self):
284
        """Verify Markdown can be published from an item (normative)."""
285
        expected = ("## 1.2 req4" + '\n\n'
286
                    "This shall..." + '\n\n'
287
                    "> `Doorstop.sublime-project`" + '\n\n'
288
                    "*Links: sys4*" + '\n\n')
289
        # Act
290
        lines = publisher.publish_lines(self.item3, '.md', linkify=False)
291
        text = ''.join(line + '\n' for line in lines)
292
        # Assert
293
        self.assertEqual(expected, text)
294
295
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', True)
296
    def test_lines_markdown_item_with_child_links(self):
297
        """Verify Markdown can be published from an item w/ child links."""
298
        # Act
299
        lines = publisher.publish_lines(self.item2, '.md')
300
        text = ''.join(line + '\n' for line in lines)
301
        # Assert
302
        self.assertIn("Child links: tst1", text)
303
304
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
305
    def test_lines_markdown_item_without_child_links(self):
306
        """Verify Markdown can be published from an item w/o child links."""
307
        # Act
308
        lines = publisher.publish_lines(self.item2, '.md')
309
        text = ''.join(line + '\n' for line in lines)
310
        # Assert
311
        self.assertNotIn("Child links", text)
312
313
    @patch('doorstop.settings.PUBLISH_BODY_LEVELS', False)
314
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
315
    def test_lines_markdown_item_without_body_levels(self):
316
        """Verify Markdown can be published from an item (no body levels)."""
317
        expected = ("## req4" + '\n\n'
318
                    "This shall..." + '\n\n'
319
                    "> `Doorstop.sublime-project`" + '\n\n'
320
                    "*Links: sys4*" + '\n\n')
321
        # Act
322
        lines = publisher.publish_lines(self.item3, '.md', linkify=False)
323
        text = ''.join(line + '\n' for line in lines)
324
        # Assert
325
        self.assertEqual(expected, text)
326
327
    @patch('doorstop.settings.CHECK_REF', False)
328
    def test_lines_markdown_item_no_ref(self):
329
        """Verify Markdown can be published without checking references."""
330
        lines = publisher.publish_lines(self.item5, '.md')
331
        text = ''.join(line + '\n' for line in lines)
332
        self.assertIn("> 'abc123'", text)
333
334
    def test_lines_html_item(self):
335
        """Verify HTML can be published from an item."""
336
        expected = '<h2>1.1 Heading</h2>\n'
337
        # Act
338
        lines = publisher.publish_lines(self.item, '.html')
339
        text = ''.join(line + '\n' for line in lines)
340
        # Assert
341
        self.assertEqual(expected, text)
342
343
    @patch('doorstop.settings.PUBLISH_HEADING_LEVELS', False)
344
    def test_lines_html_item_no_heading_levels(self):
345
        """Verify an item heading level can be ommitted."""
346
        expected = '<h2>Heading</h2>\n'
347
        # Act
348
        lines = publisher.publish_lines(self.item, '.html')
349
        text = ''.join(line + '\n' for line in lines)
350
        # Assert
351
        self.assertEqual(expected, text)
352
353
    def test_lines_html_item_linkify(self):
354
        """Verify HTML (hyper) can be published from an item."""
355
        expected = '<h2 id="req3">1.1 Heading</h2>\n'
356
        # Act
357
        lines = publisher.publish_lines(self.item, '.html', linkify=True)
358
        text = ''.join(line + '\n' for line in lines)
359
        # Assert
360
        self.assertEqual(expected, text)
361
362
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', True)
363
    def test_lines_html_item_with_child_links(self):
364
        """Verify HTML can be published from an item w/ child links."""
365
        # Act
366
        lines = publisher.publish_lines(self.item2, '.html')
367
        text = ''.join(line + '\n' for line in lines)
368
        # Assert
369
        self.assertIn("Child links: tst1", text)
370
371
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', False)
372
    def test_lines_html_item_without_child_links(self):
373
        """Verify HTML can be published from an item w/o child links."""
374
        # Act
375
        lines = publisher.publish_lines(self.item2, '.html')
376
        text = ''.join(line + '\n' for line in lines)
377
        # Assert
378
        self.assertNotIn("Child links", text)
379
380
    @patch('doorstop.settings.PUBLISH_CHILD_LINKS', True)
381
    def test_lines_html_item_with_child_links_linkify(self):
382
        """Verify HTML (hyper) can be published from an item w/ child links."""
383
        # Act
384
        lines = publisher.publish_lines(self.item2, '.html', linkify=True)
385
        text = ''.join(line + '\n' for line in lines)
386
        # Assert
387
        self.assertIn("Child links:", text)
388
        self.assertIn("tst.html#tst1", text)
389
390
    def test_lines_unknown(self):
391
        """Verify an exception is raised when iterating an unknown format."""
392
        # Act
393
        gen = publisher.publish_lines(self.document, '.a')
394
        # Assert
395
        self.assertRaises(DoorstopError, list, gen)
396