commit c0ba705ff19c5951ab5ed84dc0e2d4cf5af7c577
parent e86ac0f3532d0513a8f61ec05287bec2ac0fd5cc
Author: triesap <tyson@radroots.org>
Date: Thu, 9 Jul 2026 01:20:04 +0000
tests: cover Hybrid product publish satisfaction
- add listing Hybrid publish coverage for default AllTargets
- add farm Hybrid publish coverage for default AllTargets
- assert Reticulum preview targets remain stored and unattempted
- verify focused listing and farm runtime regressions
Diffstat:
2 files changed, 192 insertions(+), 9 deletions(-)
diff --git a/crates/sdk/tests/farms_runtime.rs b/crates/sdk/tests/farms_runtime.rs
@@ -11,17 +11,21 @@ use radroots_events::{
ids::RadrootsAddressableCoordinate,
kinds::{KIND_FARM, KIND_PROFILE},
};
-use radroots_outbox::{RadrootsOutbox, RadrootsOutboxEventState};
+use radroots_outbox::{
+ RadrootsOutbox, RadrootsOutboxDeliveryPlanStatus, RadrootsOutboxDeliveryTargetStatus,
+ RadrootsOutboxEventState,
+};
use radroots_sdk::{
FARM_PUBLISH_OPERATION_KIND, FarmEnqueuePublishRequest, FarmPreparePublishRequest,
FarmPrivateLocationClearRequest, FarmPrivateLocationInput, FarmPrivateLocationLookupCandidate,
FarmPrivateLocationLookupReceipt, FarmPrivateLocationReceipt, FarmPrivateLocationSetRequest,
FarmPrivateLocationSetResult, FarmPrivateLocationUpsertRequest, Geocoder,
- GeocoderLocalityQuery, NostrProfile, NostrRelayUrlPolicy, PushOutboxEventState,
+ GeocoderLocalityQuery, HybridProfile, NostrProfile, NostrRelayUrlPolicy, PushOutboxEventState,
PushOutboxRequest, PushOutboxTargetOutcomeKind, RadrootsClient, RadrootsSdkError,
RadrootsSdkErrorClass, RadrootsSdkGeoNamesErrorKind, RadrootsSdkRecoveryAction,
- RadrootsSdkTimestamp, SdkExactLocation, SdkIdempotencyKey, SdkMutationState, SdkPublicLocality,
- StorageStatusRequest, TargetPolicy, TargetSet, TransportProfile,
+ RadrootsSdkTimestamp, ReticulumPreviewProfile, SdkExactLocation, SdkIdempotencyKey,
+ SdkMutationState, SdkPublicLocality, StorageStatusRequest, TargetPolicy, TargetSet,
+ TransportProfile,
};
use radroots_transport_nostr::RadrootsMockRelayPublishAdapter;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
@@ -148,6 +152,21 @@ async fn directory_sdk_with_relays(relays: &[&str]) -> (tempfile::TempDir, Radro
(tempdir, sdk)
}
+async fn hybrid_directory_sdk() -> (tempfile::TempDir, RadrootsClient) {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ let sdk = RadrootsClient::builder()
+ .directory_storage(tempdir.path().join("sdk"))
+ .fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000))
+ .transport_profile(TransportProfile::hybrid(HybridProfile::new(
+ NostrProfile::new([RELAY], NostrRelayUrlPolicy::Public).expect("Nostr profile"),
+ ReticulumPreviewProfile::preview_unavailable(),
+ )))
+ .build()
+ .await
+ .expect("sdk");
+ (tempdir, sdk)
+}
+
async fn fixture_geocoder(tempdir: &tempfile::TempDir) -> Geocoder {
let path = tempdir.path().join("geonames-fixture.db");
let options = SqliteConnectOptions::new()
@@ -827,6 +846,78 @@ async fn farm_enqueue_publish_pushes_queued_event_with_mock_relay_sync() {
}
#[tokio::test]
+async fn farm_hybrid_profile_publishes_after_nostr_success_and_retains_reticulum_preview() {
+ let (_tempdir, sdk) = hybrid_directory_sdk().await;
+ let enqueue_receipt = sdk
+ .farms()
+ .enqueue_publish_with_explicit_signer(
+ FarmEnqueuePublishRequest::new(
+ farmer_actor(),
+ farm(FARM_E_D_TAG, "Hybrid Farm"),
+ TargetPolicy::use_transport_profile(),
+ ),
+ &FixtureSigner::new(FARMER),
+ )
+ .await
+ .expect("enqueue");
+ let adapter = RadrootsMockRelayPublishAdapter::new();
+
+ let push_receipt = sdk
+ .sync()
+ .push_outbox_with_adapter(&adapter, PushOutboxRequest::new().with_limit(1))
+ .await
+ .expect("push");
+
+ assert_eq!(push_receipt.attempted_events, 1);
+ assert_eq!(push_receipt.published_events, 1);
+ assert_eq!(push_receipt.retryable_events, 0);
+ assert_eq!(push_receipt.terminal_events, 0);
+ let event = &push_receipt.events[0];
+ assert_eq!(event.outbox_event_id, enqueue_receipt.outbox_event_id);
+ assert_eq!(event.final_state, PushOutboxEventState::Published);
+ assert_eq!(event.quorum, 1);
+ assert!(event.quorum_met);
+ assert_eq!(event.targets.len(), 1);
+ assert_eq!(event.targets[0].endpoint_uri, RELAY);
+ assert_eq!(
+ event.targets[0].outcome_kind,
+ PushOutboxTargetOutcomeKind::Accepted
+ );
+ assert_eq!(adapter.captured_raw_events().len(), 1);
+
+ let outbox = RadrootsOutbox::open_file(&sdk.storage_paths().expect("paths").outbox_path)
+ .await
+ .expect("outbox");
+ let stored = outbox
+ .get_event(enqueue_receipt.outbox_event_id)
+ .await
+ .expect("stored")
+ .expect("stored");
+ assert_eq!(stored.state, RadrootsOutboxEventState::Published);
+ let plans = outbox
+ .delivery_plans(enqueue_receipt.outbox_event_id)
+ .await
+ .expect("delivery plans");
+ assert_eq!(plans.len(), 1);
+ assert_eq!(plans[0].required_success_count, 1);
+ assert_eq!(plans[0].status, RadrootsOutboxDeliveryPlanStatus::Complete);
+ let targets = outbox
+ .delivery_targets(enqueue_receipt.outbox_event_id)
+ .await
+ .expect("delivery targets");
+ assert_eq!(targets.len(), 2);
+ assert!(targets.iter().any(|target| {
+ target.endpoint_uri.to_string() == RELAY
+ && target.status == RadrootsOutboxDeliveryTargetStatus::Accepted
+ }));
+ assert!(targets.iter().any(|target| {
+ target.endpoint_uri.to_string() == "reticulum:preview-unavailable"
+ && target.status == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ && target.attempt_count == 0
+ }));
+}
+
+#[tokio::test]
async fn farm_enqueue_publish_reports_preflight_idempotency_conflict_without_mutation() {
let (_tempdir, sdk) = directory_sdk().await;
let first = FarmEnqueuePublishRequest::new(
diff --git a/crates/sdk/tests/listings_runtime.rs b/crates/sdk/tests/listings_runtime.rs
@@ -16,14 +16,19 @@ use radroots_events::{
kinds::KIND_LISTING,
listing::{RadrootsListing, RadrootsListingBin, RadrootsListingProduct},
};
-use radroots_outbox::{RadrootsOutbox, RadrootsOutboxEventState};
+use radroots_outbox::{
+ RadrootsOutbox, RadrootsOutboxDeliveryPlanStatus, RadrootsOutboxDeliveryTargetStatus,
+ RadrootsOutboxEventState,
+};
use radroots_sdk::{
- LISTING_PUBLISH_OPERATION_KIND, ListingEnqueuePublishRequest, ListingPreparePublishRequest,
- NostrProfile, NostrRelayUrlPolicy, RadrootsClient, RadrootsSdkError, RadrootsSdkRecoveryAction,
- RadrootsSdkTimestamp, SdkIdempotencyKey, SdkMutationState, TargetPolicy, TargetSet,
- TransportProfile,
+ HybridProfile, LISTING_PUBLISH_OPERATION_KIND, ListingEnqueuePublishRequest,
+ ListingPreparePublishRequest, NostrProfile, NostrRelayUrlPolicy, PushOutboxEventState,
+ PushOutboxRequest, PushOutboxTargetOutcomeKind, RadrootsClient, RadrootsSdkError,
+ RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, ReticulumPreviewProfile, SdkIdempotencyKey,
+ SdkMutationState, TargetPolicy, TargetSet, TransportProfile,
};
use radroots_trade::listing::RadrootsListingDraftDocumentV1;
+use radroots_transport_nostr::RadrootsMockRelayPublishAdapter;
#[path = "support/serializer_failure.rs"]
mod serializer_failure;
@@ -181,6 +186,21 @@ async fn directory_sdk_with_relays(relays: &[&str]) -> (tempfile::TempDir, Radro
(tempdir, sdk)
}
+async fn hybrid_directory_sdk() -> (tempfile::TempDir, RadrootsClient) {
+ let tempdir = tempfile::tempdir().expect("tempdir");
+ let sdk = RadrootsClient::builder()
+ .directory_storage(tempdir.path().join("sdk"))
+ .fixed_clock(RadrootsSdkTimestamp::from_unix_seconds(1_700_000_000))
+ .transport_profile(TransportProfile::hybrid(HybridProfile::new(
+ NostrProfile::new([RELAY], NostrRelayUrlPolicy::Public).expect("Nostr profile"),
+ ReticulumPreviewProfile::preview_unavailable(),
+ )))
+ .build()
+ .await
+ .expect("sdk");
+ (tempdir, sdk)
+}
+
#[tokio::test]
async fn prepare_publish_is_side_effect_free() {
let (_tempdir, sdk) = directory_sdk().await;
@@ -788,3 +808,75 @@ async fn enqueue_publish_derives_order_independent_idempotency_key() {
.collect::<Vec<_>>();
assert_eq!(relay_urls, vec![RELAY_B.to_owned(), RELAY.to_owned()]);
}
+
+#[tokio::test]
+async fn listing_hybrid_profile_publishes_after_nostr_success_and_retains_reticulum_preview() {
+ let (_tempdir, sdk) = hybrid_directory_sdk().await;
+ let enqueue_receipt = sdk
+ .listings()
+ .enqueue_publish_with_explicit_signer(
+ ListingEnqueuePublishRequest::new(
+ actor(),
+ listing(LISTING_G_D_TAG, "Hybrid Coffee"),
+ TargetPolicy::use_transport_profile(),
+ ),
+ &FixtureSigner::new(SELLER),
+ )
+ .await
+ .expect("enqueue");
+ let adapter = RadrootsMockRelayPublishAdapter::new();
+
+ let push_receipt = sdk
+ .sync()
+ .push_outbox_with_adapter(&adapter, PushOutboxRequest::new().with_limit(1))
+ .await
+ .expect("push");
+
+ assert_eq!(push_receipt.attempted_events, 1);
+ assert_eq!(push_receipt.published_events, 1);
+ assert_eq!(push_receipt.retryable_events, 0);
+ assert_eq!(push_receipt.terminal_events, 0);
+ let event = &push_receipt.events[0];
+ assert_eq!(event.outbox_event_id, enqueue_receipt.outbox_event_id);
+ assert_eq!(event.final_state, PushOutboxEventState::Published);
+ assert_eq!(event.quorum, 1);
+ assert!(event.quorum_met);
+ assert_eq!(event.targets.len(), 1);
+ assert_eq!(event.targets[0].endpoint_uri, RELAY);
+ assert_eq!(
+ event.targets[0].outcome_kind,
+ PushOutboxTargetOutcomeKind::Accepted
+ );
+ assert_eq!(adapter.captured_raw_events().len(), 1);
+
+ let outbox = RadrootsOutbox::open_file(&sdk.storage_paths().expect("paths").outbox_path)
+ .await
+ .expect("outbox");
+ let stored = outbox
+ .get_event(enqueue_receipt.outbox_event_id)
+ .await
+ .expect("stored")
+ .expect("stored");
+ assert_eq!(stored.state, RadrootsOutboxEventState::Published);
+ let plans = outbox
+ .delivery_plans(enqueue_receipt.outbox_event_id)
+ .await
+ .expect("delivery plans");
+ assert_eq!(plans.len(), 1);
+ assert_eq!(plans[0].required_success_count, 1);
+ assert_eq!(plans[0].status, RadrootsOutboxDeliveryPlanStatus::Complete);
+ let targets = outbox
+ .delivery_targets(enqueue_receipt.outbox_event_id)
+ .await
+ .expect("delivery targets");
+ assert_eq!(targets.len(), 2);
+ assert!(targets.iter().any(|target| {
+ target.endpoint_uri.to_string() == RELAY
+ && target.status == RadrootsOutboxDeliveryTargetStatus::Accepted
+ }));
+ assert!(targets.iter().any(|target| {
+ target.endpoint_uri.to_string() == "reticulum:preview-unavailable"
+ && target.status == RadrootsOutboxDeliveryTargetStatus::PreviewUnavailable
+ && target.attempt_count == 0
+ }));
+}