mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 15:58:48 +00:00
Merge pull request #161 from PR0M3TH3AN/codex/display-nostr-event-id-after-push
Display event ID after Nostr sync
This commit is contained in:
11
src/main.py
11
src/main.py
@@ -226,9 +226,14 @@ def handle_post_to_nostr(
|
|||||||
Handles the action of posting the encrypted password index to Nostr.
|
Handles the action of posting the encrypted password index to Nostr.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
success = password_manager.sync_vault(alt_summary=alt_summary)
|
event_id = password_manager.sync_vault(alt_summary=alt_summary)
|
||||||
if success:
|
if event_id:
|
||||||
print(colored("\N{WHITE HEAVY CHECK MARK} Sync complete.", "green"))
|
print(
|
||||||
|
colored(
|
||||||
|
f"\N{WHITE HEAVY CHECK MARK} Sync complete. Event ID: {event_id}",
|
||||||
|
"green",
|
||||||
|
)
|
||||||
|
)
|
||||||
logging.info("Encrypted index posted to Nostr successfully.")
|
logging.info("Encrypted index posted to Nostr successfully.")
|
||||||
else:
|
else:
|
||||||
print(colored("\N{CROSS MARK} Sync failed…", "red"))
|
print(colored("\N{CROSS MARK} Sync failed…", "red"))
|
||||||
|
@@ -142,7 +142,7 @@ class NostrClient:
|
|||||||
encrypted_json: bytes,
|
encrypted_json: bytes,
|
||||||
to_pubkey: str | None = None,
|
to_pubkey: str | None = None,
|
||||||
alt_summary: str | None = None,
|
alt_summary: str | None = None,
|
||||||
) -> bool:
|
) -> str | None:
|
||||||
"""Builds and publishes a Kind 1 text note or direct message.
|
"""Builds and publishes a Kind 1 text note or direct message.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -177,12 +177,12 @@ class NostrClient:
|
|||||||
else str(event_output)
|
else str(event_output)
|
||||||
)
|
)
|
||||||
logger.info(f"Successfully published event with ID: {event_id_hex}")
|
logger.info(f"Successfully published event with ID: {event_id_hex}")
|
||||||
return True
|
return event_id_hex
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.last_error = str(e)
|
self.last_error = str(e)
|
||||||
logger.error(f"Failed to publish JSON to Nostr: {e}")
|
logger.error(f"Failed to publish JSON to Nostr: {e}")
|
||||||
return False
|
return None
|
||||||
|
|
||||||
def publish_event(self, event):
|
def publish_event(self, event):
|
||||||
"""Publish a prepared event to the configured relays."""
|
"""Publish a prepared event to the configured relays."""
|
||||||
@@ -242,7 +242,7 @@ class NostrClient:
|
|||||||
|
|
||||||
async def publish_snapshot(
|
async def publish_snapshot(
|
||||||
self, encrypted_bytes: bytes, limit: int = 50_000
|
self, encrypted_bytes: bytes, limit: int = 50_000
|
||||||
) -> Manifest:
|
) -> tuple[Manifest, str]:
|
||||||
"""Publish a compressed snapshot split into chunks.
|
"""Publish a compressed snapshot split into chunks.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -276,10 +276,11 @@ class NostrClient:
|
|||||||
.build(self.keys.public_key())
|
.build(self.keys.public_key())
|
||||||
.sign_with_keys(self.keys)
|
.sign_with_keys(self.keys)
|
||||||
)
|
)
|
||||||
await self.client.send_event(manifest_event)
|
result = await self.client.send_event(manifest_event)
|
||||||
|
manifest_id = result.id.to_hex() if hasattr(result, "id") else str(result)
|
||||||
self.current_manifest = manifest
|
self.current_manifest = manifest
|
||||||
self._delta_events = []
|
self._delta_events = []
|
||||||
return manifest
|
return manifest, manifest_id
|
||||||
|
|
||||||
async def fetch_latest_snapshot(self) -> Tuple[Manifest, list[bytes]] | None:
|
async def fetch_latest_snapshot(self) -> Tuple[Manifest, list[bytes]] | None:
|
||||||
"""Retrieve the latest manifest and all snapshot chunks."""
|
"""Retrieve the latest manifest and all snapshot chunks."""
|
||||||
|
@@ -1129,26 +1129,26 @@ class PasswordManager:
|
|||||||
# Re-raise the exception to inform the calling function of the failure
|
# Re-raise the exception to inform the calling function of the failure
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def sync_vault(self, alt_summary: str | None = None) -> bool:
|
def sync_vault(self, alt_summary: str | None = None) -> str | None:
|
||||||
"""Publish the current vault contents to Nostr."""
|
"""Publish the current vault contents to Nostr."""
|
||||||
try:
|
try:
|
||||||
encrypted = self.get_encrypted_data()
|
encrypted = self.get_encrypted_data()
|
||||||
if not encrypted:
|
if not encrypted:
|
||||||
return False
|
return None
|
||||||
pub_snap = getattr(self.nostr_client, "publish_snapshot", None)
|
pub_snap = getattr(self.nostr_client, "publish_snapshot", None)
|
||||||
if callable(pub_snap):
|
if callable(pub_snap):
|
||||||
if asyncio.iscoroutinefunction(pub_snap):
|
if asyncio.iscoroutinefunction(pub_snap):
|
||||||
asyncio.run(pub_snap(encrypted))
|
_, event_id = asyncio.run(pub_snap(encrypted))
|
||||||
else:
|
else:
|
||||||
pub_snap(encrypted)
|
_, event_id = pub_snap(encrypted)
|
||||||
else:
|
else:
|
||||||
# Fallback for tests using simplified stubs
|
# Fallback for tests using simplified stubs
|
||||||
self.nostr_client.publish_json_to_nostr(encrypted)
|
event_id = self.nostr_client.publish_json_to_nostr(encrypted)
|
||||||
self.is_dirty = False
|
self.is_dirty = False
|
||||||
return True
|
return event_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Failed to sync vault: {e}", exc_info=True)
|
logging.error(f"Failed to sync vault: {e}", exc_info=True)
|
||||||
return False
|
return None
|
||||||
|
|
||||||
def backup_database(self) -> None:
|
def backup_database(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@@ -22,6 +22,7 @@ class FakeNostrClient:
|
|||||||
|
|
||||||
def publish_snapshot(self, data: bytes):
|
def publish_snapshot(self, data: bytes):
|
||||||
self.published.append(data)
|
self.published.append(data)
|
||||||
|
return None, "abcd"
|
||||||
|
|
||||||
|
|
||||||
def test_manager_workflow(monkeypatch):
|
def test_manager_workflow(monkeypatch):
|
||||||
|
@@ -25,7 +25,7 @@ def test_backup_and_publish_to_nostr():
|
|||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"nostr.client.NostrClient.publish_snapshot",
|
"nostr.client.NostrClient.publish_snapshot",
|
||||||
AsyncMock(return_value=None),
|
AsyncMock(return_value=(None, "abcd")),
|
||||||
) as mock_publish, patch("nostr.client.ClientBuilder"), patch(
|
) as mock_publish, patch("nostr.client.ClientBuilder"), patch(
|
||||||
"nostr.client.KeyManager"
|
"nostr.client.KeyManager"
|
||||||
), patch.object(
|
), patch.object(
|
||||||
@@ -38,4 +38,4 @@ def test_backup_and_publish_to_nostr():
|
|||||||
result = asyncio.run(nostr_client.publish_snapshot(encrypted_index))
|
result = asyncio.run(nostr_client.publish_snapshot(encrypted_index))
|
||||||
|
|
||||||
mock_publish.assert_awaited_with(encrypted_index)
|
mock_publish.assert_awaited_with(encrypted_index)
|
||||||
assert result is None
|
assert result == (None, "abcd")
|
||||||
|
@@ -29,7 +29,7 @@ def test_retrieve_multi_chunk_snapshot(dummy_nostr_client):
|
|||||||
|
|
||||||
client, relay = dummy_nostr_client
|
client, relay = dummy_nostr_client
|
||||||
data = os.urandom(120000)
|
data = os.urandom(120000)
|
||||||
manifest = asyncio.run(client.publish_snapshot(data, limit=50000))
|
manifest, _ = asyncio.run(client.publish_snapshot(data, limit=50000))
|
||||||
assert len(manifest.chunks) > 1
|
assert len(manifest.chunks) > 1
|
||||||
fetched_manifest, chunk_bytes = asyncio.run(client.fetch_latest_snapshot())
|
fetched_manifest, chunk_bytes = asyncio.run(client.fetch_latest_snapshot())
|
||||||
assert len(chunk_bytes) == len(manifest.chunks)
|
assert len(chunk_bytes) == len(manifest.chunks)
|
||||||
@@ -40,7 +40,7 @@ def test_retrieve_multi_chunk_snapshot(dummy_nostr_client):
|
|||||||
def test_publish_and_fetch_deltas(dummy_nostr_client):
|
def test_publish_and_fetch_deltas(dummy_nostr_client):
|
||||||
client, relay = dummy_nostr_client
|
client, relay = dummy_nostr_client
|
||||||
base = b"base"
|
base = b"base"
|
||||||
manifest = asyncio.run(client.publish_snapshot(base))
|
manifest, _ = asyncio.run(client.publish_snapshot(base))
|
||||||
manifest_id = relay.manifests[-1].id
|
manifest_id = relay.manifests[-1].id
|
||||||
d1 = b"d1"
|
d1 = b"d1"
|
||||||
d2 = b"d2"
|
d2 = b"d2"
|
||||||
|
@@ -43,7 +43,7 @@ def test_change_password_triggers_nostr_backup(monkeypatch):
|
|||||||
|
|
||||||
with patch("password_manager.manager.NostrClient") as MockClient:
|
with patch("password_manager.manager.NostrClient") as MockClient:
|
||||||
mock_instance = MockClient.return_value
|
mock_instance = MockClient.return_value
|
||||||
mock_instance.publish_snapshot = AsyncMock(return_value=None)
|
mock_instance.publish_snapshot = AsyncMock(return_value=(None, "abcd"))
|
||||||
pm.nostr_client = mock_instance
|
pm.nostr_client = mock_instance
|
||||||
pm.change_password()
|
pm.change_password()
|
||||||
mock_instance.publish_snapshot.assert_called_once()
|
mock_instance.publish_snapshot.assert_called_once()
|
||||||
|
@@ -54,7 +54,9 @@ def test_password_change_and_unlock(monkeypatch):
|
|||||||
pm.fingerprint_dir = fp
|
pm.fingerprint_dir = fp
|
||||||
pm.current_fingerprint = "fp"
|
pm.current_fingerprint = "fp"
|
||||||
pm.parent_seed = SEED
|
pm.parent_seed = SEED
|
||||||
pm.nostr_client = SimpleNamespace(publish_snapshot=lambda *a, **k: None)
|
pm.nostr_client = SimpleNamespace(
|
||||||
|
publish_snapshot=lambda *a, **k: (None, "abcd")
|
||||||
|
)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"password_manager.manager.prompt_existing_password", lambda *_: old_pw
|
"password_manager.manager.prompt_existing_password", lambda *_: old_pw
|
||||||
@@ -64,7 +66,9 @@ def test_password_change_and_unlock(monkeypatch):
|
|||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"password_manager.manager.NostrClient",
|
"password_manager.manager.NostrClient",
|
||||||
lambda *a, **kw: SimpleNamespace(publish_snapshot=lambda *a, **k: None),
|
lambda *a, **kw: SimpleNamespace(
|
||||||
|
publish_snapshot=lambda *a, **k: (None, "abcd")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
pm.change_password()
|
pm.change_password()
|
||||||
|
@@ -9,16 +9,17 @@ import main
|
|||||||
|
|
||||||
def test_handle_post_success(capsys):
|
def test_handle_post_success(capsys):
|
||||||
pm = SimpleNamespace(
|
pm = SimpleNamespace(
|
||||||
sync_vault=lambda alt_summary=None: True,
|
sync_vault=lambda alt_summary=None: "abcd",
|
||||||
)
|
)
|
||||||
main.handle_post_to_nostr(pm)
|
main.handle_post_to_nostr(pm)
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "✅ Sync complete." in out
|
assert "✅ Sync complete." in out
|
||||||
|
assert "abcd" in out
|
||||||
|
|
||||||
|
|
||||||
def test_handle_post_failure(capsys):
|
def test_handle_post_failure(capsys):
|
||||||
pm = SimpleNamespace(
|
pm = SimpleNamespace(
|
||||||
sync_vault=lambda alt_summary=None: False,
|
sync_vault=lambda alt_summary=None: None,
|
||||||
)
|
)
|
||||||
main.handle_post_to_nostr(pm)
|
main.handle_post_to_nostr(pm)
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
|
@@ -62,7 +62,10 @@ def test_add_and_delete_entry(monkeypatch):
|
|||||||
|
|
||||||
published = []
|
published = []
|
||||||
pm.nostr_client = SimpleNamespace(
|
pm.nostr_client = SimpleNamespace(
|
||||||
publish_snapshot=lambda data, alt_summary=None: published.append(data)
|
publish_snapshot=lambda data, alt_summary=None: (
|
||||||
|
published.append(data),
|
||||||
|
(None, "abcd"),
|
||||||
|
)[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
inputs = iter([str(index)])
|
inputs = iter([str(index)])
|
||||||
|
@@ -81,8 +81,9 @@ def test_publish_snapshot_success():
|
|||||||
with patch.object(
|
with patch.object(
|
||||||
client.client, "send_event", side_effect=fake_send
|
client.client, "send_event", side_effect=fake_send
|
||||||
) as mock_send:
|
) as mock_send:
|
||||||
manifest = asyncio.run(client.publish_snapshot(b"data"))
|
manifest, event_id = asyncio.run(client.publish_snapshot(b"data"))
|
||||||
assert isinstance(manifest, Manifest)
|
assert isinstance(manifest, Manifest)
|
||||||
|
assert event_id == "abcd"
|
||||||
assert mock_send.await_count >= 1
|
assert mock_send.await_count >= 1
|
||||||
|
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@ def setup_pm(tmp_path, monkeypatch):
|
|||||||
relays=list(DEFAULT_RELAYS),
|
relays=list(DEFAULT_RELAYS),
|
||||||
close_client_pool=lambda: None,
|
close_client_pool=lambda: None,
|
||||||
initialize_client_pool=lambda: None,
|
initialize_client_pool=lambda: None,
|
||||||
publish_snapshot=lambda data, alt_summary=None: None,
|
publish_snapshot=lambda data, alt_summary=None: (None, "abcd"),
|
||||||
key_manager=SimpleNamespace(get_npub=lambda: "npub"),
|
key_manager=SimpleNamespace(get_npub=lambda: "npub"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user