diff --git a/doorstop/cli/tests/files/settings_modified.py b/doorstop/cli/tests/files/settings_modified.py new file mode 100644 index 000000000..afa0742b1 --- /dev/null +++ b/doorstop/cli/tests/files/settings_modified.py @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: LGPL-3.0-only + +"""Settings for the Doorstop package.""" + +import logging +import os + +# Logging settings +DEFAULT_LOGGING_FORMAT = "%(message)s" +LEVELED_LOGGING_FORMAT = "%(levelname)s: %(message)s" +VERBOSE_LOGGING_FORMAT = "[%(levelname)-8s] %(message)s" +VERBOSE2_LOGGING_FORMAT = "[%(levelname)-8s] (%(name)s @%(lineno)4d) %(message)s" +QUIET_LOGGING_LEVEL = logging.WARNING +TIMED_LOGGING_FORMAT = "%(asctime)s" + " " + VERBOSE_LOGGING_FORMAT +DEFAULT_LOGGING_LEVEL = logging.WARNING +VERBOSE_LOGGING_LEVEL = logging.INFO +VERBOSE2_LOGGING_LEVEL = logging.DEBUG +VERBOSE3_LOGGING_LEVEL = logging.DEBUG - 1 + +# Value constants +SEP_CHARS = "-_." # valid prefix/number separators +SKIP_EXTS = [".yml", ".csv", ".tsv"] # extensions skipped in reference search +RESERVED_WORDS = ["all"] # keywords that cannot be used for prefixes +PLACEHOLDER = "..." # placeholder for new item UIDs on export/import +PLACEHOLDER_COUNT = 1 # number of placeholders to include on export + +# Formatting settings +MAX_LINE_LENGTH = 20 # line length to trigger multiline on extended attributes + +# Validation settings +REFORMAT = True # reformat item files during validation +REORDER = False # reorder document levels during validation +CHECK_LEVELS = True # validate document levels during validation +CHECK_REF = True # validate external file references +CHECK_CHILD_LINKS = True # validate reverse links +# require child (reverse) links from every document +CHECK_CHILD_LINKS_STRICT = False +CHECK_SUSPECT_LINKS = True # check stamps on links +CHECK_REVIEW_STATUS = True # check stamps on items +WARN_ALL = False # display info-level issues as warnings +ERROR_ALL = False # display warning-level issues as errors + +# Review settings +REVIEW_NEW_ITEMS = True # automatically review new items during validation + +# Stamping settings +STAMP_NEW_LINKS = True # automatically stamp links upon creation + +# Publishing settings +PUBLISH_CHILD_LINKS = True # include child links when publishing +PUBLISH_BODY_LEVELS = True # include levels on non-header items +PUBLISH_HEADING_LEVELS = True # include levels on header items +ENABLE_HEADERS = True # use headers if defined +WRITE_LINESEPERATOR = os.linesep + +# Version control settings +ADDREMOVE_FILES = True # automatically add/remove new/changed files + +# Caching settings +CACHE_ITEMS = True # cache items in documents and trees +CACHE_DOCUMENTS = True # cache documents in trees +CACHE_PATHS = True # cache file/directory paths and contents + +# Server settings +SERVER_HOST = None # '' = server not specified, None = no server in use +SERVER_PORT = 7867 diff --git a/doorstop/cli/tests/test_main.py b/doorstop/cli/tests/test_main.py index ab304cb5b..4c061ceb7 100644 --- a/doorstop/cli/tests/test_main.py +++ b/doorstop/cli/tests/test_main.py @@ -9,7 +9,7 @@ from doorstop import settings from doorstop.cli import main -from doorstop.cli.tests import SettingsTestCase +from doorstop.cli.tests import FILES, SettingsTestCase class TestMain(SettingsTestCase): @@ -84,3 +84,12 @@ def test_main(self): spec.loader.exec_module(runpy) # Assert self.assertIsNotNone(runpy) + + @patch("doorstop.cli.commands.run", Mock()) + def test_run_modified_settings_through_file(self): + """Verify --settings has override settings.""" + main.main(args=["--settings", f"{FILES}/settings_modified.py"]) + self.assertEqual(settings.MAX_LINE_LENGTH, 20) + # rollback to the original value to not impact an item test + settings.MAX_LINE_LENGTH = 79 + self.assertEqual(settings.MAX_LINE_LENGTH, 79) diff --git a/doorstop/core/tests/__init__.py b/doorstop/core/tests/__init__.py index 32f8f36fa..aca01c4b3 100644 --- a/doorstop/core/tests/__init__.py +++ b/doorstop/core/tests/__init__.py @@ -104,6 +104,15 @@ def set_items(self, items): self._items = items +class MockSimpleDocumentExtensions(MockSimpleDocument): + """Mock Document class that enable extensions.""" + + def __init__(self, **kwargs): + super().__init__() + for k, v in kwargs.items(): + self.extensions[k] = v + + class MockDocumentSkip(MockDocument): # pylint: disable=W0223,R0902 """Mock Document class that is always skipped in tree placement.""" diff --git a/doorstop/core/tests/files/published.html b/doorstop/core/tests/files/published.html index 3fdbe718f..b3f27c012 100644 --- a/doorstop/core/tests/files/published.html +++ b/doorstop/core/tests/files/published.html @@ -105,7 +105,7 @@
Hello, world!
Hello, world!
- +Hello, world!
diff --git a/doorstop/core/tests/files/published2.html b/doorstop/core/tests/files/published2.html index ecc2a319f..830a3e77e 100644 --- a/doorstop/core/tests/files/published2.html +++ b/doorstop/core/tests/files/published2.html @@ -105,7 +105,7 @@Hello, world!
Hello, world!
- +Hello, world!
Test Math Expressions in Latex Style:
diff --git a/doorstop/core/tests/test_item_extensions.py b/doorstop/core/tests/test_item_extensions.py new file mode 100644 index 000000000..49f280817 --- /dev/null +++ b/doorstop/core/tests/test_item_extensions.py @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: LGPL-3.0-only +# pylint: disable=C0302 + +"""Unit tests for the doorstop.core.item module.""" + +import os +import unittest +from types import ModuleType +from unittest.mock import patch + +from doorstop.common import import_path_as_module +from doorstop.core.tests import TESTS_ROOT, MockItem, MockSimpleDocumentExtensions + + +class TestItem(unittest.TestCase): + """Unit tests for the Item class.""" + + # pylint: disable=protected-access,no-value-for-parameter + + def setUp(self): + path = os.path.join("path", "to", "RQ001.yml") + self.item = MockItem(MockSimpleDocumentExtensions(item_sha_required=True), path) + + @patch("doorstop.settings.CACHE_PATHS", False) + def test_find_references_and_get_sha(self): + """Verify an item's references can be found.""" + self.item.root = TESTS_ROOT + + self.item.references = [ + {"path": "files/REQ001.yml", "type": "file"}, + {"path": "files/REQ002.yml", "type": "file"}, + ] + # Generate sha through review + self.item.review() + refs = self.item.references + + self.assertIn("sha", refs[0]) + self.assertIn("sha", refs[1]) + + @patch("doorstop.settings.CACHE_PATHS", False) + def test_load_custom_validator_per_folder(self): + """Load a valid custom validator per folder.""" + path = os.path.join("path", "to", "RQ001.yml") + self.item = MockItem( + MockSimpleDocumentExtensions( + item_validator=f"{TESTS_ROOT}/validators/validator_dummy.py" + ), + path, + ) + document = self.item.document + validator = import_path_as_module(document.extensions["item_validator"]) + + self.assertEqual(isinstance(validator, ModuleType), True) + + @patch("doorstop.settings.CACHE_PATHS", False) + def test_load_custom_validator_per_folder_and_fails(self): + """Load a invalid custom validator per folder and fails with FileNotFoundError.""" + path = os.path.join("path", "to", "RQ001.yml") + self.item = MockItem( + MockSimpleDocumentExtensions( + item_validator=f"{TESTS_ROOT}/files/validator_dummy2.py" + ), + path, + ) + document = self.item.document + try: + validator = import_path_as_module(document.extensions["item_validator"]) + except FileNotFoundError: + validator = FileNotFoundError + + self.assertEqual(FileNotFoundError, validator) + + @patch("doorstop.settings.CACHE_PATHS", False) + def test_no_sha_ref(self): + """Verify sha is not obtained if extension is not enabled.""" + path = os.path.join("path", "to", "RQ001.yml") + self.item = MockItem( + MockSimpleDocumentExtensions(), + path, + ) + + self.item.root = TESTS_ROOT + + self.item.references = [ + {"path": "files/REQ001.yml", "type": "file"}, + {"path": "files/REQ002.yml", "type": "file"}, + ] + # without item_sha_required, sha must return None + self.item.review() + refs = self.item.references + sha = self.item._hash_reference(refs[0]["path"]) + self.assertNotIn("sha", refs[0].keys()) + self.assertIsNone(sha) diff --git a/doorstop/core/tests/validators/validator_dummy.py b/doorstop/core/tests/validators/validator_dummy.py new file mode 100644 index 000000000..f75ea2811 --- /dev/null +++ b/doorstop/core/tests/validators/validator_dummy.py @@ -0,0 +1,6 @@ +from doorstop import DoorstopError, DoorstopInfo, DoorstopWarning + + +def item_validator(item): + if item: + yield DoorstopInfo("Loaded")