diff --git a/src/main.py b/src/main.py index 6e52519..97fc1e3 100644 --- a/src/main.py +++ b/src/main.py @@ -226,9 +226,14 @@ def handle_post_to_nostr( Handles the action of posting the encrypted password index to Nostr. """ try: - success = password_manager.sync_vault(alt_summary=alt_summary) - if success: - print(colored("\N{WHITE HEAVY CHECK MARK} Sync complete.", "green")) + event_id = password_manager.sync_vault(alt_summary=alt_summary) + if event_id: + print( + colored( + f"\N{WHITE HEAVY CHECK MARK} Sync complete. Event ID: {event_id}", + "green", + ) + ) logging.info("Encrypted index posted to Nostr successfully.") else: print(colored("\N{CROSS MARK} Sync failed…", "red")) diff --git a/src/nostr/client.py b/src/nostr/client.py index c2aee6e..20fd2f3 100644 --- a/src/nostr/client.py +++ b/src/nostr/client.py @@ -142,7 +142,7 @@ class NostrClient: encrypted_json: bytes, to_pubkey: str | None = None, alt_summary: str | None = None, - ) -> bool: + ) -> str | None: """Builds and publishes a Kind 1 text note or direct message. Parameters @@ -177,12 +177,12 @@ class NostrClient: else str(event_output) ) logger.info(f"Successfully published event with ID: {event_id_hex}") - return True + return event_id_hex except Exception as e: self.last_error = str(e) logger.error(f"Failed to publish JSON to Nostr: {e}") - return False + return None def publish_event(self, event): """Publish a prepared event to the configured relays.""" @@ -242,7 +242,7 @@ class NostrClient: async def publish_snapshot( self, encrypted_bytes: bytes, limit: int = 50_000 - ) -> Manifest: + ) -> tuple[Manifest, str]: """Publish a compressed snapshot split into chunks. Parameters @@ -276,10 +276,11 @@ class NostrClient: .build(self.keys.public_key()) .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._delta_events = [] - return manifest + return manifest, manifest_id async def fetch_latest_snapshot(self) -> Tuple[Manifest, list[bytes]] | None: """Retrieve the latest manifest and all snapshot chunks.""" diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index a09fa57..21cfd22 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -1129,26 +1129,26 @@ class PasswordManager: # Re-raise the exception to inform the calling function of the failure 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.""" try: encrypted = self.get_encrypted_data() if not encrypted: - return False + return None pub_snap = getattr(self.nostr_client, "publish_snapshot", None) if callable(pub_snap): if asyncio.iscoroutinefunction(pub_snap): - asyncio.run(pub_snap(encrypted)) + _, event_id = asyncio.run(pub_snap(encrypted)) else: - pub_snap(encrypted) + _, event_id = pub_snap(encrypted) else: # 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 - return True + return event_id except Exception as e: logging.error(f"Failed to sync vault: {e}", exc_info=True) - return False + return None def backup_database(self) -> None: """ diff --git a/src/tests/test_manager_workflow.py b/src/tests/test_manager_workflow.py index abb2bab..009c640 100644 --- a/src/tests/test_manager_workflow.py +++ b/src/tests/test_manager_workflow.py @@ -22,6 +22,7 @@ class FakeNostrClient: def publish_snapshot(self, data: bytes): self.published.append(data) + return None, "abcd" def test_manager_workflow(monkeypatch): diff --git a/src/tests/test_nostr_backup.py b/src/tests/test_nostr_backup.py index b9faca4..0ab10f8 100644 --- a/src/tests/test_nostr_backup.py +++ b/src/tests/test_nostr_backup.py @@ -25,7 +25,7 @@ def test_backup_and_publish_to_nostr(): with patch( "nostr.client.NostrClient.publish_snapshot", - AsyncMock(return_value=None), + AsyncMock(return_value=(None, "abcd")), ) as mock_publish, patch("nostr.client.ClientBuilder"), patch( "nostr.client.KeyManager" ), patch.object( @@ -38,4 +38,4 @@ def test_backup_and_publish_to_nostr(): result = asyncio.run(nostr_client.publish_snapshot(encrypted_index)) mock_publish.assert_awaited_with(encrypted_index) - assert result is None + assert result == (None, "abcd") diff --git a/src/tests/test_nostr_dummy_client.py b/src/tests/test_nostr_dummy_client.py index cd1ab1a..fe4e998 100644 --- a/src/tests/test_nostr_dummy_client.py +++ b/src/tests/test_nostr_dummy_client.py @@ -29,7 +29,7 @@ def test_retrieve_multi_chunk_snapshot(dummy_nostr_client): client, relay = dummy_nostr_client 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 fetched_manifest, chunk_bytes = asyncio.run(client.fetch_latest_snapshot()) 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): client, relay = dummy_nostr_client base = b"base" - manifest = asyncio.run(client.publish_snapshot(base)) + manifest, _ = asyncio.run(client.publish_snapshot(base)) manifest_id = relay.manifests[-1].id d1 = b"d1" d2 = b"d2" diff --git a/src/tests/test_password_change.py b/src/tests/test_password_change.py index 83c0c61..5be68fb 100644 --- a/src/tests/test_password_change.py +++ b/src/tests/test_password_change.py @@ -43,7 +43,7 @@ def test_change_password_triggers_nostr_backup(monkeypatch): with patch("password_manager.manager.NostrClient") as MockClient: 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.change_password() mock_instance.publish_snapshot.assert_called_once() diff --git a/src/tests/test_password_unlock_after_change.py b/src/tests/test_password_unlock_after_change.py index 38e11e2..c548135 100644 --- a/src/tests/test_password_unlock_after_change.py +++ b/src/tests/test_password_unlock_after_change.py @@ -54,7 +54,9 @@ def test_password_change_and_unlock(monkeypatch): pm.fingerprint_dir = fp pm.current_fingerprint = "fp" 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( "password_manager.manager.prompt_existing_password", lambda *_: old_pw @@ -64,7 +66,9 @@ def test_password_change_and_unlock(monkeypatch): ) monkeypatch.setattr( "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() diff --git a/src/tests/test_post_sync_messages.py b/src/tests/test_post_sync_messages.py index 2a4e95e..2491217 100644 --- a/src/tests/test_post_sync_messages.py +++ b/src/tests/test_post_sync_messages.py @@ -9,16 +9,17 @@ import main def test_handle_post_success(capsys): pm = SimpleNamespace( - sync_vault=lambda alt_summary=None: True, + sync_vault=lambda alt_summary=None: "abcd", ) main.handle_post_to_nostr(pm) out = capsys.readouterr().out assert "✅ Sync complete." in out + assert "abcd" in out def test_handle_post_failure(capsys): pm = SimpleNamespace( - sync_vault=lambda alt_summary=None: False, + sync_vault=lambda alt_summary=None: None, ) main.handle_post_to_nostr(pm) out = capsys.readouterr().out diff --git a/src/tests/test_profile_management.py b/src/tests/test_profile_management.py index ff35a7c..de0635b 100644 --- a/src/tests/test_profile_management.py +++ b/src/tests/test_profile_management.py @@ -62,7 +62,10 @@ def test_add_and_delete_entry(monkeypatch): published = [] 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)]) diff --git a/src/tests/test_publish_json_result.py b/src/tests/test_publish_json_result.py index 08f97ce..176c968 100644 --- a/src/tests/test_publish_json_result.py +++ b/src/tests/test_publish_json_result.py @@ -81,8 +81,9 @@ def test_publish_snapshot_success(): with patch.object( client.client, "send_event", side_effect=fake_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 event_id == "abcd" assert mock_send.await_count >= 1 diff --git a/src/tests/test_settings_menu.py b/src/tests/test_settings_menu.py index 668cc04..4bf2cb1 100644 --- a/src/tests/test_settings_menu.py +++ b/src/tests/test_settings_menu.py @@ -33,7 +33,7 @@ def setup_pm(tmp_path, monkeypatch): relays=list(DEFAULT_RELAYS), close_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"), )