From ee240cbd1eec368481f9a0860e25543947c5f5d6 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:58:58 -0400 Subject: [PATCH] Add notification tracking and tests --- src/constants.py | 3 ++ src/password_manager/manager.py | 25 ++++++++++- .../test_manager_current_notification.py | 45 +++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 src/tests/test_manager_current_notification.py diff --git a/src/constants.py b/src/constants.py index 2ebdf36..7d99552 100644 --- a/src/constants.py +++ b/src/constants.py @@ -51,6 +51,9 @@ MAX_PASSWORD_LENGTH = 128 # Maximum allowed password length # Timeout in seconds before the vault locks due to inactivity INACTIVITY_TIMEOUT = 15 * 60 # 15 minutes +# Duration in seconds that a notification remains active +NOTIFICATION_DURATION = 10 + # ----------------------------------- # Additional Constants (if any) # ----------------------------------- diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 9b60b4e..c6d10c3 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -75,6 +75,7 @@ from constants import ( DEFAULT_PASSWORD_LENGTH, INACTIVITY_TIMEOUT, DEFAULT_SEED_BACKUP_FILENAME, + NOTIFICATION_DURATION, initialize_app, ) @@ -137,6 +138,8 @@ class PasswordManager: self.nostr_client: Optional[NostrClient] = None self.config_manager: Optional[ConfigManager] = None self.notifications: queue.Queue[Notification] = queue.Queue() + self._current_notification: Optional[Notification] = None + self._notification_expiry: float = 0.0 # Track changes to trigger periodic Nostr sync self.is_dirty: bool = False @@ -228,8 +231,26 @@ class PasswordManager: self.last_activity = time.time() def notify(self, message: str, level: str = "INFO") -> None: - """Enqueue a notification for later retrieval.""" - self.notifications.put(Notification(message, level)) + """Enqueue a notification and set it as the active message.""" + note = Notification(message, level) + self.notifications.put(note) + self._current_notification = note + self._notification_expiry = time.time() + NOTIFICATION_DURATION + + def get_current_notification(self) -> Optional[Notification]: + """Return the active notification if it hasn't expired.""" + if not self.notifications.empty(): + latest = self.notifications.queue[-1] + if latest is not self._current_notification: + self._current_notification = latest + self._notification_expiry = time.time() + NOTIFICATION_DURATION + + if ( + self._current_notification is not None + and time.time() < self._notification_expiry + ): + return self._current_notification + return None def lock_vault(self) -> None: """Clear sensitive information from memory.""" diff --git a/src/tests/test_manager_current_notification.py b/src/tests/test_manager_current_notification.py new file mode 100644 index 0000000..2fceb74 --- /dev/null +++ b/src/tests/test_manager_current_notification.py @@ -0,0 +1,45 @@ +import queue + +from pathlib import Path +import sys + +sys.path.append(str(Path(__file__).resolve().parents[1])) + +from password_manager.manager import PasswordManager, Notification +from constants import NOTIFICATION_DURATION + + +def _make_pm(): + pm = PasswordManager.__new__(PasswordManager) + pm.notifications = queue.Queue() + pm._current_notification = None + pm._notification_expiry = 0.0 + return pm + + +def test_notify_sets_current(monkeypatch): + pm = _make_pm() + current = {"val": 100.0} + monkeypatch.setattr("password_manager.manager.time.time", lambda: current["val"]) + pm.notify("hello") + note = pm._current_notification + assert isinstance(note, Notification) + assert note.message == "hello" + assert pm._notification_expiry == 100.0 + NOTIFICATION_DURATION + assert pm.notifications.qsize() == 1 + + +def test_get_current_notification_ttl(monkeypatch): + pm = _make_pm() + now = {"val": 0.0} + monkeypatch.setattr("password_manager.manager.time.time", lambda: now["val"]) + pm.notify("note1") + + assert pm.get_current_notification().message == "note1" + assert pm.notifications.qsize() == 1 + + now["val"] += NOTIFICATION_DURATION - 1 + assert pm.get_current_notification().message == "note1" + + now["val"] += 2 + assert pm.get_current_notification() is None