commit 91e3b1f224cbc0ed4fe9ba7566b03e61ea781cd1
parent 5e5a352931239d13995790f0095d07d0c3c22f80
Author: triesap <tyson@radroots.org>
Date: Tue, 16 Jun 2026 00:15:38 -0700
sdk: use local-dev relay policy
Diffstat:
2 files changed, 85 insertions(+), 3 deletions(-)
diff --git a/crates/sdk/src/sync_runtime.rs b/crates/sdk/src/sync_runtime.rs
@@ -9,7 +9,8 @@ use radroots_relay_transport::RadrootsNostrClientPublishAdapter;
#[cfg(feature = "runtime")]
use radroots_relay_transport::{
RadrootsOutboxPublishPolicy, RadrootsRelayOutcomeKind, RadrootsRelayPublishAdapter,
- RadrootsRelayPublishReceipt, RadrootsRelayPublishRelayReceipt, publish_claimed_outbox_event,
+ RadrootsRelayPublishReceipt, RadrootsRelayPublishRelayReceipt, RadrootsRelayUrlPolicy,
+ publish_claimed_outbox_event,
};
#[cfg(feature = "runtime")]
@@ -234,7 +235,8 @@ impl<'sdk> SyncClient<'sdk> {
};
let policy =
RadrootsOutboxPublishPolicy::new(now_ms.saturating_add(NEXT_ATTEMPT_DELAY_MS))
- .republish_accepted_relays(request.republish_accepted_relays);
+ .republish_accepted_relays(request.republish_accepted_relays)
+ .relay_url_policy(RadrootsRelayUrlPolicy::LocalDev);
let publish = publish_claimed_outbox_event(
&self.sdk._outbox,
&self.sdk._event_store,
diff --git a/crates/sdk/tests/sync_runtime.rs b/crates/sdk/tests/sync_runtime.rs
@@ -25,6 +25,7 @@ use radroots_sdk::{
PUSH_OUTBOX_MAX_LIMIT, PushOutboxEventState, PushOutboxRelayOutcomeKind, PushOutboxRequest,
RadrootsSdk, RadrootsSdkError, RadrootsSdkTimestamp, SdkRelayTargetPolicy, SdkRelayUrlPolicy,
};
+use std::collections::BTreeSet;
const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const FARM_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAA";
@@ -34,6 +35,10 @@ const LISTING_C_D_TAG: &str = "AAAAAAAAAAAAAAAAAAAAAw";
const RELAY_A: &str = "wss://relay-a.example.com";
const RELAY_B: &str = "wss://relay-b.example.com";
const RELAY_C: &str = "wss://relay-c.example.com";
+const LOCAL_RELAY_A: &str = "ws://localhost:8080";
+const LOCAL_RELAY_B: &str = "ws://127.0.0.1:8081";
+const LOCAL_RELAY_C: &str = "ws://[::1]:8082";
+const NONLOCAL_WS_RELAY: &str = "ws://relay.example.com";
#[derive(Clone)]
struct FixtureSigner {
@@ -175,6 +180,16 @@ async fn directory_sdk(relays: &[&str]) -> (tempfile::TempDir, RadrootsSdk) {
}
async fn enqueue_listing(sdk: &RadrootsSdk, d_tag: &str, title: &str, relays: &[&str]) -> i64 {
+ enqueue_listing_with_policy(sdk, d_tag, title, relays, SdkRelayUrlPolicy::Public).await
+}
+
+async fn enqueue_listing_with_policy(
+ sdk: &RadrootsSdk,
+ d_tag: &str,
+ title: &str,
+ relays: &[&str],
+ url_policy: SdkRelayUrlPolicy,
+) -> i64 {
sdk.listings()
.enqueue_publish(
ListingEnqueuePublishRequest::new(
@@ -182,7 +197,7 @@ async fn enqueue_listing(sdk: &RadrootsSdk, d_tag: &str, title: &str, relays: &[
listing(d_tag, title),
SdkRelayTargetPolicy::UseConfiguredRelays,
)
- .try_with_target_relays(relays, SdkRelayUrlPolicy::Public)
+ .try_with_target_relays(relays, url_policy)
.expect("relay targets"),
&FixtureSigner::new(SELLER),
)
@@ -311,6 +326,71 @@ async fn push_outbox_with_adapter_uses_queued_targets_without_builder_relays() {
}
#[tokio::test]
+async fn push_outbox_with_adapter_accepts_queued_localhost_ws_targets() {
+ let (_tempdir, sdk) = directory_sdk(&[]).await;
+ let outbox_event_id = enqueue_listing_with_policy(
+ &sdk,
+ LISTING_A_D_TAG,
+ "Local Coffee",
+ &[LOCAL_RELAY_A, LOCAL_RELAY_B, LOCAL_RELAY_C],
+ SdkRelayUrlPolicy::Localhost,
+ )
+ .await;
+ let adapter = RadrootsMockRelayPublishAdapter::new();
+
+ let receipt = sdk
+ .sync()
+ .push_outbox_with_adapter(&adapter, PushOutboxRequest::new().with_limit(1))
+ .await
+ .expect("push");
+
+ assert_eq!(receipt.attempted_events, 1);
+ assert_eq!(receipt.published_events, 1);
+ assert_eq!(receipt.retryable_events, 0);
+ assert_eq!(receipt.terminal_events, 0);
+ assert_eq!(receipt.events.len(), 1);
+ let event = &receipt.events[0];
+ assert_eq!(event.outbox_event_id, outbox_event_id);
+ assert_eq!(event.final_state, PushOutboxEventState::Published);
+ assert_eq!(event.attempted_count, 3);
+ assert_eq!(event.accepted_count, 3);
+ assert_eq!(event.retryable_count, 0);
+ assert_eq!(event.terminal_count, 0);
+ assert_eq!(event.quorum, 3);
+ assert!(event.quorum_met);
+ assert_eq!(event.relays.len(), 3);
+ assert!(
+ event
+ .relays
+ .iter()
+ .all(|relay| relay.outcome_kind == PushOutboxRelayOutcomeKind::Accepted)
+ );
+ let relay_urls = event
+ .relays
+ .iter()
+ .map(|relay| relay.relay_url.as_str())
+ .collect::<BTreeSet<_>>();
+ assert_eq!(
+ relay_urls,
+ BTreeSet::from([LOCAL_RELAY_A, LOCAL_RELAY_B, LOCAL_RELAY_C])
+ );
+ assert_eq!(adapter.captured_raw_events().len(), 1);
+}
+
+#[test]
+fn enqueue_publish_rejects_nonlocal_ws_relay_targets() {
+ let error = ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(LISTING_C_D_TAG, "Nonlocal Coffee"),
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_target_relays([NONLOCAL_WS_RELAY], SdkRelayUrlPolicy::Localhost)
+ .expect_err("nonlocal ws relay target");
+
+ assert!(matches!(error, RadrootsSdkError::InvalidRelayUrl { .. }));
+}
+
+#[tokio::test]
async fn push_outbox_preserves_retryable_and_terminal_relay_outcomes() {
let (_tempdir, sdk) = directory_sdk(&[RELAY_A, RELAY_B, RELAY_C]).await;
enqueue_listing(