myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

commit 218689b4e5bc62c53cef615d2265d9c0460f2de2
parent 55580439c805c02f8c0b8267b52e8910f2b8d1e8
Author: triesap <tyson@radroots.org>
Date:   Thu, 26 Mar 2026 14:12:48 +0000

tests: harden external nostr response polling

Diffstat:
Mtests/nip46_e2e.rs | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 63 insertions(+), 4 deletions(-)

diff --git a/tests/nip46_e2e.rs b/tests/nip46_e2e.rs @@ -614,6 +614,16 @@ fn build_external_request_event( .expect("sign external request event") } +fn build_signer_noise_event(signer_identity: &RadrootsIdentity, created_at_unix: u64) -> Event { + EventBuilder::new( + Kind::Custom(RADROOTS_NOSTR_CONNECT_RPC_KIND), + "non-nip44-signer-noise", + ) + .custom_created_at(Timestamp::from(created_at_unix)) + .sign_with_keys(signer_identity.keys()) + .expect("sign noise event") +} + fn decrypt_response( client_identity: &RadrootsIdentity, signer_public_key: PublicKey, @@ -635,16 +645,20 @@ async fn wait_for_external_response( request_id: &str, method: ExternalNostrConnectMethod, ) -> TestResult<(Event, ExternalNostrConnectResponse)> { - timeout(Duration::from_secs(5), async { + timeout(Duration::from_secs(10), async { loop { let events = relay.published_events_by_author(signer_public_key).await; for event in events { - let plaintext = nip44::decrypt( + let Ok(plaintext) = nip44::decrypt( client_identity.keys().secret_key(), &signer_public_key, &event.content, - )?; - let message = ExternalNostrConnectMessage::from_json(&plaintext)?; + ) else { + continue; + }; + let Ok(message) = ExternalNostrConnectMessage::from_json(&plaintext) else { + continue; + }; if message.id() != request_id { continue; } @@ -1206,6 +1220,51 @@ async fn external_nostr_client_surfaces_auth_challenge_state() -> TestResult<()> } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn external_nostr_client_ignores_unrelated_signer_events_before_response() -> TestResult<()> { + let relay = TestRelay::spawn().await?; + let test_runtime = MycTestRuntime::new(relay.url(), MycConnectionApproval::NotRequired); + let runtime = test_runtime.runtime.clone(); + let signer_identity = runtime.signer_identity(); + let signer_public_key = signer_identity.public_key(); + let client_identity = + identity("5656565656565656565656565656565656565656565656565656565656565656"); + let base_created_at = Timestamp::now().as_secs(); + + register_external_client_session(&runtime, client_identity.public_key(), relay.url(), "")?; + + let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>(); + let service_runtime = runtime.clone(); + let listener_task = tokio::spawn(async move { + service_runtime + .run_until(async { + let _ = shutdown_rx.await; + }) + .await + }); + + relay.wait_for_subscription_count(1).await?; + + let noise_event = build_signer_noise_event(&signer_identity, base_created_at); + publish_event(relay.url(), &noise_event).await?; + + let (_, ping_response) = publish_external_request_and_wait_for_response( + &relay, + &client_identity, + signer_public_key, + "external-noise-ping", + ExternalNostrConnectRequest::Ping, + base_created_at + 1, + ) + .await?; + assert_eq!(ping_response.result, Some(ExternalResponseResult::Pong)); + assert_eq!(ping_response.error, None); + + let _ = shutdown_tx.send(()); + listener_task.await??; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn live_listener_consumes_connect_secret_only_after_successful_publish() -> TestResult<()> { let relay = TestRelay::spawn().await?; let test_runtime = MycTestRuntime::new(relay.url(), MycConnectionApproval::NotRequired);