Refactor test fixtures and fix linting issues

- Moved `client` and `dummy_nostr_client` fixtures to `src/tests/conftest.py` to fix F811 redefinition errors and improve test organization.
- Introduced `make_dummy_nostr_client` factory fixture to support tests requiring multiple client instances.
- Updated all affected tests to use the new fixture pattern.
- Removed deprecated `is_flag=True` from Typer boolean options in CLI modules.
- Fixed F541 (f-string without placeholders) in `entry_management.py`.
- Fixed F841 (unused variable) in `manager.py`.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-18 03:39:55 +00:00
1 parent 8cdf1c523d
commit 1f92fe7901
26 files changed
+131 -120

No files matched your search

-2
View File
@@ -28,14 +28,12 @@ no_clipboard_option = typer.Option(
False,
"--no-clipboard",
help="Disable clipboard support and print secrets instead",
is_flag=True,
)
deterministic_totp_option = typer.Option(
False,
"--deterministic-totp",
help="Derive TOTP secrets deterministically",
is_flag=True,
)
# Sub command groups
+1 -2
View File
@@ -123,7 +123,7 @@ def entry_add(
username: Optional[str] = typer.Option(None, "--username"),
url: Optional[str] = typer.Option(None, "--url"),
no_special: bool = typer.Option(
False, "--no-special", help="Exclude special characters", is_flag=True
False, "--no-special", help="Exclude special characters"
),
allowed_special_chars: Optional[str] = typer.Option(
None, "--allowed-special-chars", help="Explicit set of special characters"
@@ -137,7 +137,6 @@ def entry_add(
False,
"--exclude-ambiguous",
help="Exclude ambiguous characters",
is_flag=True,
),
min_uppercase: Optional[int] = typer.Option(None, "--min-uppercase"),
min_lowercase: Optional[int] = typer.Option(None, "--min-lowercase"),
+1 -2
View File
@@ -14,7 +14,7 @@ def generate_password(
ctx: typer.Context,
length: int = 24,
no_special: bool = typer.Option(
False, "--no-special", help="Exclude special characters", is_flag=True
False, "--no-special", help="Exclude special characters"
),
allowed_special_chars: Optional[str] = typer.Option(
None, "--allowed-special-chars", help="Explicit set of special characters"
@@ -28,7 +28,6 @@ def generate_password(
False,
"--exclude-ambiguous",
help="Exclude ambiguous characters",
is_flag=True,
),
min_uppercase: Optional[int] = typer.Option(None, "--min-uppercase"),
min_lowercase: Optional[int] = typer.Option(None, "--min-lowercase"),
+1 -1
View File
@@ -1336,7 +1336,7 @@ class EntryManager:
atomic_write(checksum_path, lambda f: f.write(checksum))
logger.debug(f"Checksum updated and written to '{checksum_path}'.")
print(colored(f"[+] Checksum updated successfully.", "green"))
print(colored("[+] Checksum updated successfully.", "green"))
except Exception as e:
logger.error(f"Failed to update checksum: {e}", exc_info=True)
+2 -2
View File
@@ -1671,7 +1671,7 @@ class PasswordManager:
self.error_queue.put(exc)
try:
loop = asyncio.get_running_loop()
asyncio.get_running_loop()
except RuntimeError:
thread = threading.Thread(
target=lambda: asyncio.run(_worker()), daemon=True
@@ -1734,7 +1734,7 @@ class PasswordManager:
self.error_queue.put(exc)
try:
loop = asyncio.get_running_loop()
asyncio.get_running_loop()
except RuntimeError:
threading.Thread(target=_worker, daemon=True).start()
else:
+101 -1
View File
@@ -4,14 +4,30 @@ import sys
from pathlib import Path
import pytest
from types import SimpleNamespace
from httpx import ASGITransport, AsyncClient
import bcrypt
sys.path.append(str(Path(__file__).resolve().parents[1]))
from helpers import create_vault, TEST_PASSWORD, TEST_SEED
from seedpass import api
from seedpass.core.entry_types import EntryType
from helpers import (
create_vault,
TEST_PASSWORD,
TEST_SEED,
DummyBuilder,
DummyFilter,
DummyTag,
DummyTimestamp,
DummyEventId,
DummyRelayClient,
)
from seedpass.core.backup import BackupManager
from seedpass.core.config_manager import ConfigManager
from seedpass.core.entry_management import EntryManager
from seedpass.core.manager import EncryptionMode, PasswordManager
from seedpass.core.encryption import EncryptionManager as EncMgr
@pytest.fixture(
@@ -94,3 +110,87 @@ def password_manager(vault, tmp_path):
pm.is_dirty = False
pm.secret_mode_enabled = False
return pm
@pytest.fixture
async def client(monkeypatch):
dummy = SimpleNamespace(
entry_manager=SimpleNamespace(
search_entries=lambda q: [
(1, "Site", "user", "url", False, EntryType.PASSWORD)
],
retrieve_entry=lambda i: {"label": "Site"},
add_entry=lambda *a, **k: 1,
modify_entry=lambda *a, **k: None,
archive_entry=lambda i: None,
restore_entry=lambda i: None,
),
config_manager=SimpleNamespace(
load_config=lambda require_pin=False: {"k": "v"},
set_pin=lambda v: None,
set_password_hash=lambda v: None,
set_relays=lambda v, require_pin=False: None,
set_inactivity_timeout=lambda v: None,
set_additional_backup_path=lambda v: None,
set_secret_mode_enabled=lambda v: None,
set_clipboard_clear_delay=lambda v: None,
set_quick_unlock=lambda v: None,
),
fingerprint_manager=SimpleNamespace(list_fingerprints=lambda: ["fp"]),
nostr_client=SimpleNamespace(
key_manager=SimpleNamespace(get_npub=lambda: "np")
),
verify_password=lambda pw: True,
)
monkeypatch.setattr(api, "PasswordManager", lambda: dummy)
monkeypatch.setenv("SEEDPASS_CORS_ORIGINS", "http://example.com")
token = api.start_server()
transport = ASGITransport(app=api.app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
yield ac, token
@pytest.fixture
def make_dummy_nostr_client(monkeypatch):
"""Factory to return a NostrClient wired to a DummyRelayClient."""
from cryptography.fernet import Fernet
from nostr.client import NostrClient
def _make(base_path):
relay = DummyRelayClient()
monkeypatch.setattr("nostr.client.Client", lambda signer: relay)
monkeypatch.setattr("nostr.client.EventBuilder", DummyBuilder)
monkeypatch.setattr("nostr.client.Filter", DummyFilter)
monkeypatch.setattr("nostr.client.Tag", DummyTag)
monkeypatch.setattr("nostr.client.Timestamp", DummyTimestamp)
monkeypatch.setattr("nostr.client.EventId", DummyEventId)
from nostr.backup_models import KIND_DELTA as KD
monkeypatch.setattr("nostr.client.KIND_DELTA", KD, raising=False)
monkeypatch.setattr(NostrClient, "initialize_client_pool", lambda self: None)
enc_mgr = EncMgr(Fernet.generate_key(), base_path)
class DummyKeys:
def private_key_hex(self):
return "1" * 64
def public_key_hex(self):
return "2" * 64
class DummyKeyManager:
def __init__(self, *a, **k):
self.keys = DummyKeys()
with pytest.MonkeyPatch().context() as mp:
mp.setattr("nostr.client.KeyManager", DummyKeyManager)
mp.setattr(enc_mgr, "decrypt_parent_seed", lambda: TEST_SEED)
client = NostrClient(enc_mgr, "fp")
return client, relay
return _make
@pytest.fixture
def dummy_nostr_client(make_dummy_nostr_client, tmp_path):
return make_dummy_nostr_client(tmp_path)
-37
View File
@@ -36,7 +36,6 @@ def create_vault(
import uuid
import asyncio
import pytest
from nostr.backup_models import (
KIND_MANIFEST,
@@ -239,39 +238,3 @@ class DummyRelayClient:
return Result(events)
@pytest.fixture
def dummy_nostr_client(tmp_path, monkeypatch):
"""Return a NostrClient wired to a DummyRelayClient."""
from cryptography.fernet import Fernet
from nostr.client import NostrClient
relay = DummyRelayClient()
monkeypatch.setattr("nostr.client.Client", lambda signer: relay)
monkeypatch.setattr("nostr.client.EventBuilder", DummyBuilder)
monkeypatch.setattr("nostr.client.Filter", DummyFilter)
monkeypatch.setattr("nostr.client.Tag", DummyTag)
monkeypatch.setattr("nostr.client.Timestamp", DummyTimestamp)
monkeypatch.setattr("nostr.client.EventId", DummyEventId)
from nostr.backup_models import KIND_DELTA as KD
monkeypatch.setattr("nostr.client.KIND_DELTA", KD, raising=False)
monkeypatch.setattr(NostrClient, "initialize_client_pool", lambda self: None)
enc_mgr = EncryptionManager(Fernet.generate_key(), tmp_path)
class DummyKeys:
def private_key_hex(self):
return "1" * 64
def public_key_hex(self):
return "2" * 64
class DummyKeyManager:
def __init__(self, *a, **k):
self.keys = DummyKeys()
with pytest.MonkeyPatch().context() as mp:
mp.setattr("nostr.client.KeyManager", DummyKeyManager)
mp.setattr(enc_mgr, "decrypt_parent_seed", lambda: TEST_SEED)
client = NostrClient(enc_mgr, "fp")
return client, relay
-41
View File
@@ -1,53 +1,12 @@
from types import SimpleNamespace
from pathlib import Path
import sys
import pytest
from httpx import ASGITransport, AsyncClient
import bcrypt
sys.path.append(str(Path(__file__).resolve().parents[1]))
from seedpass import api
from seedpass.core.entry_types import EntryType
@pytest.fixture
async def client(monkeypatch):
dummy = SimpleNamespace(
entry_manager=SimpleNamespace(
search_entries=lambda q: [
(1, "Site", "user", "url", False, EntryType.PASSWORD)
],
retrieve_entry=lambda i: {"label": "Site"},
add_entry=lambda *a, **k: 1,
modify_entry=lambda *a, **k: None,
archive_entry=lambda i: None,
restore_entry=lambda i: None,
),
config_manager=SimpleNamespace(
load_config=lambda require_pin=False: {"k": "v"},
set_pin=lambda v: None,
set_password_hash=lambda v: None,
set_relays=lambda v, require_pin=False: None,
set_inactivity_timeout=lambda v: None,
set_additional_backup_path=lambda v: None,
set_secret_mode_enabled=lambda v: None,
set_clipboard_clear_delay=lambda v: None,
set_quick_unlock=lambda v: None,
),
fingerprint_manager=SimpleNamespace(list_fingerprints=lambda: ["fp"]),
nostr_client=SimpleNamespace(
key_manager=SimpleNamespace(get_npub=lambda: "np")
),
verify_password=lambda pw: True,
)
monkeypatch.setattr(api, "PasswordManager", lambda: dummy)
monkeypatch.setenv("SEEDPASS_CORS_ORIGINS", "http://example.com")
token = api.start_server()
transport = ASGITransport(app=api.app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
yield ac, token
@pytest.mark.anyio
-2
View File
@@ -5,8 +5,6 @@ import base64
import pytest
from seedpass import api
from test_api import client
from helpers import dummy_nostr_client
import string
from seedpass.core.password_generation import PasswordGenerator, PasswordPolicy
from seedpass.core.encryption import EncryptionManager
-1
View File
@@ -1,4 +1,3 @@
from test_api import client
from types import SimpleNamespace
import queue
import pytest
-1
View File
@@ -1,4 +1,3 @@
from test_api import client
import pytest
+1 -1
View File
@@ -2,7 +2,7 @@ import asyncio
from pathlib import Path
from tempfile import TemporaryDirectory
from helpers import create_vault, dummy_nostr_client
from helpers import create_vault
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
+1 -1
View File
@@ -2,7 +2,7 @@ import asyncio
from pathlib import Path
from tempfile import TemporaryDirectory
from helpers import create_vault, dummy_nostr_client
from helpers import create_vault
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
@@ -5,7 +5,6 @@ from tempfile import TemporaryDirectory
import asyncio
import gzip
from helpers import dummy_nostr_client
def load_script():
+1 -1
View File
@@ -5,7 +5,7 @@ from types import SimpleNamespace
import pytest
from helpers import create_vault, TEST_SEED, TEST_PASSWORD, dummy_nostr_client
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
sys.path.append(str(Path(__file__).resolve().parents[1]))
-1
View File
@@ -1,6 +1,5 @@
import asyncio
from helpers import dummy_nostr_client
def test_published_events_no_fingerprint(dummy_nostr_client):
+4 -4
View File
@@ -2,7 +2,7 @@ import asyncio
from pathlib import Path
from tempfile import TemporaryDirectory
from helpers import create_vault, dummy_nostr_client, TEST_SEED
from helpers import create_vault, TEST_SEED
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
@@ -34,8 +34,8 @@ def _init_pm(dir_path: Path, client) -> PasswordManager:
return pm
def test_manifest_state_restored(monkeypatch, tmp_path):
client, relay = dummy_nostr_client.__wrapped__(tmp_path / "c1", monkeypatch)
def test_manifest_state_restored(monkeypatch, tmp_path, make_dummy_nostr_client):
client, relay = make_dummy_nostr_client(tmp_path / "c1")
with TemporaryDirectory() as tmpdir:
fp_dir = Path(tmpdir)
pm1 = _init_pm(fp_dir, client)
@@ -48,7 +48,7 @@ def test_manifest_state_restored(monkeypatch, tmp_path):
assert delta_ts > 0
assert result["manifest_id"] == manifest_id
client2, _ = dummy_nostr_client.__wrapped__(tmp_path / "c2", monkeypatch)
client2, _ = make_dummy_nostr_client(tmp_path / "c2")
monkeypatch.setattr(
"seedpass.core.manager.NostrClient", lambda *a, **k: client2
)
+1 -1
View File
@@ -2,7 +2,7 @@ import asyncio
from pathlib import Path
from tempfile import TemporaryDirectory
from helpers import create_vault, dummy_nostr_client
from helpers import create_vault
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
+1 -1
View File
@@ -3,7 +3,7 @@ import gzip
import math
import pytest
from helpers import create_vault, dummy_nostr_client, TEST_SEED
from helpers import create_vault, TEST_SEED
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
from seedpass.core.config_manager import ConfigManager
+1 -1
View File
@@ -3,7 +3,7 @@ import base64
import hashlib
import json
from helpers import DummyEvent, DummyFilter, dummy_nostr_client
from helpers import DummyEvent, DummyFilter
from nostr.backup_models import KIND_MANIFEST, KIND_SNAPSHOT_CHUNK
from nostr_sdk import Keys
+3 -3
View File
@@ -1,7 +1,7 @@
from pathlib import Path
import main
from helpers import create_vault, dummy_nostr_client, TEST_SEED, TEST_PASSWORD
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
from seedpass.core.config_manager import ConfigManager
@@ -28,8 +28,8 @@ def _init_pm(dir_path: Path, client) -> PasswordManager:
return pm
def test_restore_flow_from_snapshot(monkeypatch, tmp_path):
client, relay = dummy_nostr_client.__wrapped__(tmp_path / "srv", monkeypatch)
def test_restore_flow_from_snapshot(monkeypatch, tmp_path, make_dummy_nostr_client):
client, relay = make_dummy_nostr_client(tmp_path / "srv")
dir_a = tmp_path / "A"
dir_b = tmp_path / "B"
+1 -1
View File
@@ -6,7 +6,7 @@ sys.path.append(str(Path(__file__).resolve().parents[1]))
from utils.fingerprint_manager import FingerprintManager
from seedpass.core.manager import PasswordManager, EncryptionMode
from helpers import create_vault, dummy_nostr_client
from helpers import create_vault
import gzip
from nostr.backup_models import Manifest, ChunkMeta
+7 -7
View File
@@ -1,6 +1,6 @@
from pathlib import Path
from helpers import create_vault, dummy_nostr_client, TEST_SEED, TEST_PASSWORD
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
@@ -28,8 +28,8 @@ def _init_pm(dir_path: Path, client) -> PasswordManager:
return pm
def test_handle_new_seed_setup_restore_from_nostr(monkeypatch, tmp_path, capsys):
client, _relay = dummy_nostr_client.__wrapped__(tmp_path / "srv", monkeypatch)
def test_handle_new_seed_setup_restore_from_nostr(monkeypatch, tmp_path, capsys, make_dummy_nostr_client):
client, _relay = make_dummy_nostr_client(tmp_path / "srv")
dir_a = tmp_path / "A"
dir_b = tmp_path / "B"
@@ -133,8 +133,8 @@ async def _no_snapshot():
return None
def test_restore_from_nostr_warns(monkeypatch, tmp_path, capsys):
client, _relay = dummy_nostr_client.__wrapped__(tmp_path / "srv", monkeypatch)
def test_restore_from_nostr_warns(monkeypatch, tmp_path, capsys, make_dummy_nostr_client):
client, _relay = make_dummy_nostr_client(tmp_path / "srv")
monkeypatch.setattr(client, "fetch_latest_snapshot", _no_snapshot)
pm = PasswordManager.__new__(PasswordManager)
@@ -150,8 +150,8 @@ def test_restore_from_nostr_warns(monkeypatch, tmp_path, capsys):
assert "No Nostr backup" in out
def test_restore_from_nostr_abort(monkeypatch, tmp_path, capsys):
client, _relay = dummy_nostr_client.__wrapped__(tmp_path / "srv", monkeypatch)
def test_restore_from_nostr_abort(monkeypatch, tmp_path, capsys, make_dummy_nostr_client):
client, _relay = make_dummy_nostr_client(tmp_path / "srv")
monkeypatch.setattr(client, "fetch_latest_snapshot", _no_snapshot)
pm = PasswordManager.__new__(PasswordManager)
+1 -1
View File
@@ -4,7 +4,7 @@ from pathlib import Path
import pytest
from helpers import create_vault, TEST_SEED, TEST_PASSWORD, dummy_nostr_client
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
from seedpass.core.manager import PasswordManager, EncryptionMode
+3 -3
View File
@@ -3,7 +3,7 @@ import threading
from pathlib import Path
from helpers import create_vault, dummy_nostr_client
from helpers import create_vault
from seedpass.core.entry_management import EntryManager
from seedpass.core.backup import BackupManager
@@ -31,8 +31,8 @@ def _init_pm(dir_path: Path, client) -> PasswordManager:
return pm
def test_sync_race_conditions(monkeypatch, tmp_path):
client_a, relay = dummy_nostr_client.__wrapped__(tmp_path / "c1", monkeypatch)
def test_sync_race_conditions(monkeypatch, tmp_path, make_dummy_nostr_client):
client_a, relay = make_dummy_nostr_client(tmp_path / "c1")
from cryptography.fernet import Fernet
from nostr.client import NostrClient
-1
View File
@@ -2,7 +2,6 @@ import asyncio
import logging
from seedpass.core.manager import PasswordManager
from helpers import dummy_nostr_client
def test_unlock_vault_logs_time(monkeypatch, caplog, tmp_path):