tests: add deterministic nostr sync failure-mode coverage

This commit is contained in:
thePR0M3TH3AN committed 2026-03-02 09:06:26 -05:00
1 parent b77c887446
commit 45cd27ea4f
4 files changed
+130 -3

No files matched your search

+9 -2
View File
@@ -64,8 +64,8 @@ Use these docs as inputs, but treat this file as the decision layer:
## 4) Recommended Next Steps (Priority Order)
1. Advance Nostr resilience validation with deterministic failure-mode suites (then optional real-relay soak lane).
2. Update cutover memo to reflect current reality and remaining concrete blockers.
1. Update cutover memo to reflect current reality and remaining concrete blockers.
2. Advance Nostr resilience validation with optional real-relay soak lane after deterministic suites.
3. Finish supply-chain readiness evidence and release-protection policy.
## 5) Next Slice Definition (Recommended Immediate Work)
@@ -105,6 +105,13 @@ Progress update (2026-03-02):
- new focused gate `scripts/tui2_coverage_gate.sh` enforces:
- `src/seedpass/tui_v2/app.py >= 78%`
- `src/seedpass/core/api.py >= 85%`
- Nostr deterministic resilience suite expanded:
- new tests in `src/tests/test_nostr_resilience_failure_modes.py` cover:
- no encrypted data sync attempt
- publish-without-event fallback error handling
- preservation of relay-provided publish errors
- snapshot-missing warning path
- snapshot-fetch exception warning path
## 6) Working Rule
+1 -1
View File
@@ -8,7 +8,7 @@ Status legend: `Not Started`, `In Progress`, `Blocked`, `Done`
| 2 | Crypto and key management review | Done | Security / Core | `docs/crypto_key_management_review.md`, `src/tests/test_kdf_modes.py`, `src/tests/test_export_plaintext_policy.py` |
| 3 | Secret handling and local data exposure hardening | Done | Core | `docs/secret_handling_local_exposure_review.md`, `src/tests/test_memory_protection.py`, `src/tests/test_clipboard_utils.py`, `src/tests/test_fingerprint_encryption.py`, `src/tests/test_api_new_endpoints.py`, `src/tests/test_bip85_derivation_path.py` |
| 4 | Backup and restore integrity validation | In Progress | Core / QA | `src/tests/test_account_roundtrip_restore.py`, `src/tests/test_portable_backup.py` |
| 5 | Nostr sync security and resilience validation | In Progress | Core / QA | `src/tests/test_nostr_real.py`, `src/tests/test_nostr_index_size.py` |
| 5 | Nostr sync security and resilience validation | In Progress | Core / QA | `src/tests/test_nostr_real.py`, `src/tests/test_nostr_index_size.py`, `src/tests/test_nostr_resilience_failure_modes.py` |
| 6 | Auth, lock/unlock, and access-control hardening | Done | Core | `docs/auth_lock_unlock_access_review.md`, `src/tests/test_api_new_endpoints.py`, `src/tests/test_vault_lock_flag.py`, `src/tests/test_inactivity_lock.py`, `src/tests/test_unlock_sync.py` |
| 7 | Testing gates and quality thresholds | In Progress | QA | `scripts/check_critical_coverage.py`, `scripts/tui2_coverage_gate.sh`, CI workflows |
| 8 | Supply chain and release integrity | In Progress | DevOps | `docs/supply_chain_release_integrity.md`, `.github/workflows/release-integrity.yml`, `scripts/release_integrity.py`, `src/tests/test_release_integrity_utils.py` |
+15
View File
@@ -721,3 +721,18 @@
- `src/seedpass/core/api.py: 86.64%`
- `src/seedpass/tui_v2/app.py: 79.87%`
- `python3 scripts/check_critical_coverage.py artifacts/coverage/coverage.json --json-output artifacts/coverage/critical_gate.full.json` -> PASS.
## 2026-03-02 Nostr resilience deterministic failure-mode suite expansion
- Added new deterministic resilience tests in `src/tests/test_nostr_resilience_failure_modes.py` targeting `PasswordManager` Nostr sync error paths.
- New covered scenarios:
- `sync_vault_async` returns `None` and sets explicit error when encrypted index bytes are unavailable.
- `sync_vault_async` preserves relay/publish error text when `publish_snapshot` fails to produce an event ID.
- `sync_vault_async` sets fallback publish failure error when no relay error text is provided.
- `sync_index_from_nostr_async` emits warning notification when snapshot fetch returns no result and client has `last_error`.
- `sync_index_from_nostr_async` emits warning notification on snapshot-fetch exceptions.
- Validation run:
- `pytest -q src/tests/test_nostr_resilience_failure_modes.py src/tests/test_background_vault_sync_paths.py src/tests/test_post_sync_messages.py src/tests/test_offline_mode_behavior.py`
- Result: `16 passed`.
- Docs updated:
- `docs/security_readiness_checklist.md` now includes `test_nostr_resilience_failure_modes.py` under Nostr resilience evidence.
- `docs/dev_control_center.md` progress notes updated to reflect deterministic Nostr resilience suite expansion.
@@ -0,0 +1,105 @@
from __future__ import annotations
import asyncio
from types import SimpleNamespace
from seedpass.core.manager import PasswordManager
def test_sync_vault_async_sets_last_error_when_no_encrypted_data() -> None:
pm = PasswordManager.__new__(PasswordManager)
pm.offline_mode = False
pm.get_encrypted_data = lambda: b""
pm.nostr_client = SimpleNamespace(last_error=None)
result = asyncio.run(pm.sync_vault_async())
assert result is None
assert pm.nostr_client.last_error == "No encrypted index data available to sync."
def test_sync_vault_async_preserves_existing_error_when_publish_returns_none() -> None:
pm = PasswordManager.__new__(PasswordManager)
pm.offline_mode = False
pm.is_dirty = True
pm.get_encrypted_data = lambda: b"encrypted"
pm.state_manager = None
client = SimpleNamespace(last_error=None, get_delta_events=lambda: [])
def _publish_snapshot(_enc):
client.last_error = "relay timeout"
return None, None
client.publish_snapshot = _publish_snapshot
pm.nostr_client = client
result = asyncio.run(pm.sync_vault_async())
assert result is None
assert pm.nostr_client.last_error == "relay timeout"
assert pm.is_dirty is False
def test_sync_vault_async_sets_default_error_when_publish_returns_none() -> None:
pm = PasswordManager.__new__(PasswordManager)
pm.offline_mode = False
pm.is_dirty = True
pm.get_encrypted_data = lambda: b"encrypted"
pm.state_manager = None
pm.nostr_client = SimpleNamespace(
last_error=None,
publish_snapshot=lambda _enc: (None, None),
get_delta_events=lambda: [],
)
result = asyncio.run(pm.sync_vault_async())
assert result is None
assert pm.nostr_client.last_error == (
"Failed to publish encrypted index to configured relays."
)
assert pm.is_dirty is False
def test_sync_index_from_nostr_async_notifies_last_error_when_snapshot_missing() -> None:
notifications: list[tuple[str, str]] = []
async def _no_snapshot():
return None
pm = PasswordManager.__new__(PasswordManager)
pm.current_fingerprint = "fp"
pm.nostr_client = SimpleNamespace(
relays=["wss://relay-1"],
last_error="timeout waiting for snapshot",
fetch_latest_snapshot=_no_snapshot,
)
pm.notify = lambda msg, level="INFO": notifications.append((msg, level))
asyncio.run(pm.sync_index_from_nostr_async())
assert notifications == [
("Sync failed: timeout waiting for snapshot", "WARNING"),
]
def test_sync_index_from_nostr_async_notifies_exception_when_fetch_raises() -> None:
notifications: list[tuple[str, str]] = []
async def _boom():
raise RuntimeError("relay unreachable")
pm = PasswordManager.__new__(PasswordManager)
pm.current_fingerprint = "fp"
pm.nostr_client = SimpleNamespace(
relays=["wss://relay-1"],
last_error=None,
fetch_latest_snapshot=_boom,
)
pm.notify = lambda msg, level="INFO": notifications.append((msg, level))
asyncio.run(pm.sync_index_from_nostr_async())
assert notifications == [
("Sync failed: relay unreachable", "WARNING"),
]