Merge pull request #790 from PR0M3TH3AN/codex/improve-nostr-client-reliability-and-error-handling

Guard missing Nostr client in background sync
This commit is contained in:
thePR0M3TH3AN
2025-08-06 21:40:19 -04:00
committed by GitHub
4 changed files with 46 additions and 1 deletions

View File

@@ -1401,6 +1401,8 @@ class PasswordManager:
async def sync_index_from_nostr_async(self) -> None:
"""Always fetch the latest vault data from Nostr and update the local index."""
if not getattr(self, "nostr_client", None):
return
start = time.perf_counter()
try:
if getattr(self, "current_fingerprint", None):
@@ -1518,6 +1520,8 @@ class PasswordManager:
"""Launch a thread to synchronize the vault without blocking the UI."""
if getattr(self, "offline_mode", False):
return
if getattr(self, "nostr_client", None) is None:
return
if getattr(self, "_sync_task", None) and not getattr(
self._sync_task, "done", True
):
@@ -1529,7 +1533,7 @@ class PasswordManager:
await self.sync_index_from_nostr_async()
except Exception as exc:
logger.warning(f"Background sync failed: {exc}")
if hasattr(self, "error_queue"):
if hasattr(self, "error_queue") and getattr(self, "nostr_client", None):
self.error_queue.put(exc)
try:
@@ -1615,6 +1619,9 @@ class PasswordManager:
local index file was written. Returns ``False`` otherwise. The local
index file is not created on failure.
"""
if not getattr(self, "nostr_client", None):
return False
index_file = self.fingerprint_dir / "seedpass_entries_db.json.enc"
if index_file.exists():
return True
@@ -1680,6 +1687,8 @@ class PasswordManager:
asyncio.run(self.sync_index_from_nostr_if_missing_async())
async def sync_index_from_nostr_if_missing_async(self) -> None:
if not getattr(self, "nostr_client", None):
return
success = await self.attempt_initial_sync_async()
if not success:
self.vault.save_index({"schema_version": LATEST_VERSION, "entries": {}})

View File

@@ -12,6 +12,7 @@ def _make_pm():
pm.notify = lambda msg, level="INFO": pm.notifications.put(
manager_module.Notification(msg, level)
)
pm.nostr_client = object()
return pm

View File

@@ -1,10 +1,14 @@
import sys
import importlib
import queue
import time
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace
from unittest.mock import patch
import pytest
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
sys.path.append(str(Path(__file__).resolve().parents[1]))
@@ -12,6 +16,7 @@ sys.path.append(str(Path(__file__).resolve().parents[1]))
import main
from nostr.client import DEFAULT_RELAYS
from seedpass.core.config_manager import ConfigManager
from seedpass.core.manager import Notification, PasswordManager
from seedpass.core.vault import Vault
from utils.fingerprint_manager import FingerprintManager
@@ -137,3 +142,31 @@ def test_settings_menu_change_password_incorrect(monkeypatch, capsys):
out = capsys.readouterr().out
assert "Incorrect password" in out
def test_settings_menu_without_nostr_client(monkeypatch):
pm = PasswordManager.__new__(PasswordManager)
pm.offline_mode = False
pm.nostr_client = None
pm.notifications = queue.Queue()
pm.error_queue = queue.Queue()
pm.notify = lambda msg, level="INFO": pm.notifications.put(Notification(msg, level))
pm.is_dirty = False
pm.last_update = time.time()
pm.last_activity = time.time()
pm.update_activity = lambda: None
pm.lock_vault = lambda: None
pm.unlock_vault = lambda: None
pm.start_background_relay_check = lambda: None
pm.poll_background_errors = PasswordManager.poll_background_errors.__get__(pm)
pm.display_stats = lambda: None
inputs = iter(["7", ""])
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
monkeypatch.setattr("builtins.input", lambda *_: "")
with pytest.raises(SystemExit):
main.display_menu(pm, sync_interval=1000, inactivity_timeout=1000)
assert pm.error_queue.empty()
assert pm.notifications.empty()

View File

@@ -17,6 +17,7 @@ def test_unlock_triggers_sync(monkeypatch, tmp_path):
pm.setup_encryption_manager = lambda *a, **k: None
pm.initialize_bip85 = lambda: None
pm.initialize_managers = lambda: None
pm.nostr_client = object()
called = {"sync": False}
async def fake_sync(self):
@@ -62,6 +63,7 @@ def test_quick_unlock_background_sync(monkeypatch, tmp_path):
def test_start_background_sync_running_loop(monkeypatch):
pm = PasswordManager.__new__(PasswordManager)
pm.offline_mode = False
pm.nostr_client = object()
called = {"init": False, "sync": False}
async def fake_attempt(self):