# type: ignore """Tests for the application module. How mocking works in this test suite: 1. The application_main() method is mocked using a side effect iterable. This allows us to pass a value in the first run, and then a KeyError in the second run to exit the loop. 2. All questions are mocked using return_value. This allows us to pass in a value to the question and then the method will return that value. This is useful for testing questionary prompts without user input. """ import re from pathlib import Path import pytest from obsidian_metadata.models.enums import MetadataType from tests.helpers import Regex, strip_ansi def test_instantiate_application(test_application) -> None: """Test application. GIVEN an application WHEN the application is instantiated THEN check the attributes are set correctly """ app = test_application app._load_vault() assert app.dry_run is False assert app.config.name == "command_line_vault" assert app.config.exclude_paths == [".git", ".obsidian"] assert app.dry_run is False assert len(app.vault.all_notes) == 13 def test_abort(test_application, mocker, capsys) -> None: """Test aborting the application. GIVEN an application WHEN the users selects "abort" from the main menu THEN check the application exits """ app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", return_value="abort", ) app.application_main() captured = strip_ansi(capsys.readouterr().out) assert "Done!" in captured def test_add_metadata_frontmatter(test_application, mocker, capsys) -> None: """Test adding new metadata to the vault. GIVEN an application WHEN the wants to update a key in the frontmatter THEN check the application updates the key """ app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["add_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_meta_type", return_value=MetadataType.FRONTMATTER, ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_key", return_value="new_key", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_value", return_value="new_key_value", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"SUCCESS +\| Added metadata to \d+ notes", re.DOTALL) def test_add_metadata_inline(test_application, mocker, capsys) -> None: """Test adding new metadata to the vault. GIVEN an application WHEN the user wants to add a key in the inline metadata THEN check the application updates the key """ app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["add_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_meta_type", return_value=MetadataType.INLINE, ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_key", return_value="new_key", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_value", return_value="new_key_value", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"SUCCESS +\| Added metadata to \d+ notes", re.DOTALL) def test_add_metadata_tag(test_application, mocker, capsys) -> None: """Test adding new metadata to the vault. GIVEN an application WHEN the user wants to add a tag THEN check the application adds the tag """ app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["add_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_meta_type", return_value=MetadataType.TAGS, ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_tag", return_value="new_tag", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"SUCCESS +\| Added metadata to \d+ notes", re.DOTALL) def test_delete_tag_1(test_application, mocker, capsys) -> None: """Test renaming an inline tag. GIVEN an application WHEN the user wants to delete an inline tag THEN check the application deletes the tag """ app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["delete_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["delete_tag", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_tag", return_value="breakfast", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"SUCCESS +\| Deleted inline tag: breakfast in \d+ notes", re.DOTALL) def test_delete_tag_2(test_application, mocker, capsys) -> None: """Test renaming an inline tag. GIVEN an application WHEN the user wants to delete an inline tag that does not exist THEN check the application does not update any notes """ app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["delete_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["delete_tag", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_tag", return_value="not_a_tag_in_vault", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert "WARNING | No notes were changed" in captured def test_delete_key(test_application, mocker, capsys) -> None: """Test renaming an inline tag.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["delete_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["delete_key", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_keys_regex", return_value=r"\d{7}", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert r"WARNING | No notes found with a key matching regex: \d{7}" in captured mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["delete_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["delete_key", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_keys_regex", return_value=r"d\w+", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"SUCCESS \| Deleted keys matching: d\\w\+ from \d+ notes", re.DOTALL) def test_delete_value(test_application, mocker, capsys) -> None: """Test renaming an inline tag.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["delete_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["delete_value", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="area", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_value_regex", return_value=r"\d{7}", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert r"WARNING | No notes found matching: area: \d{7}" in captured mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["delete_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["delete_value", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="area", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_value_regex", return_value=r"^front\w+$", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"SUCCESS | Deleted value \^front\\w\+\$ from key area in \d+ notes") def test_filter_notes(test_application, mocker, capsys) -> None: """Test renaming a key.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["filter_notes", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["apply_path_filter", "list_notes", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_filter_path", return_value="inline", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"SUCCESS +\| Loaded \d+ notes from \d+ total", re.DOTALL) assert "02 inline/inline 2.md" in captured assert "03 mixed/mixed 1.md" not in captured mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["filter_notes", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["apply_metadata_filter", "list_notes", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="on_one_note", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_value", return_value="", ) with pytest.raises(KeyError): app.application_main() captured = capsys.readouterr() assert captured.out == Regex(r"SUCCESS +\| Loaded.*1.*notes from.*\d+.*total", re.DOTALL) assert "02 inline/inline 2.md" in captured.out assert "03 mixed/mixed 1.md" not in captured.out def test_filter_clear(test_application, mocker, capsys) -> None: """Test clearing filters.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["filter_notes", "filter_notes", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["apply_metadata_filter", "list_filters", "list_notes", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="on_one_note", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_value", return_value="", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_number", return_value="1", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert "02 inline/inline 2.md" in captured assert "03 mixed/mixed 1.md" in captured assert "01 frontmatter/frontmatter 4.md" in captured assert "04 no metadata/no_metadata_1.md " in captured def test_inspect_metadata_all(test_application, mocker, capsys) -> None: """Test backing up a vault.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["inspect_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["all_metadata", "back"], ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"tags +│ bar ") assert captured == Regex(r"status +│ new ") assert captured == Regex(r"in_text_key +│ in-text value") assert "#breakfast" in captured def test_rename_tag(test_application, mocker, capsys) -> None: """Test renaming an inline tag.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["rename_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["rename_tag", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_tag", return_value="not_a_tag", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_tag", return_value="new_tag", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert "No notes were changed" in captured mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["rename_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["rename_tag", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_tag", return_value="breakfast", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_tag", return_value="new_tag", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"Renamed breakfast to new_tag in \d+ notes", re.DOTALL) def test_rename_key(test_application, mocker, capsys) -> None: """Test renaming a key.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["rename_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["rename_key", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="tag", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_key", return_value="new_tags", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert "WARNING | No notes were changed" in captured mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["rename_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["rename_key", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="tags", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_key", return_value="new_tags", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"Renamed tags to new_tags in \d+ notes", re.DOTALL) def test_rename_value_fail(test_application, mocker, capsys) -> None: """Test renaming an inline tag.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["rename_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["rename_value", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="area", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_value", return_value="not_exists", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_value", return_value="new_key", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert "WARNING | No notes were changed" in captured mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["rename_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["rename_value", "back"], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="area", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_value", return_value="frontmatter", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_value", return_value="new_key", ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex( r"SUCCESS +\| Renamed 'area:frontmatter' to 'area:new_key' in \d+ notes", re.DOTALL ) def test_review_no_changes(test_application, mocker, capsys) -> None: """Review changes when no changes to vault.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["review_changes", KeyError], ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert "INFO | No changes to review" in captured def test_review_changes(test_application, mocker, capsys) -> None: """Review changes when no changes to vault.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["rename_metadata", "review_changes", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_existing_key", return_value="tags", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_new_key", return_value="new_tags", ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["rename_key", 1, "return"], ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r".*Found \d+ changed notes in the vault", re.DOTALL) assert "- tags:" in captured assert "+ new_tags:" in captured def test_transpose_metadata_1(test_application, mocker, capsys) -> None: """Transpose metadata. GIVEN a test application WHEN the user wants to transpose all inline metadata to frontmatter THEN the metadata is transposed """ app = test_application app._load_vault() assert app.vault.inline_meta["inline_key"] == ["inline_key_value"] mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["reorganize_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["inline_to_frontmatter", "transpose_all"], ) with pytest.raises(KeyError): app.application_main() assert app.vault.inline_meta == {} assert app.vault.frontmatter["inline_key"] == ["inline_key_value"] captured = strip_ansi(capsys.readouterr().out) assert "SUCCESS | Transposed Inline Metadata to Frontmatter in 5 notes" in captured def test_transpose_metadata_2(test_application, mocker) -> None: """Transpose metadata. GIVEN a test application WHEN the user wants to transpose all frontmatter to inline metadata THEN the metadata is transposed """ app = test_application app._load_vault() assert app.vault.frontmatter["date_created"] == ["2022-12-21", "2022-12-22"] mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["reorganize_metadata", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["frontmatter_to_inline", "transpose_all"], ) with pytest.raises(KeyError): app.application_main() assert app.vault.inline_meta["date_created"] == ["2022-12-21", "2022-12-22"] assert app.vault.frontmatter == {} def test_vault_backup(test_application, mocker, capsys) -> None: """Test backing up a vault.""" app = test_application app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["vault_actions", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["backup_vault", "back"], ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex( r"SUCCESS +\| Vault backed up to:[-\w\d\/\s]+application\.bak", re.DOTALL ) def test_vault_delete(test_application, mocker, capsys, tmp_path) -> None: """Test backing up a vault.""" app = test_application backup_path = Path(tmp_path / "application.bak") backup_path.mkdir() app._load_vault() mocker.patch( "obsidian_metadata.models.application.Questions.ask_application_main", side_effect=["vault_actions", KeyError], ) mocker.patch( "obsidian_metadata.models.application.Questions.ask_selection", side_effect=["delete_backup", "back"], ) with pytest.raises(KeyError): app.application_main() captured = strip_ansi(capsys.readouterr().out) assert captured == Regex(r"SUCCESS +\| Backup deleted", re.DOTALL)