def test_rename_associated_file(self, setup: Any, preserve_files: bool) -> None: """Test removing associated files. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. preserve_files: argument to `DeleteCommand`. """ try: config.commands.edit.editor = "sed -i 's/einstein:/dummy:/'" with tempfile.TemporaryDirectory() as tmpdirname: path = RelPath(tmpdirname + "/einstein.pdf") open( # pylint: disable=consider-using-with path.path, "w", encoding="utf-8").close() Database()["einstein"].file = str(path) args = ["einstein"] if preserve_files: args.insert(2, "--preserve-files") EditCommand().execute(args) assert "dummy" in Database().keys() target = RelPath(tmpdirname + "/dummy.pdf") if preserve_files: assert path.path.exists() else: assert target.path.exists() finally: config.defaults()
def test_handle_argument_error(self, caplog: pytest.LogCaptureFixture) -> None: """Test handling of ArgumentError. Args: caplog: the built-in pytest fixture. """ # use temporary config config.database.file = self.COBIB_TEST_DIR / "database.yaml" config.database.git = True # load temporary database os.makedirs(self.COBIB_TEST_DIR, exist_ok=True) copyfile(get_resource("example_literature.yaml"), config.database.file) Database().read() try: super().test_handle_argument_error(caplog) finally: # clean up file system os.remove(config.database.file) # clean up database Database().clear() # clean up config config.defaults()
def test_scroll_live(self, keys: str, assertion_kwargs: Dict[str, Union[int, str]]) -> None: """Test scrolling while the TUI is actually running. Args: keys: the string of keys to send to the running TUI. assertion_kwargs: additional keyword arguments to pass to the assertion method. """ def assertion(screen, logs, **kwargs): # type: ignore direction = kwargs["direction"] update = kwargs["update"] term_width = len(screen.buffer[0]) if direction == "y" or update == 0: assert [c.fg for c in screen.buffer[1 + update].values() ] == ["white"] * term_width assert [c.bg for c in screen.buffer[1 + update].values() ] == ["cyan"] * term_width elif direction == "x": # TODO: figure out how (or if) to actually use the update information assert [c.fg for c in screen.buffer[1].values() ] == ["white"] * term_width assert [c.bg for c in screen.buffer[1].values() ] == ["cyan"] * term_width # overwrite database config.database.file = get_resource("scrolling_database.yaml", "tui") try: Database().read() self.run_tui(keys, assertion, assertion_kwargs) finally: Database().clear()
def test_database_read() -> None: """Test the `cobib.database.Database.read` method.""" bib = Database() bib.read() # pylint: disable=protected-access assert Database._unsaved_entries == {} # pylint: disable=C1803 assert list(bib.keys()) == ["einstein", "latexcompanion", "knuthwebsite"]
def test_database_update() -> None: """Test the `cobib.database.Database.update` method.""" entries = {"dummy1": "test1", "dummy2": "test2", "dummy3": "test3"} bib = Database() bib.update(entries) # type: ignore # pylint: disable=protected-access assert Database._unsaved_entries == {e: e for e in list(entries.keys())} for key, val in entries.items(): assert bib[key] == val
def test_database_rename() -> None: """Test the `cobib.database.Database.rename` method.""" bib = Database() # pylint: disable=protected-access Database._unsaved_entries = {} assert Database._unsaved_entries == {} # pylint: disable=C1803 bib.rename("einstein", "dummy") # pylint: disable=protected-access assert Database._unsaved_entries == {"einstein": "dummy"}
def test_command(self, setup: Any, args: List[str]) -> None: """Test the command itself. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. args: the arguments to pass to the command. """ if "-z" in args: # add a dummy file to the `einstein` entry entry = Database()["einstein"] entry.file = get_resource("debug.py") ExportCommand().execute(args) self._assert(args)
def test_event_pre_delete_command(self, setup: Any) -> None: """Tests the PreDeleteCommand event.""" @Event.PreDeleteCommand.subscribe def hook(largs: Namespace) -> None: largs.labels = ["einstein"] assert Event.PreDeleteCommand.validate() DeleteCommand().execute(["knuthwebsite"]) assert "einstein" not in Database().keys() assert "knuthwebsite" in Database().keys()
def test_database_save_add() -> None: """Test the `cobib.database.Database.save` method after entry addition.""" # prepare temporary database config.database.file = TMPDIR / "cobib_test_database_file.yaml" copyfile(EXAMPLE_LITERATURE, config.database.file) # initialize database bib = Database() bib.read() bib.update({"dummy": DUMMY_ENTRY}) bib.save() expected = [] with open(EXAMPLE_LITERATURE, "r", encoding="utf-8") as file: expected.extend(file.readlines()) expected.extend(DUMMY_ENTRY_YAML.split("\n")) try: # pylint: disable=protected-access assert Database._unsaved_entries == {} # pylint: disable=C1803 with open(config.database.file, "r", encoding="utf-8") as file: # NOTE: do NOT use zip_longest to omit last entries (for testing simplicity) for line, truth in zip(file, expected): assert line.strip() == truth.strip() with pytest.raises(StopIteration): file.__next__() finally: os.remove(config.database.file) config.database.file = EXAMPLE_LITERATURE
def test_database_save_modify() -> None: """Test the `cobib.database.Database.save` method after entry modification.""" # prepare temporary database config.database.file = TMPDIR / "cobib_test_database_file.yaml" copyfile(EXAMPLE_LITERATURE, config.database.file) # initialize database bib = Database() bib.read() entry = copy.deepcopy(bib["einstein"]) entry.data["tags"] = "test" bib.update({"einstein": entry}) bib.save() try: # pylint: disable=protected-access assert Database._unsaved_entries == {} # pylint: disable=C1803 with open(config.database.file, "r", encoding="utf-8") as file: with open(EXAMPLE_LITERATURE, "r", encoding="utf-8") as expected: # NOTE: do NOT use zip_longest to omit last entries (for testing simplicity) for line, truth in zip(file, expected): if "tags" in line: assert line == " tags: test\n" # advance only the `file` iterator one step in order to catch up with the # `expected` iterator line = next(file) assert line == truth with pytest.raises(StopIteration): file.__next__() finally: os.remove(config.database.file) config.database.file = EXAMPLE_LITERATURE
def test_database_save_delete() -> None: """Test the `cobib.database.Database.save` method after entry deletion.""" # prepare temporary database config.database.file = TMPDIR / "cobib_test_database_file.yaml" copyfile(EXAMPLE_LITERATURE, config.database.file) # initialize database bib = Database() bib.read() bib.pop("knuthwebsite") bib.save() try: # pylint: disable=protected-access assert Database._unsaved_entries == {} # pylint: disable=C1803 with open(config.database.file, "r", encoding="utf-8") as file: with open(EXAMPLE_LITERATURE, "r", encoding="utf-8") as expected: # NOTE: do NOT use zip_longest to omit last entries (for testing simplicity) for line, truth in zip(file, expected): assert line == truth with pytest.raises(StopIteration): file.__next__() finally: os.remove(config.database.file) config.database.file = EXAMPLE_LITERATURE
def test_database_disambiguate_label(label_suffix: Tuple[str, Callable[[str], str]], expected: str) -> None: # pylint: disable=invalid-name """Test the `cobib.database.Database.disambiguate_label` method.""" config.database.format.default_label_format = "test" config.database.format.label_suffix = label_suffix entries = {"dummy": "test", "test": "no"} bib = Database() bib.update(entries) # type: ignore new_label = Database().disambiguate_label("test", entries["dummy"]) # type: ignore assert new_label == expected
def test_command(self, setup: Any, more_args: List[str], entry_kwargs: Dict[str, Any]) -> None: """Test the command itself. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. more_args: additional arguments to be passed to the command. entry_kwargs: the expected contents of the resulting `Entry`. """ git = setup.get("git", False) try: label = more_args[more_args.index("-l") + 1] except ValueError: label = "example_multi_file_entry" args = ["-b", EXAMPLE_MULTI_FILE_ENTRY_BIB] + more_args AddCommand().execute(args) assert Database()[label] if entry_kwargs or label != "example_multi_file_entry": self._assert_entry(label, **entry_kwargs) else: # only when we don't use extra arguments the files will match self._assert(EXAMPLE_MULTI_FILE_ENTRY_YAML) if git: # assert the git commit message # Note: we do not assert the arguments, because they depend on the available parsers self.assert_git_commit_message("add", None)
def post_setup( self, monkeypatch: pytest.MonkeyPatch, request: _pytest.fixtures.SubRequest ) -> Generator[Dict[str, Any], None, None]: """Additional setup instructions. Args: monkeypatch: the built-in pytest fixture. request: a pytest sub-request providing access to nested parameters. Yields: The internally used parameters for potential later re-use during the actual test. """ if not hasattr(request, "param"): # use default settings request.param = {"stdin_list": None, "multi_file": True} if request.param.get("multi_file", True): with open(get_resource("example_multi_file_entry.yaml", "commands"), "r", encoding="utf-8") as multi_file_entry: with open(config.database.file, "a", encoding="utf-8") as database: database.write(multi_file_entry.read()) Database().read() monkeypatch.setattr("sys.stdin", MockStdin(request.param.get("stdin_list", None))) yield request.param
def test_add_skip_download(self, setup: Any, caplog: pytest.LogCaptureFixture) -> None: """Test adding a new entry and skipping the automatic download. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. caplog: the built-in pytest fixture. """ path = RelPath("/tmp/Cao2018.pdf") try: args = ["-a", "1812.09976", "--skip-download"] AddCommand().execute(args) if ( "cobib.parsers.arxiv", logging.ERROR, "An Exception occurred while trying to query the arXiv ID: 1812.09976.", ) in caplog.record_tuples: pytest.skip("The requests API encountered an error. Skipping test.") entry = Database()["Cao2018"] assert entry.label == "Cao2018" assert entry.data["archivePrefix"] == "arXiv" assert entry.data["arxivid"].startswith("1812.09976") assert "_download" not in entry.data.keys() assert not os.path.exists(path.path) finally: try: os.remove(path.path) except FileNotFoundError: pass
def test_disambiguate_label(self, setup: Any, caplog: pytest.LogCaptureFixture) -> None: """Test label disambiguation if the provided one already exists. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. caplog: the built-in pytest fixture. """ git = setup.get("git", False) AddCommand().execute(["-b", EXAMPLE_DUPLICATE_ENTRY_BIB]) assert ( "cobib.commands.add", 30, "You tried to add a new entry 'einstein' which already exists!", ) in caplog.record_tuples assert ( "cobib.commands.add", 30, "The label will be disambiguated based on the configuration option: " "config.database.format.label_suffix", ) in caplog.record_tuples assert Database()["einstein_a"] if git: # assert the git commit message self.assert_git_commit_message("add", None)
def _assert(self, labels: List[str]) -> None: """Common assertion utility method. Args: labels: the list of labels to be deleted. """ bib = Database() for label in labels: assert bib.get(label, None) is None with open(config.database.file, "r", encoding="utf-8") as file: with open(get_resource("example_literature.yaml"), "r", encoding="utf-8") as expected: # NOTE: do NOT use zip_longest to omit last entries (for testing simplicity) for line, truth in zip(file, expected): assert line == truth with pytest.raises(StopIteration): file.__next__()
def test_cmdline(self, setup: Any, monkeypatch: pytest.MonkeyPatch, args: List[str]) -> None: """Test the command-line access of the command. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. monkeypatch: the built-in pytest fixture. args: additional arguments to pass to the command. """ self.run_module(monkeypatch, "main", ["cobib", "modify"] + args) assert Database()["einstein"].data["tags"] == ["test"]
def _assert_entry(self, label: str, **kwargs) -> None: # type: ignore """An additional assertion utility to check specific entry fields. Args: label: the label of the entry. kwargs: additional keyword arguments whose contents are checked against the Entry's `data contents. """ entry = Database()[label] for key, value in kwargs.items(): assert entry.data.get(key, None) == value
def test_event_post_edit_command(self, setup: Any) -> None: """Tests the PostEditCommand event.""" @Event.PostEditCommand.subscribe def hook(new_entry: Entry) -> None: new_entry.data["tags"] = "test" assert Event.PostEditCommand.validate() EditCommand().execute(["-a", "dummy"]) assert Database()["dummy"].data["tags"] == "test"
def test_event_pre_add_command(self, setup: Any) -> None: """Tests the PreAddCommand event.""" @Event.PreAddCommand.subscribe def hook(largs: Namespace) -> None: largs.label = "dummy" assert Event.PreAddCommand.validate() AddCommand().execute(["-b", EXAMPLE_DUPLICATE_ENTRY_BIB]) assert "dummy" in Database().keys()
def test_f_string_interpretation(self, setup: Any, modification: str, expected: Any) -> None: """Test f-string interpretation. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. modification: the modification string to apply. expected: the expected final `Entry` field. """ # modify some data args = [modification, "++label", "einstein"] field, *_ = modification.split(":") ModifyCommand().execute(args) if field != "label": assert Database()["einstein"].data[field] == expected else: assert "eistein" not in Database().keys() assert expected in Database().keys() assert Database()[expected].label == expected
def test_event_post_add_command(self, setup: Any) -> None: """Tests the PostAddCommand event.""" @Event.PostAddCommand.subscribe def hook(new_entries: Dict[str, Entry]) -> None: new_entries["dummy"] = new_entries.pop("einstein_a") assert Event.PostAddCommand.validate() AddCommand().execute(["-b", EXAMPLE_DUPLICATE_ENTRY_BIB]) assert "dummy" in Database().keys()
def test_event_pre_modify_command(self, setup: Any) -> None: """Tests the PreModifyCommand event.""" @Event.PreModifyCommand.subscribe def hook(largs: Namespace) -> None: largs.modification = ("number", "2") assert Event.PreModifyCommand.validate() ModifyCommand().execute(["-a", "number:3", "++label", "einstein"]) assert Database()["einstein"].data["number"] == 12
def setup() -> Generator[Any, None, None]: """Setup debugging configuration. This method also clears the `Database` after each test run. It is automatically enabled for all tests in this file. Yields: Access to the local fixture variables. """ config.load(get_resource("debug.py")) yield Database().clear() config.defaults()
def _assert(self) -> None: """Common assertion utility method.""" assert Database().get("example_multi_file_entry", None) is not None # get last commit message with subprocess.Popen( ["git", "-C", self.COBIB_TEST_DIR, "show", "--format=format:%B", "--no-patch", "HEAD"], stdout=subprocess.PIPE, ) as proc: message, _ = proc.communicate() # decode it split_message = message.decode("utf-8").split("\n") # assert subject line assert "Redo" in split_message[0]
def list_labels(args: List[str]) -> List[str]: """List all available labels in the database. Args: args: a sequence of additional arguments used for the execution. None are supported yet. Returns: The list of all labels. """ # pylint: disable=import-outside-toplevel from cobib.database import Database labels = list(Database().keys()) return labels
def test_rename_associated_file(self, setup: Any, preserve_files: bool) -> None: """Test removing associated files. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. preserve_files: argument to `DeleteCommand`. """ with tempfile.TemporaryDirectory() as tmpdirname: path = RelPath(tmpdirname + "/knuthwebsite.pdf") open(path.path, "w", encoding="utf-8").close() # pylint: disable=consider-using-with Database()["knuthwebsite"].file = str(path) args = ["label:dummy", "-s", "--", "knuthwebsite"] if preserve_files: args.insert(2, "--preserve-files") ModifyCommand().execute(args) assert "dummy" in Database().keys() target = RelPath(tmpdirname + "/dummy.pdf") if preserve_files: assert path.path.exists() else: assert target.path.exists()
def test_add_mode(self, setup: Any, modification: str, expected: Any) -> None: """Test more cases of the add mode. Args: setup: the `tests.commands.command_test.CommandTest.setup` fixture. modification: the modification string to apply. expected: the expected final `Entry` field. """ # modify some data args = ["-a", modification, "++label", "einstein"] field, _ = modification.split(":") ModifyCommand().execute(args) assert Database()["einstein"].data[field] == expected
def list_filters(args: List[str]) -> Set[str]: """Lists all field names available for filtering. Args: args: a sequence of additional arguments used for the execution. None are supported yet. Returns: The list of all available filters. """ # pylint: disable=import-outside-toplevel from cobib.database import Database filters: Set[str] = {"label"} for entry in Database().values(): filters.update(entry.data.keys()) return filters