From 1f669746dbd992b62a2c0b941b4e0650ce1a8b9c Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:24:53 -0400 Subject: [PATCH] Handle empty profile cleanup --- src/main.py | 36 ++++++++++------ src/tests/test_auto_sync.py | 1 + src/tests/test_profile_deletion_sync.py | 55 +++++++++++++++++++++++++ src/utils/fingerprint_manager.py | 13 ++++-- 4 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 src/tests/test_profile_deletion_sync.py diff --git a/src/main.py b/src/main.py index f0ad992..f528dfb 100644 --- a/src/main.py +++ b/src/main.py @@ -187,11 +187,7 @@ def handle_add_new_fingerprint(password_manager: PasswordManager): def handle_remove_fingerprint(password_manager: PasswordManager): - """ - Handles removing an existing seed profile. - - :param password_manager: An instance of PasswordManager. - """ + """Handle removing an existing seed profile.""" try: fingerprints = password_manager.fingerprint_manager.list_fingerprints() if not fingerprints: @@ -210,12 +206,24 @@ def handle_remove_fingerprint(password_manager: PasswordManager): selected_fingerprint = fingerprints[int(choice) - 1] confirm = confirm_action( - f"Are you sure you want to remove seed profile {selected_fingerprint}? This will delete all associated data. (Y/N): " + f"Are you sure you want to remove seed profile {selected_fingerprint}? This will delete all associated data. (Y/N):" ) if confirm: + + def _cleanup_and_exit() -> None: + password_manager.current_fingerprint = None + password_manager.is_dirty = False + getattr(password_manager, "cleanup", lambda: None)() + print(colored("All seed profiles removed. Exiting.", "yellow")) + sys.exit(0) + if password_manager.fingerprint_manager.remove_fingerprint( - selected_fingerprint + selected_fingerprint, _cleanup_and_exit ): + password_manager.current_fingerprint = ( + password_manager.fingerprint_manager.current_fingerprint + ) + password_manager.is_dirty = False print( colored( f"Seed profile {selected_fingerprint} removed successfully.", @@ -1028,11 +1036,15 @@ def display_menu( getattr(password_manager, "start_background_relay_check", lambda: None)() continue # Periodically push updates to Nostr - if ( - password_manager.is_dirty - and time.time() - password_manager.last_update >= sync_interval - ): - handle_post_to_nostr(password_manager) + current_fp = getattr(password_manager, "current_fingerprint", None) + if current_fp: + if ( + password_manager.is_dirty + and time.time() - password_manager.last_update >= sync_interval + ): + handle_post_to_nostr(password_manager) + password_manager.is_dirty = False + else: password_manager.is_dirty = False # Flush logging handlers diff --git a/src/tests/test_auto_sync.py b/src/tests/test_auto_sync.py index 26f0e22..670fc5f 100644 --- a/src/tests/test_auto_sync.py +++ b/src/tests/test_auto_sync.py @@ -15,6 +15,7 @@ def test_auto_sync_triggers_post(monkeypatch): is_dirty=True, last_update=time.time() - 0.2, last_activity=time.time(), + current_fingerprint="fp", nostr_client=SimpleNamespace(close_client_pool=lambda: None), handle_add_password=lambda: None, handle_retrieve_entry=lambda: None, diff --git a/src/tests/test_profile_deletion_sync.py b/src/tests/test_profile_deletion_sync.py new file mode 100644 index 0000000..4e136a1 --- /dev/null +++ b/src/tests/test_profile_deletion_sync.py @@ -0,0 +1,55 @@ +import time +from types import SimpleNamespace + +import pytest +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).resolve().parents[1])) +import main +from utils.fingerprint_manager import FingerprintManager +from tests.helpers import TEST_SEED + + +def test_profile_deletion_stops_sync(monkeypatch, tmp_path): + fm = FingerprintManager(tmp_path) + fp = fm.add_fingerprint(TEST_SEED) + + calls = {"post": 0, "cleanup": 0} + + def fake_post(_pm): + calls["post"] += 1 + + monkeypatch.setattr(main, "handle_post_to_nostr", fake_post) + monkeypatch.setattr("builtins.input", lambda *_: "1") + monkeypatch.setattr(main, "confirm_action", lambda *_: True) + + pm = SimpleNamespace( + fingerprint_manager=fm, + current_fingerprint=fp, + is_dirty=False, + last_update=time.time(), + last_activity=time.time(), + nostr_client=SimpleNamespace(close_client_pool=lambda: None), + handle_add_password=lambda: None, + handle_retrieve_entry=lambda: None, + handle_modify_entry=lambda: None, + update_activity=lambda: None, + lock_vault=lambda: None, + unlock_vault=lambda: None, + start_background_sync=lambda: None, + start_background_relay_check=lambda: None, + cleanup=lambda: calls.__setitem__("cleanup", calls["cleanup"] + 1), + ) + + main.handle_post_to_nostr(pm) + assert calls["post"] == 1 + + with pytest.raises(SystemExit): + main.handle_remove_fingerprint(pm) + + assert calls["post"] == 1 + assert calls["cleanup"] == 1 + pm.current_fingerprint = fm.current_fingerprint + assert pm.current_fingerprint is None + assert pm.is_dirty is False diff --git a/src/utils/fingerprint_manager.py b/src/utils/fingerprint_manager.py index b0b2527..192006f 100644 --- a/src/utils/fingerprint_manager.py +++ b/src/utils/fingerprint_manager.py @@ -5,7 +5,7 @@ import json import logging import traceback from pathlib import Path -from typing import List, Optional +from typing import Callable, List, Optional import shutil # Ensure shutil is imported if used within the class @@ -138,12 +138,15 @@ class FingerprintManager: logger.error("Fingerprint generation failed.") return None - def remove_fingerprint(self, fingerprint: str) -> bool: - """ - Removes a fingerprint and its associated directory. + def remove_fingerprint( + self, fingerprint: str, on_last_removed: Optional[Callable[[], None]] = None + ) -> bool: + """Remove a fingerprint and its associated directory. Parameters: fingerprint (str): The fingerprint to remove. + on_last_removed (Callable | None): Callback invoked when the last + fingerprint is deleted. Returns: bool: True if removed successfully, False otherwise. @@ -167,6 +170,8 @@ class FingerprintManager: shutil.rmtree(child) fingerprint_dir.rmdir() logger.info(f"Fingerprint {fingerprint} removed successfully.") + if not self.fingerprints and on_last_removed: + on_last_removed() return True except Exception as e: logger.error(