working on adding tests for write_budget_markdown
authorJacob Lifshay <programmerjake@gmail.com>
Tue, 15 Sep 2020 02:03:07 +0000 (19:03 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Tue, 15 Sep 2020 02:03:07 +0000 (19:03 -0700)
src/budget_sync/test/mock_path.py [new file with mode: 0644]
src/budget_sync/test/test_mock_path.py [new file with mode: 0644]
src/budget_sync/test/test_write_budget_markdown.py [new file with mode: 0644]
src/budget_sync/write_budget_markdown.py

diff --git a/src/budget_sync/test/mock_path.py b/src/budget_sync/test/mock_path.py
new file mode 100644 (file)
index 0000000..5e72e38
--- /dev/null
@@ -0,0 +1,135 @@
+from typing import Optional, Dict, Iterator, Tuple, Set, Union
+from pathlib import PurePosixPath
+from os import PathLike
+from posixpath import normpath
+from enum import Enum
+import errno
+from functools import cached_property
+
+
+class MockDir(Enum):
+    DIR = ""
+
+
+DIR = MockDir.DIR
+
+
+class MockFilesystem:
+    files: Dict[str, Union[bytes, MockDir]]
+    current_dir: PurePosixPath
+
+    def __init__(self,
+                 files: Optional[Dict[str, Union[bytes, MockDir]]] = None,
+                 current_dir: Optional[PathLike] = None):
+        if files is None:
+            files = {"/": DIR}
+        self.files = files
+        if current_dir is None:
+            current_dir = PurePosixPath("/")
+        self.current_dir = current_dir
+
+    def normalize_path(self, path: PathLike) -> PurePosixPath:
+        abs_path = self.current_dir.joinpath(path)
+        return PurePosixPath(normpath(abs_path))
+
+    def check_parent(self, normalized_path: PurePosixPath):
+        parent = self.files.get(str(normalized_path.parent))
+        if parent is None:
+            raise FileNotFoundError(normalized_path)
+        if parent is not DIR:
+            raise NotADirectoryError(normalized_path)
+
+    def create(self, path: PathLike, contents: Union[bytes, MockDir]):
+        normalized_path = self.normalize_path(path)
+        self.check_parent(normalized_path)
+        if str(normalized_path) in self.files:
+            raise FileExistsError(normalized_path)
+        self.files[str(normalized_path)] = contents
+
+    def create_or_write_file(self, path: PathLike, contents: bytes):
+        normalized_path = self.normalize_path(path)
+        self.check_parent(normalized_path)
+        if self.files.get(str(normalized_path)) is DIR:
+            raise IsADirectoryError(normalized_path)
+        self.files[str(normalized_path)] = contents
+
+    def write_existing_file(self, path: PathLike, contents: bytes):
+        normalized_path = self.normalize_path(path)
+        self.check_parent(normalized_path)
+        old_file = self.files.get(str(normalized_path))
+        if old_file is None:
+            raise FileNotFoundError(normalized_path)
+        if old_file is DIR:
+            raise IsADirectoryError(normalized_path)
+        self.files[str(normalized_path)] = contents
+
+    def is_dir(self, path: PathLike) -> bool:
+        normalized_path = self.normalize_path(path)
+        return self.files.get(str(normalized_path)) is DIR
+
+    def change_dir(self, path: PathLike):
+        normalized_path = self.normalize_path(path)
+        f = self.files.get(str(normalized_path))
+        if f is None:
+            raise FileNotFoundError(normalized_path)
+        if f is not DIR:
+            raise NotADirectoryError(normalized_path)
+        self.current_dir = normalized_path
+
+    def __repr__(self) -> str:
+        return f"MockFilesystem(files={self.files!r}, " \
+            f"current_dir={self.current_dir!r})"
+
+
+class MockPath:
+    filesystem: MockFilesystem
+    path: PurePosixPath
+
+    def __init__(self, path: PathLike, filesystem: MockFilesystem):
+        self.path = PurePosixPath(path)
+        self.filesystem = filesystem
+
+    @cached_property
+    def parent(self) -> "MockPath":
+        return MockPath(self.path.parent, self.filesystem)
+
+    def __eq__(self, other) -> bool:
+        return self.path == other.path
+
+    def is_dir(self) -> bool:
+        self.filesystem.is_dir(self.path)
+
+    def mkdir(self, parents: bool = False, exist_ok: bool = False):
+        # derived from Python's Path.mkdir
+        try:
+            self.filesystem.create(self.path, DIR)
+        except FileNotFoundError:
+            if not parents or self.parent == self:
+                raise
+            self.parent.mkdir(parents=True, exist_ok=True)
+            self.mkdir(parents=False, exist_ok=exist_ok)
+        except OSError:
+            if not exist_ok or not self.is_dir():
+                raise
+
+    def joinpath(self, *args) -> "MockPath":
+        return MockPath(self.path.joinpath(*args), self.filesystem)
+
+    def write_bytes(self, data):
+        self.filesystem.create_or_write_file(self.path,
+                                             bytes(memoryview(data)))
+
+    def write_text(self, data, encoding=None, errors=None):
+        if not isinstance(data, str):
+            raise TypeError()
+        if encoding is None:
+            encoding = "utf-8"
+        if errors is None:
+            errors = "strict"
+        self.write_bytes(data.encode(encoding=encoding, errors=errors))
+
+    def __str__(self) -> str:
+        return str(self.path)
+
+    def __repr__(self) -> str:
+        return repr(self.path)
diff --git a/src/budget_sync/test/test_mock_path.py b/src/budget_sync/test/test_mock_path.py
new file mode 100644 (file)
index 0000000..6ddcd4c
--- /dev/null
@@ -0,0 +1,33 @@
+from contextlib import contextmanager
+import unittest
+from budget_sync.test.mock_path import MockPath, MockFilesystem, DIR
+
+
+@contextmanager
+def make_filesystem_and_report_if_error(test_case: unittest.TestCase):
+    filesystem = MockFilesystem()
+    try:
+        yield filesystem
+    except Exception as e:
+        if isinstance(e, AssertionError):
+            raise
+        with test_case.subTest(filesystem=filesystem):
+            raise
+
+
+class TestMockPath(unittest.TestCase):
+    # TODO: add more test cases
+
+    def test_mkdir(self):
+        with make_filesystem_and_report_if_error(self) as filesystem:
+            MockPath("/dir/", filesystem).mkdir()
+            self.assertEqual(filesystem.files,
+                             {
+                                 "/": DIR,
+                                 "/dir": DIR,
+                             })
+        # TODO: add more test cases
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/src/budget_sync/test/test_write_budget_markdown.py b/src/budget_sync/test/test_write_budget_markdown.py
new file mode 100644 (file)
index 0000000..8ff5956
--- /dev/null
@@ -0,0 +1,45 @@
+import unittest
+from budget_sync.config import Config
+from budget_sync.test.mock_bug import MockBug
+from budget_sync.test.mock_path import MockPath, DIR
+from budget_sync.test.test_mock_path import make_filesystem_and_report_if_error
+from budget_sync.budget_graph import BudgetGraph
+from budget_sync.write_budget_markdown import write_budget_markdown
+
+
+class TestWriteBudgetMarkdown(unittest.TestCase):
+    def test(self):
+        config = Config.from_str(
+            """
+            bugzilla_url = "https://bugzilla.example.com/"
+            [milestones]
+            [people."person1"]
+            output_markdown_file = "person1.mdwn"
+            [people."person2"]
+            output_markdown_file = "person2.mdwn"
+            """)
+        budget_graph = BudgetGraph([
+            MockBug(bug_id=1,
+                    cf_budget_parent=None,
+                    cf_budget="0",
+                    cf_total_budget="0",
+                    cf_nlnet_milestone=None,
+                    cf_payees_list="",
+                    summary=""),
+        ], config)
+        self.assertEqual([], budget_graph.get_errors())
+        with make_filesystem_and_report_if_error(self) as filesystem:
+            output_dir = MockPath("/output_dir/", filesystem=filesystem)
+            write_budget_markdown(budget_graph, output_dir)
+            self.assertEqual({
+                "/": DIR,
+                "/output_dir": DIR,
+                '/output_dir/person1.mdwn': b'<!-- autogenerated by '
+                b'budget-sync -->\n# person1\n\n# Status Tracking\n',
+                '/output_dir/person2.mdwn': b'<!-- autogenerated by '
+                b'budget-sync -->\n# person2\n\n# Status Tracking\n',
+            }, filesystem.files)
+
+
+if __name__ == "__main__":
+    unittest.main()
index efe359838512fa10585abf43c72e4827699d2333..6780a10325ee7b42fc64076f47650a963fea5ae9 100644 (file)
@@ -33,8 +33,8 @@ def _markdown_for_person(person: Person,
         for payment in payments_list:
             # TODO: finish
             summary = _markdown_escape(payment.node.bug.summary)
-            print(f"* [Bug #{payment.node.bug.id}: "
-                  f"{summary}]({payment.node.bug_url})",
+            print(f"* [Bug #{payment.node.bug.id}]({payment.node.bug_url}): "
+                  f"{summary}",
                   file=buffer)
     return buffer.getvalue()