commit a78f2edbca35dfb2749d4e79564774da0731ea27
parent 9e6b3cb1c9cbff9a0380eef4c17a987fe38f5744
Author: triesap <tyson@radroots.org>
Date: Sun, 22 Mar 2026 12:09:51 +0000
tests: prove discovery publish retries
- add a relay-backed test for rejected NIP-89 publication followed by a successful retry
- assert that rejected publishes do not emit events and are recorded as discovery handler audit failures
- assert that the retry publishes exactly once and records a succeeding discovery handler audit entry
- validate with cargo test --locked --test nip46_e2e explicit_nip89_publish_retries_cleanly_after_rejection and cargo test --locked
Diffstat:
| M | tests/nip46_e2e.rs | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 75 insertions(+), 0 deletions(-)
diff --git a/tests/nip46_e2e.rs b/tests/nip46_e2e.rs
@@ -977,3 +977,78 @@ async fn explicit_nip89_publish_uses_app_identity_and_records_audit() -> TestRes
Ok(())
}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn explicit_nip89_publish_retries_cleanly_after_rejection() -> TestResult<()> {
+ let relay = TestRelay::spawn().await?;
+ let test_runtime =
+ MycTestRuntime::new_with_discovery(relay.url(), MycConnectionApproval::ExplicitUser);
+ let runtime = test_runtime.runtime;
+ let app_identity = RadrootsIdentity::load_from_path_auto(
+ runtime
+ .config()
+ .discovery
+ .app_identity_path
+ .as_ref()
+ .expect("app identity path"),
+ )?;
+
+ relay
+ .queue_publish_outcomes(app_identity.public_key(), &[false, true])
+ .await;
+
+ let failed = publish_nip89_event(&runtime)
+ .await
+ .expect_err("first publish should fail");
+ assert!(failed.to_string().contains("Nostr publish failed"));
+ assert!(
+ relay
+ .published_events_by_author(app_identity.public_key())
+ .await
+ .is_empty()
+ );
+
+ let first_audit = wait_for_operation_audit_count(&runtime, 1).await?;
+ assert_eq!(
+ first_audit[0].operation,
+ MycOperationAuditKind::DiscoveryHandlerPublish
+ );
+ assert_eq!(first_audit[0].outcome, MycOperationAuditOutcome::Rejected);
+ assert!(first_audit[0].connection_id.is_none());
+ assert!(first_audit[0].request_id.is_some());
+ assert_eq!(first_audit[0].relay_count, 1);
+ assert_eq!(first_audit[0].acknowledged_relay_count, 0);
+ assert!(
+ first_audit[0]
+ .relay_outcome_summary
+ .contains("blocked by test relay")
+ );
+
+ let published = publish_nip89_event(&runtime).await?;
+ let published_events = relay
+ .wait_for_published_events_by_author(app_identity.public_key(), 1)
+ .await?;
+ assert_eq!(published_events.len(), 1);
+ assert_eq!(published.relay_count, 1);
+ assert_eq!(published.acknowledged_relay_count, 1);
+
+ let second_audit = wait_for_operation_audit_count(&runtime, 2).await?;
+ assert_eq!(
+ second_audit[1].operation,
+ MycOperationAuditKind::DiscoveryHandlerPublish
+ );
+ assert_eq!(second_audit[1].outcome, MycOperationAuditOutcome::Succeeded);
+ assert_eq!(
+ second_audit[1].request_id.as_deref(),
+ Some(published.event.id.to_hex().as_str())
+ );
+ assert_eq!(second_audit[1].relay_count, 1);
+ assert_eq!(second_audit[1].acknowledged_relay_count, 1);
+ assert!(
+ second_audit[1]
+ .relay_outcome_summary
+ .contains("1/1 relays acknowledged publish")
+ );
+
+ Ok(())
+}