commit 39bb2c54aa7bd0dda369702cdbca5007b17eca0e
parent a4ca6f873df7ca0c86793a713be7c412fbfc8277
Author: triesap <tyson@radroots.org>
Date: Tue, 28 Apr 2026 00:33:27 +0000
order: carry listing event pointer
Diffstat:
6 files changed, 293 insertions(+), 7 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1702,6 +1702,7 @@ dependencies = [
"radroots_nostr_signer",
"radroots_protected_store",
"radroots_replica_db",
+ "radroots_replica_db_schema",
"radroots_replica_sync",
"radroots_runtime_paths",
"radroots_secret_vault",
diff --git a/Cargo.toml b/Cargo.toml
@@ -34,6 +34,7 @@ radroots_nostr = { path = "../lib/crates/nostr", features = ["client", "events"]
radroots_nostr_signer = { path = "../lib/crates/nostr_signer" }
radroots_protected_store = { path = "../lib/crates/protected_store", features = ["std"] }
radroots_replica_db = { path = "../lib/crates/replica_db" }
+radroots_replica_db_schema = { path = "../lib/crates/replica_db_schema" }
radroots_replica_sync = { path = "../lib/crates/replica_sync" }
radroots_runtime_paths = { path = "../lib/crates/runtime_paths" }
radroots_secret_vault = { path = "../lib/crates/secret_vault", features = ["std", "os-keyring"] }
diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs
@@ -1049,6 +1049,8 @@ pub struct OrderNewView {
#[serde(skip_serializing_if = "Option::is_none")]
pub listing_addr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
+ pub listing_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub buyer_account_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub buyer_pubkey: Option<String>,
@@ -1086,6 +1088,8 @@ pub struct OrderGetView {
#[serde(skip_serializing_if = "Option::is_none")]
pub listing_addr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
+ pub listing_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub buyer_account_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub buyer_pubkey: Option<String>,
@@ -1148,6 +1152,8 @@ pub struct OrderSubmitView {
#[serde(skip_serializing_if = "Option::is_none")]
pub listing_addr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
+ pub listing_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub buyer_account_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub buyer_pubkey: Option<String>,
@@ -1341,6 +1347,8 @@ pub struct OrderSummaryView {
#[serde(skip_serializing_if = "Option::is_none")]
pub listing_addr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
+ pub listing_event_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub buyer_account_id: Option<String>,
pub item_count: usize,
pub updated_at_unix: u64,
diff --git a/src/runtime/order.rs b/src/runtime/order.rs
@@ -6,7 +6,11 @@ use std::time::{SystemTime, UNIX_EPOCH};
use radroots_events::kinds::KIND_LISTING;
use radroots_events_codec::d_tag::is_d_tag_base64url;
use radroots_events_codec::trade::RadrootsTradeListingAddress;
-use radroots_replica_db::ReplicaSql;
+use radroots_replica_db::{ReplicaSql, nostr_event_state, trade_product};
+use radroots_replica_db_schema::nostr_event_state::{
+ INostrEventStateFindOne, INostrEventStateFindOneArgs, NostrEventStateQueryBindValues,
+};
+use radroots_replica_db_schema::trade_product::{ITradeProductFieldsFilter, ITradeProductFindMany};
use radroots_sql_core::SqliteExecutor;
use serde::{Deserialize, Serialize};
@@ -51,6 +55,8 @@ struct OrderDraft {
#[serde(default, skip_serializing_if = "String::is_empty")]
listing_addr: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
+ listing_event_id: String,
+ #[serde(default, skip_serializing_if = "String::is_empty")]
buyer_pubkey: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
seller_pubkey: String,
@@ -75,6 +81,7 @@ struct LoadedOrderDraft {
#[derive(Debug, Clone)]
struct ResolvedOrderListing {
listing_addr: String,
+ listing_event_id: String,
seller_pubkey: String,
}
@@ -109,6 +116,10 @@ pub fn scaffold(
.as_ref()
.map(|listing| listing.seller_pubkey.clone())
.unwrap_or_default();
+ let listing_event_id = resolved_listing
+ .as_ref()
+ .map(|listing| listing.listing_event_id.clone())
+ .unwrap_or_default();
let items = match normalize_optional(args.bin_id.as_deref()) {
Some(bin_id) => vec![OrderDraftItem {
@@ -129,6 +140,7 @@ pub fn scaffold(
order: OrderDraft {
order_id: order_id.clone(),
listing_addr,
+ listing_event_id,
buyer_pubkey,
seller_pubkey,
items,
@@ -181,6 +193,10 @@ pub fn scaffold_preflight(
.as_ref()
.map(|listing| listing.seller_pubkey.clone())
.unwrap_or_default();
+ let listing_event_id = resolved_listing
+ .as_ref()
+ .map(|listing| listing.listing_event_id.clone())
+ .unwrap_or_default();
let items = match normalize_optional(args.bin_id.as_deref()) {
Some(bin_id) => vec![OrderDraftItem {
@@ -198,6 +214,7 @@ pub fn scaffold_preflight(
order: OrderDraft {
order_id: order_id.clone(),
listing_addr,
+ listing_event_id,
buyer_pubkey,
seller_pubkey,
items,
@@ -231,6 +248,7 @@ pub fn get(config: &RuntimeConfig, args: &RecordLookupArgs) -> Result<OrderGetVi
file: Some(file.display().to_string()),
listing_lookup: None,
listing_addr: None,
+ listing_event_id: None,
buyer_account_id: None,
buyer_pubkey: None,
seller_pubkey: None,
@@ -258,6 +276,7 @@ pub fn get(config: &RuntimeConfig, args: &RecordLookupArgs) -> Result<OrderGetVi
file: Some(file.display().to_string()),
listing_lookup: None,
listing_addr: None,
+ listing_event_id: None,
buyer_account_id: None,
buyer_pubkey: None,
seller_pubkey: None,
@@ -340,6 +359,7 @@ pub fn submit(
file: file.display().to_string(),
listing_lookup: None,
listing_addr: None,
+ listing_event_id: None,
buyer_account_id: None,
buyer_pubkey: None,
seller_pubkey: None,
@@ -369,6 +389,7 @@ pub fn submit(
file: file.display().to_string(),
listing_lookup: None,
listing_addr: None,
+ listing_event_id: None,
buyer_account_id: None,
buyer_pubkey: None,
seller_pubkey: None,
@@ -400,6 +421,7 @@ pub fn submit(
file: loaded.file.display().to_string(),
listing_lookup: loaded.document.listing_lookup.clone(),
listing_addr: non_empty_string(loaded.document.order.listing_addr.clone()),
+ listing_event_id: non_empty_string(loaded.document.order.listing_event_id.clone()),
buyer_account_id: loaded.document.buyer_account_id.clone(),
buyer_pubkey: non_empty_string(loaded.document.order.buyer_pubkey.clone()),
seller_pubkey: non_empty_string(loaded.document.order.seller_pubkey.clone()),
@@ -430,6 +452,7 @@ pub fn submit(
file: loaded.file.display().to_string(),
listing_lookup: loaded.document.listing_lookup.clone(),
listing_addr: non_empty_string(loaded.document.order.listing_addr.clone()),
+ listing_event_id: non_empty_string(loaded.document.order.listing_event_id.clone()),
buyer_account_id: loaded.document.buyer_account_id.clone(),
buyer_pubkey: non_empty_string(loaded.document.order.buyer_pubkey.clone()),
seller_pubkey: non_empty_string(loaded.document.order.seller_pubkey.clone()),
@@ -632,12 +655,20 @@ fn resolve_order_listing(
explicit_listing_addr: Option<&str>,
) -> Result<Option<ResolvedOrderListing>, RuntimeError> {
if let Some(listing_addr) = explicit_listing_addr {
- let seller_pubkey = parse_listing_addr(listing_addr)
- .map(|listing| listing.seller_pubkey)
- .unwrap_or_default();
+ let parsed = parse_listing_addr(listing_addr).map_err(|error| {
+ RuntimeError::Config(format!("explicit listing_addr is invalid: {error}"))
+ })?;
+ if parsed.kind != KIND_LISTING {
+ return Err(RuntimeError::Config(
+ "explicit listing_addr must reference a public NIP-99 listing".to_owned(),
+ ));
+ }
+ let listing_event_id =
+ resolve_active_listing_event_id(config, listing_addr, &parsed)?.unwrap_or_default();
return Ok(Some(ResolvedOrderListing {
listing_addr: listing_addr.to_owned(),
- seller_pubkey,
+ listing_event_id,
+ seller_pubkey: parsed.seller_pubkey,
}));
}
@@ -675,8 +706,20 @@ fn resolve_order_listing(
)));
}
+ let listing_event_id = resolve_active_listing_event_id(
+ config,
+ listing_addr.as_str(),
+ &parsed,
+ )?
+ .ok_or_else(|| {
+ RuntimeError::Config(format!(
+ "listing `{listing_lookup}` is missing the latest listing event pointer; run `radroots market refresh` before creating an order from this listing"
+ ))
+ })?;
+
Ok(Some(ResolvedOrderListing {
listing_addr,
+ listing_event_id,
seller_pubkey: parsed.seller_pubkey,
}))
}
@@ -686,11 +729,92 @@ fn resolve_order_listing(
}
}
+fn resolve_active_listing_event_id(
+ config: &RuntimeConfig,
+ listing_addr: &str,
+ parsed: &RadrootsTradeListingAddress,
+) -> Result<Option<String>, RuntimeError> {
+ if !config.local.replica_db_path.exists() {
+ return Ok(None);
+ }
+
+ let executor = SqliteExecutor::open(&config.local.replica_db_path)?;
+ let product_rows = trade_product::find_many(
+ &executor,
+ &ITradeProductFindMany {
+ filter: Some(trade_product_listing_addr_filter(listing_addr)),
+ },
+ )
+ .map_err(|error| RuntimeError::Config(format!("resolve listing product state: {error:?}")))?
+ .results;
+
+ match product_rows.len() {
+ 0 => return Ok(None),
+ 1 => {}
+ count => {
+ return Err(RuntimeError::Config(format!(
+ "listing address `{listing_addr}` matched {count} active local listing rows"
+ )));
+ }
+ }
+
+ let key = format!(
+ "{}:{}:{}",
+ parsed.kind, parsed.seller_pubkey, parsed.listing_id
+ );
+ let state = nostr_event_state::find_one(
+ &executor,
+ &INostrEventStateFindOne::On(INostrEventStateFindOneArgs {
+ on: NostrEventStateQueryBindValues::Key { key },
+ }),
+ )
+ .map_err(|error| RuntimeError::Config(format!("resolve listing event state: {error:?}")))?
+ .result;
+
+ let Some(state) = state else {
+ return Ok(None);
+ };
+ if !is_valid_event_id(state.last_event_id.as_str()) {
+ return Err(RuntimeError::Config(format!(
+ "listing address `{listing_addr}` has invalid latest listing event id in local replica"
+ )));
+ }
+
+ Ok(Some(state.last_event_id))
+}
+
+fn trade_product_listing_addr_filter(listing_addr: &str) -> ITradeProductFieldsFilter {
+ ITradeProductFieldsFilter {
+ id: None,
+ created_at: None,
+ updated_at: None,
+ key: None,
+ category: None,
+ title: None,
+ summary: None,
+ process: None,
+ lot: None,
+ profile: None,
+ year: None,
+ qty_amt: None,
+ qty_unit: None,
+ qty_label: None,
+ qty_avail: None,
+ price_amt: None,
+ price_currency: None,
+ price_qty_amt: None,
+ price_qty_unit: None,
+ listing_addr: Some(listing_addr.to_owned()),
+ notes: None,
+ }
+}
+
fn view_from_loaded(loaded: LoadedOrderDraft) -> OrderGetView {
let OrderInspection {
state,
ready_for_submit,
listing_addr,
+ listing_event_id,
seller_pubkey,
issues,
} = inspect_document(&loaded.document);
@@ -705,6 +829,7 @@ fn view_from_loaded(loaded: LoadedOrderDraft) -> OrderGetView {
file: Some(loaded.file.display().to_string()),
listing_lookup: loaded.document.listing_lookup.clone(),
listing_addr,
+ listing_event_id,
buyer_account_id: loaded.document.buyer_account_id.clone(),
buyer_pubkey: non_empty_string(loaded.document.order.buyer_pubkey.clone()),
seller_pubkey,
@@ -733,6 +858,7 @@ fn summary_from_loaded(loaded: &LoadedOrderDraft) -> OrderSummaryView {
state,
ready_for_submit,
listing_addr,
+ listing_event_id,
seller_pubkey: _,
issues,
} = inspect_document(&loaded.document);
@@ -744,6 +870,7 @@ fn summary_from_loaded(loaded: &LoadedOrderDraft) -> OrderSummaryView {
file: loaded.file.display().to_string(),
listing_lookup: loaded.document.listing_lookup.clone(),
listing_addr,
+ listing_event_id,
buyer_account_id: loaded.document.buyer_account_id.clone(),
item_count: loaded.document.order.items.len(),
updated_at_unix: loaded.updated_at_unix,
@@ -765,6 +892,7 @@ fn summary_for_invalid_file(path: &Path, reason: String) -> OrderSummaryView {
file: path.display().to_string(),
listing_lookup: None,
listing_addr: None,
+ listing_event_id: None,
buyer_account_id: None,
item_count: 0,
updated_at_unix: modified_unix(path).unwrap_or_default(),
@@ -778,6 +906,7 @@ fn summary_for_invalid_file(path: &Path, reason: String) -> OrderSummaryView {
fn inspect_document(document: &OrderDraftDocument) -> OrderInspection {
let listing_addr = non_empty_string(document.order.listing_addr.clone());
+ let listing_event_id = non_empty_string(document.order.listing_event_id.clone());
let parsed_listing_addr = listing_addr
.as_deref()
.and_then(|value| parse_listing_addr(value).ok());
@@ -798,6 +927,7 @@ fn inspect_document(document: &OrderDraftDocument) -> OrderInspection {
state,
ready_for_submit,
listing_addr,
+ listing_event_id,
seller_pubkey,
issues,
}
@@ -848,6 +978,21 @@ fn collect_issues(document: &OrderDraftDocument) -> Vec<OrderIssueView> {
)),
}
+ match normalize_optional(Some(document.order.listing_event_id.as_str())) {
+ Some(listing_event_id) => {
+ if !is_valid_event_id(listing_event_id.as_str()) {
+ issues.push(issue(
+ "order.listing_event_id",
+ "listing_event_id must be a 64-character hex Nostr event id",
+ ));
+ }
+ }
+ None => issues.push(issue(
+ "order.listing_event_id",
+ "latest active listing event id is required before order submit; run `radroots market refresh` and create the order from local market data",
+ )),
+ }
+
if document.order.items.is_empty() {
issues.push(issue(
"order.items",
@@ -919,6 +1064,7 @@ fn direct_relay_unavailable_order_submit_view(
file: loaded.file.display().to_string(),
listing_lookup: loaded.document.listing_lookup.clone(),
listing_addr: non_empty_string(loaded.document.order.listing_addr.clone()),
+ listing_event_id: non_empty_string(loaded.document.order.listing_event_id.clone()),
buyer_account_id: loaded.document.buyer_account_id.clone(),
buyer_pubkey: non_empty_string(loaded.document.order.buyer_pubkey.clone()),
seller_pubkey: non_empty_string(loaded.document.order.seller_pubkey.clone()),
@@ -962,6 +1108,7 @@ fn order_binding_error_view(
file: loaded.file.display().to_string(),
listing_lookup: loaded.document.listing_lookup.clone(),
listing_addr: non_empty_string(loaded.document.order.listing_addr.clone()),
+ listing_event_id: non_empty_string(loaded.document.order.listing_event_id.clone()),
buyer_account_id: loaded.document.buyer_account_id.clone(),
buyer_pubkey: non_empty_string(loaded.document.order.buyer_pubkey.clone()),
seller_pubkey: non_empty_string(loaded.document.order.seller_pubkey.clone()),
@@ -1109,6 +1256,10 @@ fn is_valid_order_id(value: &str) -> bool {
encoded.len() == 22 && is_d_tag_base64url(encoded)
}
+fn is_valid_event_id(value: &str) -> bool {
+ value.len() == 64 && value.chars().all(|ch| ch.is_ascii_hexdigit())
+}
+
fn encode_base64url_no_pad(bytes: [u8; 16]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let mut output = String::with_capacity(22);
@@ -1142,6 +1293,7 @@ struct OrderInspection {
state: String,
ready_for_submit: bool,
listing_addr: Option<String>,
+ listing_event_id: Option<String>,
seller_pubkey: Option<String>,
issues: Vec<OrderIssueView>,
}
@@ -1155,6 +1307,7 @@ impl From<OrderGetView> for OrderNewView {
file: view.file.unwrap_or_default(),
listing_lookup: view.listing_lookup,
listing_addr: view.listing_addr,
+ listing_event_id: view.listing_event_id,
buyer_account_id: view.buyer_account_id,
buyer_pubkey: view.buyer_pubkey,
seller_pubkey: view.seller_pubkey,
@@ -1168,7 +1321,10 @@ impl From<OrderGetView> for OrderNewView {
#[cfg(test)]
mod tests {
- use super::{ORDER_DRAFT_KIND, OrderDraft, OrderDraftDocument, OrderDraftItem, next_order_id};
+ use super::{
+ ORDER_DRAFT_KIND, OrderDraft, OrderDraftDocument, OrderDraftItem, collect_issues,
+ inspect_document, next_order_id,
+ };
#[test]
fn generated_order_id_uses_stable_prefix() {
@@ -1185,6 +1341,7 @@ mod tests {
order: OrderDraft {
order_id: "ord_AAAAAAAAAAAAAAAAAAAAAg".to_owned(),
listing_addr: "30402:deadbeef:AAAAAAAAAAAAAAAAAAAAAg".to_owned(),
+ listing_event_id: "1".repeat(64),
buyer_pubkey: "a".repeat(64),
seller_pubkey: "b".repeat(64),
items: vec![OrderDraftItem {
@@ -1199,5 +1356,36 @@ mod tests {
let rendered = toml::to_string_pretty(&document).expect("render draft");
assert!(rendered.contains("kind = \"order_draft_v1\""));
assert!(rendered.contains("order_id = \"ord_AAAAAAAAAAAAAAAAAAAAAg\""));
+ assert!(rendered.contains("listing_event_id"));
+ }
+
+ #[test]
+ fn order_draft_requires_listing_event_id_for_submit_readiness() {
+ let document = OrderDraftDocument {
+ version: 1,
+ kind: ORDER_DRAFT_KIND.to_owned(),
+ order: OrderDraft {
+ order_id: "ord_AAAAAAAAAAAAAAAAAAAAAg".to_owned(),
+ listing_addr: "30402:deadbeef:AAAAAAAAAAAAAAAAAAAAAg".to_owned(),
+ listing_event_id: String::new(),
+ buyer_pubkey: "a".repeat(64),
+ seller_pubkey: "deadbeef".to_owned(),
+ items: vec![OrderDraftItem {
+ bin_id: "bin-1".to_owned(),
+ bin_count: 2,
+ }],
+ },
+ listing_lookup: Some("fresh-eggs".to_owned()),
+ buyer_account_id: Some("acct_demo".to_owned()),
+ };
+
+ let inspection = inspect_document(&document);
+ assert_eq!(inspection.state, "draft");
+ assert!(!inspection.ready_for_submit);
+ assert!(
+ collect_issues(&document)
+ .iter()
+ .any(|issue| issue.field == "order.listing_event_id")
+ );
}
}
diff --git a/tests/support/mod.rs b/tests/support/mod.rs
@@ -6,7 +6,12 @@ use std::process::{Command, Output};
use std::sync::Mutex;
use assert_cmd::prelude::*;
+use radroots_events::RadrootsNostrEvent;
+use radroots_events::kinds::{KIND_FARM, KIND_LISTING};
+use radroots_events_codec::trade::RadrootsTradeListingAddress;
use radroots_identity::{RadrootsIdentity, RadrootsIdentityPublic};
+use radroots_replica_sync::{RadrootsReplicaIngestOutcome, radroots_replica_ingest_event};
+use radroots_sql_core::SqliteExecutor;
use serde_json::Value;
use tempfile::TempDir;
@@ -199,6 +204,72 @@ pub fn assert_hex_len(value: &Value, expected_len: usize) {
assert!(value.chars().all(|ch| ch.is_ascii_hexdigit()));
}
+pub fn seed_orderable_listing(sandbox: &RadrootsCliSandbox, listing_addr: &str) -> String {
+ let store = sandbox.json_success(&["--format", "json", "store", "init"]);
+ let db_path = store["result"]["path"]
+ .as_str()
+ .expect("replica db path from store init");
+ let parsed = RadrootsTradeListingAddress::parse(listing_addr).expect("listing addr");
+ let seller_pubkey = parsed.seller_pubkey.clone();
+ let listing_id = parsed.listing_id.clone();
+ let event_id = "2".repeat(64);
+ let event = RadrootsNostrEvent {
+ id: event_id.clone(),
+ author: seller_pubkey.clone(),
+ created_at: 1,
+ kind: KIND_LISTING,
+ tags: vec![
+ vec!["d".to_owned(), listing_id],
+ vec![
+ "a".to_owned(),
+ format!(
+ "{}:{}:{}",
+ KIND_FARM, seller_pubkey, "AAAAAAAAAAAAAAAAAAAAAA"
+ ),
+ ],
+ vec!["p".to_owned(), seller_pubkey],
+ vec!["key".to_owned(), "pasture-eggs".to_owned()],
+ vec!["title".to_owned(), "Market Eggs".to_owned()],
+ vec!["category".to_owned(), "eggs".to_owned()],
+ vec!["summary".to_owned(), "Pasture-raised eggs".to_owned()],
+ vec!["process".to_owned(), "washed".to_owned()],
+ vec!["lot".to_owned(), "lot-a".to_owned()],
+ vec!["profile".to_owned(), "dozen".to_owned()],
+ vec!["year".to_owned(), "2026".to_owned()],
+ vec!["radroots:primary_bin".to_owned(), "bin-1".to_owned()],
+ vec![
+ "radroots:bin".to_owned(),
+ "bin-1".to_owned(),
+ "12".to_owned(),
+ "each".to_owned(),
+ "12".to_owned(),
+ "each".to_owned(),
+ "dozen".to_owned(),
+ ],
+ vec![
+ "radroots:price".to_owned(),
+ "bin-1".to_owned(),
+ "6".to_owned(),
+ "USD".to_owned(),
+ "1".to_owned(),
+ "each".to_owned(),
+ "6".to_owned(),
+ "each".to_owned(),
+ ],
+ vec!["inventory".to_owned(), "5".to_owned()],
+ vec!["status".to_owned(), "active".to_owned()],
+ ],
+ content: "# Market Eggs".to_owned(),
+ sig: "f".repeat(128),
+ };
+ let executor = SqliteExecutor::open(Path::new(db_path)).expect("open replica db");
+ assert_eq!(
+ radroots_replica_ingest_event(&executor, &event).expect("ingest listing"),
+ RadrootsReplicaIngestOutcome::Applied
+ );
+ event_id
+}
+
pub fn create_listing_draft(sandbox: &RadrootsCliSandbox, key: &str) -> PathBuf {
let listing_file = sandbox.root().join(format!("{key}.toml"));
let listing_file_arg = listing_file.to_string_lossy();
diff --git a/tests/target_cli.rs b/tests/target_cli.rs
@@ -8,7 +8,7 @@ use serde_json::Value;
use support::{
RadrootsCliSandbox, assert_no_daemon_runtime_reference, assert_no_removed_command_reference,
create_listing_draft, identity_public, make_listing_publishable, ndjson_from_stdout, radroots,
- write_public_identity_profile,
+ seed_orderable_listing, write_public_identity_profile,
};
const LISTING_ADDR: &str =
@@ -968,6 +968,8 @@ fn buyer_target_flow_acceptance_uses_target_operations() {
assert_eq!(signer["result"]["signer_account_id"], account_id);
assert_no_removed_command_reference(&signer, &["signer", "status", "get"]);
+ let listing_event_id = seed_orderable_listing(&sandbox, LISTING_ADDR);
+
let search = sandbox.json_success(&["--format", "json", "market", "product", "search", "eggs"]);
assert_eq!(search["operation_id"], "market.product.search");
assert_eq!(search["errors"].as_array().expect("errors").len(), 0);
@@ -1012,6 +1014,10 @@ fn buyer_target_flow_acceptance_uses_target_operations() {
.expect("order id");
assert_eq!(quote["result"]["quote"]["ready_for_submit"], true);
assert_eq!(quote["result"]["order"]["buyer_account_id"], account_id);
+ assert_eq!(
+ quote["result"]["order"]["listing_event_id"],
+ listing_event_id
+ );
let orders = sandbox.json_success(&["--format", "json", "order", "list"]);
assert_eq!(orders["operation_id"], "order.list");
@@ -1020,6 +1026,10 @@ fn buyer_target_flow_acceptance_uses_target_operations() {
assert_eq!(orders["result"]["orders"][0]["id"], order_id);
assert_eq!(orders["result"]["orders"][0]["ready_for_submit"], true);
assert_eq!(
+ orders["result"]["orders"][0]["listing_event_id"],
+ listing_event_id
+ );
+ assert_eq!(
orders["result"]["orders"][0]["buyer_account_id"],
account_id
);
@@ -1035,6 +1045,7 @@ fn buyer_target_flow_acceptance_uses_target_operations() {
assert_eq!(submit["result"]["dry_run"], true);
assert_eq!(submit["result"]["order_id"], order_id);
assert_eq!(submit["result"]["buyer_account_id"], account_id);
+ assert_eq!(submit["result"]["listing_event_id"], listing_event_id);
assert_eq!(submit["errors"].as_array().expect("errors").len(), 0);
assert_no_removed_command_reference(&submit, &["order", "submit", "--dry-run"]);
assert_no_daemon_runtime_reference(&submit, &["order", "submit", "--dry-run"]);
@@ -1090,6 +1101,7 @@ fn ready_order_submit_dry_run_validates_local_buyer_authority() {
let first_account_id = first["result"]["account"]["id"]
.as_str()
.expect("first account id");
+ let listing_event_id = seed_orderable_listing(&sandbox, LISTING_ADDR);
sandbox.json_success(&["--format", "json", "basket", "create", "ready_order"]);
sandbox.json_success(&[
"--format",
@@ -1121,6 +1133,10 @@ fn ready_order_submit_dry_run_validates_local_buyer_authority() {
quote["result"]["order"]["buyer_account_id"],
first_account_id
);
+ assert_eq!(
+ quote["result"]["order"]["listing_event_id"],
+ listing_event_id
+ );
let dry_run =
sandbox.json_success(&["--format", "json", "--dry-run", "order", "submit", order_id]);
@@ -1130,6 +1146,7 @@ fn ready_order_submit_dry_run_validates_local_buyer_authority() {
assert_eq!(dry_run["result"]["state"], "dry_run");
assert_eq!(dry_run["result"]["dry_run"], true);
assert_eq!(dry_run["result"]["buyer_account_id"], first_account_id);
+ assert_eq!(dry_run["result"]["listing_event_id"], listing_event_id);
assert_no_daemon_runtime_reference(&dry_run, &["order", "submit", "--dry-run"]);
let second = sandbox.json_success(&["--format", "json", "account", "create"]);