mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-27 16:02:05 +00:00
tests: add deterministic nostr sync failure-mode coverage
This commit is contained in:
1 parent
b77c887446
commit
45cd27ea4f
4 files changed
+130
-3
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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"),
|
||||
]
|
||||
Reference in new issue
Block a user