commit e6db188c273003e1e81279d66de48eab25d67bda
parent 97d674470771d6a3526a8f6457c6258d150cc9f3
Author: triesap <tyson@radroots.org>
Date: Tue, 23 Jun 2026 02:38:09 +0000
orders: cover runtime branch surfaces
- exercise revision and cancellation request contracts
- pin complete order issue status mapping
- cover order facade success and error wrappers
- raise measured order runtime coverage
Diffstat:
2 files changed, 742 insertions(+), 19 deletions(-)
diff --git a/crates/sdk/tests/facade.rs b/crates/sdk/tests/facade.rs
@@ -3,19 +3,25 @@ use radroots_core::{
RadrootsCoreQuantityPrice, RadrootsCoreUnit,
};
use radroots_events::farm::{RadrootsFarm, RadrootsFarmRef};
-use radroots_events::ids::RadrootsPublicKey;
-use radroots_events::kinds::{KIND_FARM, KIND_LISTING, KIND_ORDER_REQUEST, KIND_PROFILE};
+use radroots_events::ids::{RadrootsEventId, RadrootsPublicKey};
+use radroots_events::kinds::{
+ KIND_FARM, KIND_LISTING, KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST,
+ KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, KIND_PROFILE,
+};
use radroots_events::listing::{
RadrootsListing, RadrootsListingAvailability, RadrootsListingBin,
RadrootsListingDeliveryMethod, RadrootsListingLocation, RadrootsListingProduct,
RadrootsListingStatus,
};
use radroots_events::order::{
- RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderItem,
- RadrootsOrderPricingBasis, RadrootsOrderRequest,
+ RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome,
+ RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderInventoryCommitment,
+ RadrootsOrderItem, RadrootsOrderPricingBasis, RadrootsOrderRequest,
+ RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal,
};
use radroots_events::profile::{RadrootsProfile, RadrootsProfileType};
use radroots_sdk::protocol::events::{RadrootsNostrEvent, RadrootsNostrEventPtr};
+use radroots_sdk::protocol::wire::WireEventParts;
use radroots_sdk::protocol::{farm, listing, order, profile};
fn sample_profile() -> RadrootsProfile {
@@ -136,6 +142,13 @@ fn public_key(character: char) -> RadrootsPublicKey {
.expect("public key")
}
+fn event_id(character: char) -> RadrootsEventId {
+ core::iter::repeat_n(character, 64)
+ .collect::<String>()
+ .parse()
+ .expect("event id")
+}
+
fn sample_order_request() -> RadrootsOrderRequest {
let seller_pubkey = public_key('a');
@@ -189,6 +202,91 @@ fn sample_order_request() -> RadrootsOrderRequest {
}
}
+fn sample_order_decision() -> RadrootsOrderDecision {
+ let request = sample_order_request();
+
+ RadrootsOrderDecision {
+ order_id: request.order_id,
+ listing_addr: request.listing_addr,
+ buyer_pubkey: request.buyer_pubkey,
+ seller_pubkey: request.seller_pubkey,
+ decision: RadrootsOrderDecisionOutcome::Accepted {
+ inventory_commitments: vec![RadrootsOrderInventoryCommitment {
+ bin_id: "bin-1".parse().expect("bin id"),
+ bin_count: 2,
+ }],
+ },
+ }
+}
+
+fn sample_order_revision_proposal(
+ root_event_id: &RadrootsEventId,
+ prev_event_id: &RadrootsEventId,
+) -> RadrootsOrderRevisionProposal {
+ let request = sample_order_request();
+
+ RadrootsOrderRevisionProposal {
+ revision_id: "revision-1".parse().expect("revision id"),
+ order_id: request.order_id,
+ listing_addr: request.listing_addr,
+ buyer_pubkey: request.buyer_pubkey,
+ seller_pubkey: request.seller_pubkey,
+ root_event_id: root_event_id.clone(),
+ prev_event_id: prev_event_id.clone(),
+ items: vec![RadrootsOrderItem {
+ bin_id: "bin-1".parse().expect("bin id"),
+ bin_count: 2,
+ }],
+ economics: request.economics,
+ reason: "more quantity".into(),
+ }
+}
+
+fn sample_order_revision_decision(
+ proposal: &RadrootsOrderRevisionProposal,
+ prev_event_id: &RadrootsEventId,
+) -> RadrootsOrderRevisionDecision {
+ RadrootsOrderRevisionDecision {
+ revision_id: proposal.revision_id.clone(),
+ order_id: proposal.order_id.clone(),
+ listing_addr: proposal.listing_addr.clone(),
+ buyer_pubkey: proposal.buyer_pubkey.clone(),
+ seller_pubkey: proposal.seller_pubkey.clone(),
+ root_event_id: proposal.root_event_id.clone(),
+ prev_event_id: prev_event_id.clone(),
+ decision: RadrootsOrderRevisionOutcome::Accepted,
+ }
+}
+
+fn sample_order_cancellation() -> RadrootsOrderCancellation {
+ let request = sample_order_request();
+
+ RadrootsOrderCancellation {
+ order_id: request.order_id,
+ listing_addr: request.listing_addr,
+ buyer_pubkey: request.buyer_pubkey,
+ seller_pubkey: request.seller_pubkey,
+ reason: "buyer changed plan".into(),
+ }
+}
+
+fn order_event_from_parts(
+ id_character: char,
+ author: String,
+ created_at: u32,
+ parts: WireEventParts,
+) -> RadrootsNostrEvent {
+ RadrootsNostrEvent {
+ id: core::iter::repeat_n(id_character, 64).collect(),
+ author,
+ created_at,
+ kind: parts.kind,
+ tags: parts.tags,
+ content: parts.content,
+ sig: String::new(),
+ }
+}
+
#[test]
fn profile_build_draft_wraps_profile_encoder() {
let parts =
@@ -251,20 +349,152 @@ fn order_facade_wraps_build_parse_and_address_ops() {
order::build_order_request_draft(&listing_event_ptr(), &payload).expect("order draft");
assert_eq!(parts.as_wire_parts().kind, KIND_ORDER_REQUEST);
+ assert_eq!(parts.clone().into_wire_parts().kind, KIND_ORDER_REQUEST);
let parsed_addr = order::parse_listing_address(&listing_addr).expect("listing address");
assert_eq!(parsed_addr, listing_addr);
- let event = RadrootsNostrEvent {
- id: core::iter::repeat_n('b', 64).collect(),
- author: payload.buyer_pubkey.to_string(),
- created_at: 2,
- kind: parts.as_wire_parts().kind,
- tags: parts.as_wire_parts().tags.clone(),
- content: parts.as_wire_parts().content.clone(),
- sig: String::new(),
- };
+ let event = order_event_from_parts(
+ 'b',
+ payload.buyer_pubkey.to_string(),
+ 2,
+ parts.into_wire_parts(),
+ );
let envelope = order::parse_order_request(&event).expect("order envelope");
assert_eq!(envelope.payload.order_id, payload.order_id);
assert_eq!(envelope.payload.listing_addr, listing_addr);
+
+ let root_event_id = event_id('c');
+ let previous_event_id = event_id('d');
+ let decision = sample_order_decision();
+ let decision_parts =
+ order::build_order_decision_draft(&root_event_id, &root_event_id, &decision)
+ .expect("decision draft");
+ assert_eq!(decision_parts.as_wire_parts().kind, KIND_ORDER_DECISION);
+ assert_eq!(
+ decision_parts.clone().into_wire_parts().kind,
+ KIND_ORDER_DECISION
+ );
+ let decision_event = order_event_from_parts(
+ 'e',
+ decision.seller_pubkey.to_string(),
+ 3,
+ decision_parts.into_wire_parts(),
+ );
+ let decision_envelope =
+ order::parse_order_decision(&decision_event).expect("decision envelope");
+ assert_eq!(decision_envelope.payload.order_id, decision.order_id);
+
+ let proposal = sample_order_revision_proposal(&root_event_id, &previous_event_id);
+ let proposal_parts =
+ order::build_order_revision_proposal_draft(&root_event_id, &previous_event_id, &proposal)
+ .expect("proposal draft");
+ assert_eq!(
+ proposal_parts.as_wire_parts().kind,
+ KIND_ORDER_REVISION_PROPOSAL
+ );
+ assert_eq!(
+ proposal_parts.clone().into_wire_parts().kind,
+ KIND_ORDER_REVISION_PROPOSAL
+ );
+ let proposal_event = order_event_from_parts(
+ 'f',
+ proposal.seller_pubkey.to_string(),
+ 4,
+ proposal_parts.into_wire_parts(),
+ );
+ let proposal_envelope =
+ order::parse_order_revision_proposal(&proposal_event).expect("proposal envelope");
+ assert_eq!(proposal_envelope.payload.revision_id, proposal.revision_id);
+
+ let revision_decision = sample_order_revision_decision(&proposal, &previous_event_id);
+ let revision_decision_parts = order::build_order_revision_decision_draft(
+ &root_event_id,
+ &previous_event_id,
+ &revision_decision,
+ )
+ .expect("revision decision draft");
+ assert_eq!(
+ revision_decision_parts.as_wire_parts().kind,
+ KIND_ORDER_REVISION_DECISION
+ );
+ assert_eq!(
+ revision_decision_parts.clone().into_wire_parts().kind,
+ KIND_ORDER_REVISION_DECISION
+ );
+ let revision_decision_event = order_event_from_parts(
+ '0',
+ revision_decision.buyer_pubkey.to_string(),
+ 5,
+ revision_decision_parts.into_wire_parts(),
+ );
+ let revision_decision_envelope = order::parse_order_revision_decision(&revision_decision_event)
+ .expect("revision decision envelope");
+ assert_eq!(
+ revision_decision_envelope.payload.revision_id,
+ revision_decision.revision_id
+ );
+
+ let cancellation = sample_order_cancellation();
+ let cancellation_parts =
+ order::build_order_cancellation_draft(&root_event_id, &previous_event_id, &cancellation)
+ .expect("cancellation draft");
+ assert_eq!(
+ cancellation_parts.as_wire_parts().kind,
+ KIND_ORDER_CANCELLATION
+ );
+ assert_eq!(
+ cancellation_parts.clone().into_wire_parts().kind,
+ KIND_ORDER_CANCELLATION
+ );
+ let cancellation_event = order_event_from_parts(
+ '1',
+ cancellation.buyer_pubkey.to_string(),
+ 6,
+ cancellation_parts.into_wire_parts(),
+ );
+ let cancellation_envelope =
+ order::parse_order_cancellation(&cancellation_event).expect("cancellation envelope");
+ assert_eq!(
+ cancellation_envelope.payload.order_id,
+ cancellation.order_id
+ );
+}
+
+#[test]
+fn order_facade_surfaces_order_draft_build_errors() {
+ let root_event_id = event_id('c');
+ let previous_event_id = event_id('d');
+
+ let mut decision = sample_order_decision();
+ decision.decision = RadrootsOrderDecisionOutcome::Accepted {
+ inventory_commitments: Vec::new(),
+ };
+ assert!(order::build_order_decision_draft(&root_event_id, &root_event_id, &decision).is_err());
+
+ let mut proposal = sample_order_revision_proposal(&root_event_id, &previous_event_id);
+ proposal.reason = " ".into();
+ assert!(
+ order::build_order_revision_proposal_draft(&root_event_id, &previous_event_id, &proposal)
+ .is_err()
+ );
+
+ let proposal = sample_order_revision_proposal(&root_event_id, &previous_event_id);
+ let mut revision_decision = sample_order_revision_decision(&proposal, &previous_event_id);
+ revision_decision.decision = RadrootsOrderRevisionOutcome::Declined { reason: " ".into() };
+ assert!(
+ order::build_order_revision_decision_draft(
+ &root_event_id,
+ &previous_event_id,
+ &revision_decision
+ )
+ .is_err()
+ );
+
+ let mut cancellation = sample_order_cancellation();
+ cancellation.reason = " ".into();
+ assert!(
+ order::build_order_cancellation_draft(&root_event_id, &previous_event_id, &cancellation)
+ .is_err()
+ );
}
diff --git a/crates/sdk/tests/orders_runtime.rs b/crates/sdk/tests/orders_runtime.rs
@@ -36,16 +36,18 @@ use radroots_sdk::{
ORDER_CANCELLATION_OPERATION_KIND, ORDER_DECISION_OPERATION_KIND,
ORDER_REVISION_DECISION_OPERATION_KIND, ORDER_REVISION_PROPOSAL_OPERATION_KIND,
ORDER_STATUS_DEFAULT_LIMIT, ORDER_STATUS_MAX_LIMIT, ORDER_SUBMIT_OPERATION_KIND,
- OrderCancellationEnqueueRequest, OrderDecisionEnqueueRequest, OrderDecisionPrepareRequest,
- OrderEvidenceIngestRequest, OrderRequestEvidenceIngestRequest,
- OrderRevisionDecisionEnqueueRequest, OrderRevisionProposalEnqueueRequest, OrderStatusKind,
+ OrderCancellationEnqueueRequest, OrderCancellationPrepareRequest, OrderDecisionEnqueueRequest,
+ OrderDecisionPrepareRequest, OrderEvidenceIngestRequest, OrderRequestEvidenceIngestRequest,
+ OrderRevisionDecisionEnqueueRequest, OrderRevisionDecisionPrepareRequest,
+ OrderRevisionProposalEnqueueRequest, OrderRevisionProposalPrepareRequest, OrderStatusKind,
OrderStatusNextActionKind, OrderStatusRequest, OrderSubmitEnqueueRequest,
OrderSubmitPrepareRequest, OrderWorkflowKind, PushOutboxEventState, PushOutboxRelayOutcomeKind,
PushOutboxRequest, RadrootsSdk, RadrootsSdkError, RadrootsSdkPartialLocalMutationFailure,
- RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, SdkMutationState, SdkOrderStatusIssue,
- SdkOrderStatusIssueKind, SdkOrderStatusSource, SdkRelayTargetPolicy, SdkRelayTargetSet,
- SdkRelayUrlPolicy,
+ RadrootsSdkRecoveryAction, RadrootsSdkTimestamp, SdkIdempotencyKey, SdkMutationState,
+ SdkOrderStatusIssue, SdkOrderStatusIssueKind, SdkOrderStatusSource, SdkRelayTargetPolicy,
+ SdkRelayTargetSet, SdkRelayUrlPolicy,
};
+use radroots_trade::order::RadrootsOrderIssue;
const BUYER_SECRET_KEY_HEX: &str =
"10c5304d6c9ae3a1a16f7860f1cc8f5e3a76225a2663b3a989a0d775919b7df5";
@@ -1517,6 +1519,184 @@ async fn order_decision_runtime_dtos_serialize_deterministically() {
}
#[tokio::test]
+async fn order_revision_and_cancellation_dtos_serialize_deterministically() {
+ let created_at = RadrootsSdkTimestamp::from_unix_seconds(1_700_000_654);
+ let root_event_id = deterministic_event_id("order-dto-root");
+ let previous_event_id = deterministic_event_id("order-dto-previous");
+ let root_event = order_event_ptr(&root_event_id);
+ let previous_event = order_event_ptr(&previous_event_id);
+ let proposal =
+ order_revision_proposal("order-revision-dto", &root_event_id, &previous_event_id);
+ let revision_decision = order_revision_decision(
+ &proposal,
+ &previous_event_id,
+ RadrootsOrderRevisionOutcome::Declined {
+ reason: "not available".to_owned(),
+ },
+ );
+ let cancellation = order_cancellation("order-revision-dto");
+
+ let proposal_prepare = OrderRevisionProposalPrepareRequest::new(
+ seller_actor(),
+ root_event.clone(),
+ previous_event.clone(),
+ proposal.clone(),
+ )
+ .with_created_at(created_at);
+ let proposal_prepare_json =
+ serde_json::to_value(&proposal_prepare).expect("proposal prepare json");
+ assert_eq!(
+ proposal_prepare_json["actor"]["pubkey"],
+ SELLER_PUBLIC_KEY_HEX
+ );
+ assert_eq!(
+ proposal_prepare_json["root_event"]["id"],
+ root_event_id.as_str()
+ );
+ assert_eq!(
+ proposal_prepare_json["previous_event"]["id"],
+ previous_event_id.as_str()
+ );
+ assert_eq!(
+ proposal_prepare_json["proposal"]["order_id"],
+ "order-revision-dto"
+ );
+ assert_eq!(
+ proposal_prepare_json["proposal"]["reason"],
+ "increase quantity"
+ );
+ assert_eq!(proposal_prepare_json["created_at"], 1_700_000_654);
+
+ let proposal_enqueue = OrderRevisionProposalEnqueueRequest::new(
+ seller_actor(),
+ root_event.clone(),
+ previous_event.clone(),
+ proposal.clone(),
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_target_relays([RELAY, RELAY_B], SdkRelayUrlPolicy::Public)
+ .expect("proposal relays")
+ .with_idempotency_key(SdkIdempotencyKey::new("order-revision-proposal-dto").expect("key"))
+ .with_created_at(created_at);
+ let proposal_enqueue_json =
+ serde_json::to_value(&proposal_enqueue).expect("proposal enqueue json");
+ assert_eq!(
+ proposal_enqueue_json["target_relays"],
+ serde_json::json!({
+ "kind": "explicit",
+ "relays": [RELAY, RELAY_B],
+ "canonical_relays": [RELAY_B, RELAY]
+ })
+ );
+ assert_eq!(
+ proposal_enqueue_json["idempotency_key"],
+ serde_json::json!({ "value": "<redacted>", "len": 27 })
+ );
+ assert!(!proposal_enqueue_json.to_string().contains("proposal-dto"));
+
+ let decision_prepare = OrderRevisionDecisionPrepareRequest::new(
+ buyer_actor(),
+ root_event.clone(),
+ previous_event.clone(),
+ revision_decision.clone(),
+ )
+ .with_created_at(created_at);
+ let decision_prepare_json =
+ serde_json::to_value(&decision_prepare).expect("decision prepare json");
+ assert_eq!(
+ decision_prepare_json["actor"]["pubkey"],
+ BUYER_PUBLIC_KEY_HEX
+ );
+ assert_eq!(
+ decision_prepare_json["decision"]["revision_id"],
+ proposal.revision_id.as_str()
+ );
+ assert_eq!(
+ decision_prepare_json["decision"]["decision"],
+ serde_json::json!({
+ "decision": "declined",
+ "reason": "not available"
+ })
+ );
+ assert_eq!(decision_prepare_json["created_at"], 1_700_000_654);
+
+ let decision_enqueue = OrderRevisionDecisionEnqueueRequest::new(
+ buyer_actor(),
+ root_event.clone(),
+ previous_event.clone(),
+ revision_decision,
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_target_relays([RELAY, RELAY_B], SdkRelayUrlPolicy::Public)
+ .expect("decision relays")
+ .try_with_idempotency_key("order-revision-decision-dto")
+ .expect("decision idempotency")
+ .with_created_at(created_at);
+ let decision_enqueue_json =
+ serde_json::to_value(&decision_enqueue).expect("decision enqueue json");
+ assert_eq!(
+ decision_enqueue_json["idempotency_key"],
+ serde_json::json!({ "value": "<redacted>", "len": 27 })
+ );
+ assert_eq!(decision_enqueue_json["created_at"], 1_700_000_654);
+ assert!(!decision_enqueue_json.to_string().contains("decision-dto"));
+
+ let cancellation_prepare = OrderCancellationPrepareRequest::new(
+ buyer_actor(),
+ root_event.clone(),
+ previous_event.clone(),
+ cancellation.clone(),
+ )
+ .with_created_at(created_at);
+ let cancellation_prepare_json =
+ serde_json::to_value(&cancellation_prepare).expect("cancellation prepare json");
+ assert_eq!(
+ cancellation_prepare_json["cancellation"]["reason"],
+ "buyer changed pickup plan"
+ );
+ assert_eq!(cancellation_prepare_json["created_at"], 1_700_000_654);
+
+ let cancellation_enqueue = OrderCancellationEnqueueRequest::new(
+ buyer_actor(),
+ root_event,
+ previous_event,
+ cancellation,
+ SdkRelayTargetPolicy::UseConfiguredRelays,
+ )
+ .try_with_target_relays([RELAY, RELAY_B], SdkRelayUrlPolicy::Public)
+ .expect("cancellation relays")
+ .try_with_idempotency_key("order-cancellation-dto")
+ .expect("cancellation idempotency")
+ .with_created_at(created_at);
+ let cancellation_enqueue_json =
+ serde_json::to_value(&cancellation_enqueue).expect("cancellation enqueue json");
+ assert_eq!(
+ cancellation_enqueue_json["idempotency_key"],
+ serde_json::json!({ "value": "<redacted>", "len": 22 })
+ );
+ assert_eq!(cancellation_enqueue_json["created_at"], 1_700_000_654);
+ assert!(
+ !cancellation_enqueue_json
+ .to_string()
+ .contains("cancellation-dto")
+ );
+
+ let event = signed_order_request_event("order-evidence-dto", 77);
+ let request_evidence =
+ OrderRequestEvidenceIngestRequest::new(event.clone()).with_observed_at(created_at);
+ let request_evidence_json =
+ serde_json::to_value(&request_evidence).expect("request evidence json");
+ assert_eq!(request_evidence_json["event"]["id"], event.id.as_str());
+ assert_eq!(request_evidence_json["observed_at"], 1_700_000_654);
+
+ let order_evidence =
+ OrderEvidenceIngestRequest::new(event.clone()).with_observed_at(created_at);
+ let order_evidence_json = serde_json::to_value(&order_evidence).expect("order evidence json");
+ assert_eq!(order_evidence_json["event"]["id"], event.id.as_str());
+ assert_eq!(order_evidence_json["observed_at"], 1_700_000_654);
+}
+
+#[tokio::test]
async fn order_decision_enqueue_accept_stores_event_queues_outbox_and_updates_status() {
let (_tempdir, sdk, store) = directory_sdk_and_store().await;
let request_event = signed_order_request_event("order-decision-accept", 40);
@@ -2481,6 +2661,319 @@ async fn order_status_contract_dtos_serialize_deterministically() {
);
}
+#[test]
+fn order_status_issue_mapping_preserves_kind_codes_and_event_ids() {
+ macro_rules! single_issue {
+ ($variant:ident, $kind:ident, $code:literal) => {{
+ let event_id = deterministic_event_id($code);
+ (
+ RadrootsOrderIssue::$variant {
+ event_id: event_id.clone(),
+ },
+ SdkOrderStatusIssueKind::$kind,
+ $code,
+ vec![event_id],
+ )
+ }};
+ }
+
+ macro_rules! multi_issue {
+ ($variant:ident, $kind:ident, $code:literal) => {{
+ let event_ids = vec![
+ deterministic_event_id(concat!($code, "-a")),
+ deterministic_event_id(concat!($code, "-b")),
+ ];
+ (
+ RadrootsOrderIssue::$variant {
+ event_ids: event_ids.clone(),
+ },
+ SdkOrderStatusIssueKind::$kind,
+ $code,
+ event_ids,
+ )
+ }};
+ }
+
+ let cases = vec![
+ (
+ RadrootsOrderIssue::MissingRequest,
+ SdkOrderStatusIssueKind::MissingRequest,
+ "missing_request",
+ Vec::new(),
+ ),
+ multi_issue!(MultipleRequests, MultipleRequests, "multiple_requests"),
+ single_issue!(
+ RequestPayloadInvalid,
+ RequestPayloadInvalid,
+ "request_payload_invalid"
+ ),
+ single_issue!(
+ RequestOrderIdMismatch,
+ RequestOrderIdMismatch,
+ "request_order_id_mismatch"
+ ),
+ single_issue!(
+ RequestAuthorMismatch,
+ RequestAuthorMismatch,
+ "request_author_mismatch"
+ ),
+ single_issue!(
+ RequestListingAddressInvalid,
+ RequestListingAddressInvalid,
+ "request_listing_address_invalid"
+ ),
+ single_issue!(
+ RequestSellerListingMismatch,
+ RequestSellerListingMismatch,
+ "request_seller_listing_mismatch"
+ ),
+ single_issue!(
+ DecisionPayloadInvalid,
+ DecisionPayloadInvalid,
+ "decision_payload_invalid"
+ ),
+ single_issue!(
+ DecisionOrderIdMismatch,
+ DecisionOrderIdMismatch,
+ "decision_order_id_mismatch"
+ ),
+ single_issue!(
+ DecisionAuthorMismatch,
+ DecisionAuthorMismatch,
+ "decision_author_mismatch"
+ ),
+ single_issue!(
+ DecisionCounterpartyMismatch,
+ DecisionCounterpartyMismatch,
+ "decision_counterparty_mismatch"
+ ),
+ single_issue!(
+ DecisionBuyerMismatch,
+ DecisionBuyerMismatch,
+ "decision_buyer_mismatch"
+ ),
+ single_issue!(
+ DecisionSellerMismatch,
+ DecisionSellerMismatch,
+ "decision_seller_mismatch"
+ ),
+ single_issue!(
+ DecisionListingAddressInvalid,
+ DecisionListingAddressInvalid,
+ "decision_listing_address_invalid"
+ ),
+ single_issue!(
+ DecisionListingMismatch,
+ DecisionListingMismatch,
+ "decision_listing_mismatch"
+ ),
+ single_issue!(
+ DecisionRootMismatch,
+ DecisionRootMismatch,
+ "decision_root_mismatch"
+ ),
+ single_issue!(
+ DecisionPreviousMismatch,
+ DecisionPreviousMismatch,
+ "decision_previous_mismatch"
+ ),
+ single_issue!(
+ DecisionMissingInventoryCommitments,
+ DecisionMissingInventoryCommitments,
+ "decision_missing_inventory_commitments"
+ ),
+ single_issue!(
+ DecisionInventoryCommitmentMismatch,
+ DecisionInventoryCommitmentMismatch,
+ "decision_inventory_commitment_mismatch"
+ ),
+ single_issue!(
+ DecisionMissingReason,
+ DecisionMissingReason,
+ "decision_missing_reason"
+ ),
+ multi_issue!(
+ ConflictingDecisions,
+ ConflictingDecisions,
+ "conflicting_decisions"
+ ),
+ single_issue!(
+ RevisionProposalPayloadInvalid,
+ RevisionProposalPayloadInvalid,
+ "revision_proposal_payload_invalid"
+ ),
+ single_issue!(
+ RevisionProposalOrderIdMismatch,
+ RevisionProposalOrderIdMismatch,
+ "revision_proposal_order_id_mismatch"
+ ),
+ single_issue!(
+ RevisionProposalAuthorMismatch,
+ RevisionProposalAuthorMismatch,
+ "revision_proposal_author_mismatch"
+ ),
+ single_issue!(
+ RevisionProposalCounterpartyMismatch,
+ RevisionProposalCounterpartyMismatch,
+ "revision_proposal_counterparty_mismatch"
+ ),
+ single_issue!(
+ RevisionProposalBuyerMismatch,
+ RevisionProposalBuyerMismatch,
+ "revision_proposal_buyer_mismatch"
+ ),
+ single_issue!(
+ RevisionProposalSellerMismatch,
+ RevisionProposalSellerMismatch,
+ "revision_proposal_seller_mismatch"
+ ),
+ single_issue!(
+ RevisionProposalListingAddressInvalid,
+ RevisionProposalListingAddressInvalid,
+ "revision_proposal_listing_address_invalid"
+ ),
+ single_issue!(
+ RevisionProposalListingMismatch,
+ RevisionProposalListingMismatch,
+ "revision_proposal_listing_mismatch"
+ ),
+ single_issue!(
+ RevisionProposalRootMismatch,
+ RevisionProposalRootMismatch,
+ "revision_proposal_root_mismatch"
+ ),
+ single_issue!(
+ RevisionProposalPreviousMismatch,
+ RevisionProposalPreviousMismatch,
+ "revision_proposal_previous_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionWithoutProposal,
+ RevisionDecisionWithoutProposal,
+ "revision_decision_without_proposal"
+ ),
+ single_issue!(
+ RevisionDecisionPayloadInvalid,
+ RevisionDecisionPayloadInvalid,
+ "revision_decision_payload_invalid"
+ ),
+ single_issue!(
+ RevisionDecisionOrderIdMismatch,
+ RevisionDecisionOrderIdMismatch,
+ "revision_decision_order_id_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionAuthorMismatch,
+ RevisionDecisionAuthorMismatch,
+ "revision_decision_author_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionCounterpartyMismatch,
+ RevisionDecisionCounterpartyMismatch,
+ "revision_decision_counterparty_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionBuyerMismatch,
+ RevisionDecisionBuyerMismatch,
+ "revision_decision_buyer_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionSellerMismatch,
+ RevisionDecisionSellerMismatch,
+ "revision_decision_seller_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionListingAddressInvalid,
+ RevisionDecisionListingAddressInvalid,
+ "revision_decision_listing_address_invalid"
+ ),
+ single_issue!(
+ RevisionDecisionListingMismatch,
+ RevisionDecisionListingMismatch,
+ "revision_decision_listing_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionRootMismatch,
+ RevisionDecisionRootMismatch,
+ "revision_decision_root_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionPreviousMismatch,
+ RevisionDecisionPreviousMismatch,
+ "revision_decision_previous_mismatch"
+ ),
+ single_issue!(
+ RevisionDecisionRevisionIdMismatch,
+ RevisionDecisionRevisionIdMismatch,
+ "revision_decision_revision_id_mismatch"
+ ),
+ single_issue!(
+ CancellationWithoutCancellableOrder,
+ CancellationWithoutCancellableOrder,
+ "cancellation_without_cancellable_order"
+ ),
+ single_issue!(
+ CancellationPayloadInvalid,
+ CancellationPayloadInvalid,
+ "cancellation_payload_invalid"
+ ),
+ single_issue!(
+ CancellationOrderIdMismatch,
+ CancellationOrderIdMismatch,
+ "cancellation_order_id_mismatch"
+ ),
+ single_issue!(
+ CancellationAuthorMismatch,
+ CancellationAuthorMismatch,
+ "cancellation_author_mismatch"
+ ),
+ single_issue!(
+ CancellationCounterpartyMismatch,
+ CancellationCounterpartyMismatch,
+ "cancellation_counterparty_mismatch"
+ ),
+ single_issue!(
+ CancellationBuyerMismatch,
+ CancellationBuyerMismatch,
+ "cancellation_buyer_mismatch"
+ ),
+ single_issue!(
+ CancellationSellerMismatch,
+ CancellationSellerMismatch,
+ "cancellation_seller_mismatch"
+ ),
+ single_issue!(
+ CancellationListingAddressInvalid,
+ CancellationListingAddressInvalid,
+ "cancellation_listing_address_invalid"
+ ),
+ single_issue!(
+ CancellationListingMismatch,
+ CancellationListingMismatch,
+ "cancellation_listing_mismatch"
+ ),
+ single_issue!(
+ CancellationRootMismatch,
+ CancellationRootMismatch,
+ "cancellation_root_mismatch"
+ ),
+ single_issue!(
+ CancellationPreviousMismatch,
+ CancellationPreviousMismatch,
+ "cancellation_previous_mismatch"
+ ),
+ multi_issue!(ForkedLifecycle, ForkedLifecycle, "forked_lifecycle"),
+ ];
+
+ for (issue, expected_kind, expected_code, expected_event_ids) in cases {
+ let sdk_issue = SdkOrderStatusIssue::from(issue);
+
+ assert_eq!(sdk_issue.kind, expected_kind);
+ assert_eq!(sdk_issue.code(), expected_code);
+ assert_eq!(sdk_issue.event_ids, expected_event_ids);
+ }
+}
+
#[tokio::test]
async fn order_status_projects_local_request_and_decision_events() {
let (_tempdir, sdk, store) = directory_sdk_and_store().await;