cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit d8e31b37518205781dc3d80b757e6d977868fd7c
parent 3f856c6368cf5841c4ceac5e4860b042a38bcb1d
Author: triesap <tyson@radroots.org>
Date:   Wed,  8 Apr 2026 18:29:46 +0000

cli: add signer-session selection surface

Diffstat:
Msrc/cli.rs | 68+++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/domain/runtime.rs | 8++++++++
Msrc/render/mod.rs | 68++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/runtime/listing.rs | 18++++++++++++++----
Msrc/runtime/order.rs | 21++++++++++++++++++---
Mtests/listing.rs | 28+++++++++++++++++++++++++++-
Mtests/order.rs | 33+++++++++++++++++----------------
7 files changed, 175 insertions(+), 69 deletions(-)

diff --git a/src/cli.rs b/src/cli.rs @@ -349,6 +349,8 @@ pub struct ListingMutationArgs { pub file: PathBuf, #[arg(long)] pub idempotency_key: Option<String>, + #[arg(long = "signer-session-id")] + pub signer_session_id: Option<String>, #[arg(long = "print-job", action = ArgAction::SetTrue)] pub print_job: bool, #[arg(long = "print-event", action = ArgAction::SetTrue)] @@ -423,6 +425,8 @@ pub struct OrderSubmitArgs { pub key: String, #[arg(long)] pub idempotency_key: Option<String>, + #[arg(long = "signer-session-id")] + pub signer_session_id: Option<String>, } #[derive(Debug, Clone, Args)] @@ -705,6 +709,7 @@ mod tests { ListingCommand::Publish(file) => { assert_eq!(file.file.to_str(), Some("draft.toml")); assert!(file.idempotency_key.is_none()); + assert!(file.signer_session_id.is_none()); assert!(!file.print_job); assert!(!file.print_event); } @@ -728,6 +733,7 @@ mod tests { ListingCommand::Archive(file) => { assert_eq!(file.file.to_str(), Some("draft.toml")); assert_eq!(file.idempotency_key.as_deref(), Some("archive-key")); + assert!(file.signer_session_id.is_none()); assert!(file.print_job); assert!(file.print_event); } @@ -736,6 +742,25 @@ mod tests { _ => panic!("unexpected command variant"), } + let listing_update = CliArgs::parse_from([ + "radroots", + "listing", + "update", + "--signer-session-id", + "sess_123", + "draft.toml", + ]); + match listing_update.command { + Command::Listing(args) => match args.command { + ListingCommand::Update(file) => { + assert_eq!(file.file.to_str(), Some("draft.toml")); + assert_eq!(file.signer_session_id.as_deref(), Some("sess_123")); + } + _ => panic!("unexpected listing subcommand"), + }, + _ => panic!("unexpected command variant"), + } + let listing_get = CliArgs::parse_from(["radroots", "listing", "get", "lst_123"]); match listing_get.command { Command::Listing(args) => match args.command { @@ -843,12 +868,15 @@ mod tests { "ord_demo", "--idempotency-key", "submit-1", + "--signer-session-id", + "sess_456", ]); match order_submit.command { Command::Order(args) => match args.command { OrderCommand::Submit(submit) => { assert_eq!(submit.key, "ord_demo"); assert_eq!(submit.idempotency_key.as_deref(), Some("submit-1")); + assert_eq!(submit.signer_session_id.as_deref(), Some("sess_456")); } _ => panic!("unexpected order subcommand"), }, @@ -885,21 +913,15 @@ mod tests { #[test] fn command_contract_helpers_report_supported_modes() { let config_show = CliArgs::parse_from(["radroots", "config", "show"]); - assert!( - config_show - .command - .supports_output_format(OutputFormat::Human) - ); - assert!( - config_show - .command - .supports_output_format(OutputFormat::Json) - ); - assert!( - !config_show - .command - .supports_output_format(OutputFormat::Ndjson) - ); + assert!(config_show + .command + .supports_output_format(OutputFormat::Human)); + assert!(config_show + .command + .supports_output_format(OutputFormat::Json)); + assert!(!config_show + .command + .supports_output_format(OutputFormat::Ndjson)); assert!(config_show.command.supports_dry_run()); let account_new = CliArgs::parse_from(["radroots", "account", "new"]); @@ -910,18 +932,14 @@ mod tests { assert!(find.command.supports_output_format(OutputFormat::Ndjson)); let sync_watch = CliArgs::parse_from(["radroots", "sync", "watch", "--frames", "1"]); - assert!( - sync_watch - .command - .supports_output_format(OutputFormat::Ndjson) - ); + assert!(sync_watch + .command + .supports_output_format(OutputFormat::Ndjson)); let order_watch = CliArgs::parse_from(["radroots", "order", "watch", "ord_demo"]); - assert!( - order_watch - .command - .supports_output_format(OutputFormat::Ndjson) - ); + assert!(order_watch + .command + .supports_output_format(OutputFormat::Ndjson)); let order_submit = CliArgs::parse_from(["radroots", "order", "submit", "ord_demo"]); assert_eq!(order_submit.command.display_name(), "order submit"); diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs @@ -615,6 +615,8 @@ pub struct OrderSubmitView { #[serde(skip_serializing_if = "Option::is_none")] pub idempotency_key: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] + pub requested_signer_session_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub job: Option<OrderJobView>, @@ -777,6 +779,8 @@ pub struct OrderJobView { #[serde(skip_serializing_if = "Option::is_none")] pub command: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] + pub requested_signer_session_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] pub event_id: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub event_addr: Option<String>, @@ -890,6 +894,8 @@ pub struct ListingMutationView { #[serde(skip_serializing_if = "Option::is_none")] pub idempotency_key: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] + pub requested_signer_session_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub job: Option<ListingMutationJobView>, @@ -919,6 +925,8 @@ pub struct ListingMutationJobView { #[serde(skip_serializing_if = "Option::is_none")] pub idempotency_key: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] + pub requested_signer_session_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] pub signer_mode: Option<String>, } diff --git a/src/render/mod.rs b/src/render/mod.rs @@ -8,8 +8,8 @@ use crate::domain::runtime::{ OrderHistoryView, OrderJobView, OrderListView, OrderNewView, OrderSubmitView, OrderWatchView, RelayListView, RpcSessionsView, RpcStatusView, SyncActionView, SyncStatusView, SyncWatchView, }; -use crate::runtime::RuntimeError; use crate::runtime::config::{OutputConfig, OutputFormat}; +use crate::runtime::RuntimeError; const THIN_RULE: &str = "────────────────────────────────────────────────────"; @@ -435,11 +435,19 @@ fn render_ndjson_to(stdout: &mut dyn Write, output: &CommandOutput) -> Result<() } fn yes_no(value: bool) -> &'static str { - if value { "yes" } else { "no" } + if value { + "yes" + } else { + "no" + } } fn present_absent(value: bool) -> &'static str { - if value { "present" } else { "absent" } + if value { + "present" + } else { + "absent" + } } fn render_account_list(stdout: &mut dyn Write, view: &AccountListView) -> Result<(), RuntimeError> { @@ -548,7 +556,10 @@ fn render_config_show( ("secrets dir", view.account.secrets_dir.as_str()), ( "contract default secret backend", - view.account.secret_backend.contract_default_backend.as_str(), + view.account + .secret_backend + .contract_default_backend + .as_str(), ), ( "configured secret backend", @@ -1032,6 +1043,12 @@ fn render_order_submit(stdout: &mut dyn Write, view: &OrderSubmitView) -> Result if let Some(idempotency_key) = &view.idempotency_key { rows.push(("idempotency key", idempotency_key.as_str())); } + if let Some(requested_signer_session_id) = &view.requested_signer_session_id { + rows.push(( + "requested signer session", + requested_signer_session_id.as_str(), + )); + } render_pairs(stdout, "order", rows.as_slice())?; if let Some(job) = &view.job { render_order_job(stdout, job)?; @@ -1205,6 +1222,12 @@ fn render_order_job(stdout: &mut dyn Write, job: &OrderJobView) -> Result<(), Ru if let Some(command) = &job.command { rows.push(("command", command.as_str())); } + if let Some(requested_signer_session_id) = &job.requested_signer_session_id { + rows.push(( + "requested signer session", + requested_signer_session_id.as_str(), + )); + } if let Some(event_id) = &job.event_id { rows.push(("event id", event_id.as_str())); } @@ -1416,6 +1439,12 @@ fn render_listing_mutation( if let Some(signer_mode) = &view.signer_mode { rows.push(("signer", signer_mode.as_str())); } + if let Some(requested_signer_session_id) = &view.requested_signer_session_id { + rows.push(( + "requested signer session", + requested_signer_session_id.as_str(), + )); + } render_pairs(stdout, "listing", rows.as_slice())?; if let Some(reason) = &view.reason { writeln!(stdout, "reason: {reason}")?; @@ -2079,7 +2108,7 @@ fn human_command_name(view: &CommandView) -> &'static str { #[cfg(test)] mod tests { - use super::{Table, render_human_to, render_ndjson_to, render_table}; + use super::{render_human_to, render_ndjson_to, render_table, Table}; use crate::commands::runtime; use crate::domain::runtime::{ AccountListView, CommandOutput, CommandView, DoctorCheckView, DoctorView, MycStatusView, @@ -2184,19 +2213,20 @@ mod tests { "/workspace/.radroots/config.toml" ); assert_eq!(view.account.selector.as_deref(), Some("acct_demo")); - assert!( - view.account - .store_path - .ends_with(".radroots/data/shared/accounts/store.json") - ); + assert!(view + .account + .store_path + .ends_with(".radroots/data/shared/accounts/store.json")); assert_eq!(view.relay.count, 2); assert_eq!(view.relay.publish_policy, "any"); - assert_eq!(view.account.secret_backend.contract_default_backend, "host_vault"); - assert!( - view.local - .replica_db_path - .ends_with(".radroots/data/apps/cli/replica/replica.sqlite") + assert_eq!( + view.account.secret_backend.contract_default_backend, + "host_vault" ); + assert!(view + .local + .replica_db_path + .ends_with(".radroots/data/apps/cli/replica/replica.sqlite")); } #[test] @@ -2309,11 +2339,9 @@ mod tests { )); let mut buffer = Vec::new(); let error = render_ndjson_to(&mut buffer, &output).expect_err("unsupported ndjson"); - assert!( - error - .to_string() - .contains("`config show` does not support --ndjson") - ); + assert!(error + .to_string() + .contains("`config show` does not support --ndjson")); } #[test] diff --git a/src/runtime/listing.rs b/src/runtime/listing.rs @@ -7,7 +7,6 @@ use radroots_core::{ RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, RadrootsCoreUnit, }; -use radroots_events::RadrootsNostrEvent; use radroots_events::kinds::{KIND_LISTING, KIND_LISTING_DRAFT}; use radroots_events::listing::{ RadrootsListing, RadrootsListingAvailability, RadrootsListingBin, @@ -15,9 +14,10 @@ use radroots_events::listing::{ RadrootsListingProduct, RadrootsListingStatus, }; use radroots_events::trade::RadrootsTradeListingValidationError; +use radroots_events::RadrootsNostrEvent; use radroots_events_codec::d_tag::is_d_tag_base64url; use radroots_events_codec::listing::encode::to_wire_parts_with_kind; -use radroots_sql_core::{SqlExecutor, SqliteExecutor, utils}; +use radroots_sql_core::{utils, SqlExecutor, SqliteExecutor}; use radroots_trade::listing::validation::validate_listing_event; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -28,12 +28,12 @@ use crate::domain::runtime::{ ListingMutationEventView, ListingMutationJobView, ListingMutationView, ListingNewView, ListingValidateView, ListingValidationIssueView, SyncFreshnessView, }; -use crate::runtime::RuntimeError; use crate::runtime::accounts; use crate::runtime::config::RuntimeConfig; use crate::runtime::daemon; use crate::runtime::daemon::DaemonRpcError; use crate::runtime::sync::freshness_from_executor; +use crate::runtime::RuntimeError; const DRAFT_KIND: &str = "listing_draft_v1"; const LISTING_SOURCE: &str = "local draft · local first"; @@ -522,12 +522,14 @@ fn mutate( event_id: None, event_addr: Some(listing_addr.clone()), idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some("dry run requested; daemon publish skipped".to_owned()), job: args.print_job.then(|| ListingMutationJobView { rpc_method: "bridge.listing.publish".to_owned(), state: "not_submitted".to_owned(), job_id: None, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), signer_mode: Some(config.signer.backend.as_str().to_owned()), }), event: args.print_event.then_some(event_preview), @@ -584,6 +586,7 @@ fn mutate( .clone() .or_else(|| Some(listing_addr.clone())), idempotency_key: result.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: failed.then(|| { "daemon publish job failed before relay delivery completed".to_owned() }), @@ -592,6 +595,7 @@ fn mutate( state: result.status, job_id: Some(result.job_id), idempotency_key: result.idempotency_key, + requested_signer_session_id: args.signer_session_id.clone(), signer_mode: Some(result.signer_mode), }), event: args.print_event.then(|| ListingMutationEventView { @@ -974,12 +978,14 @@ fn daemon_error_view( event_id: None, event_addr: None, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some(reason), job: args.print_job.then(|| ListingMutationJobView { rpc_method: "bridge.listing.publish".to_owned(), state: "unconfigured".to_owned(), job_id: None, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), signer_mode: Some(config.signer.backend.as_str().to_owned()), }), event: args.print_event.then_some(event_preview), @@ -1005,12 +1011,14 @@ fn daemon_error_view( event_id: None, event_addr: None, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some(reason), job: args.print_job.then(|| ListingMutationJobView { rpc_method: "bridge.listing.publish".to_owned(), state: "unavailable".to_owned(), job_id: None, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), signer_mode: Some(config.signer.backend.as_str().to_owned()), }), event: args.print_event.then_some(event_preview), @@ -1035,12 +1043,14 @@ fn daemon_error_view( event_id: None, event_addr: None, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some(reason), job: args.print_job.then(|| ListingMutationJobView { rpc_method: "bridge.listing.publish".to_owned(), state: "error".to_owned(), job_id: None, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), signer_mode: Some(config.signer.backend.as_str().to_owned()), }), event: args.print_event.then_some(event_preview), @@ -1288,7 +1298,7 @@ fn encode_base64url_no_pad(bytes: [u8; 16]) -> String { #[cfg(test)] mod tests { - use super::{DRAFT_KIND, ListingDraftDocument, encode_base64url_no_pad, generate_d_tag}; + use super::{encode_base64url_no_pad, generate_d_tag, ListingDraftDocument, DRAFT_KIND}; use radroots_events_codec::d_tag::is_d_tag_base64url; #[test] diff --git a/src/runtime/order.rs b/src/runtime/order.rs @@ -16,10 +16,10 @@ use crate::domain::runtime::{ OrderIssueView, OrderJobView, OrderListView, OrderNewView, OrderSubmitView, OrderSummaryView, OrderWatchFrameView, OrderWatchView, }; -use crate::runtime::RuntimeError; use crate::runtime::accounts; use crate::runtime::config::RuntimeConfig; use crate::runtime::daemon::{self, DaemonRpcError}; +use crate::runtime::RuntimeError; const ORDER_DRAFT_KIND: &str = "order_draft_v1"; const ORDER_SOURCE: &str = "local order drafts · local first"; @@ -278,6 +278,7 @@ pub fn submit( dry_run: false, deduplicated: false, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some(format!("order draft `{}` was not found", args.key)), job: None, issues: Vec::new(), @@ -304,6 +305,7 @@ pub fn submit( dry_run: false, deduplicated: false, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some(reason), job: None, issues: Vec::new(), @@ -331,6 +333,7 @@ pub fn submit( dry_run: false, deduplicated: false, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some("order draft already has a recorded submission job".to_owned()), job: Some(job), issues: Vec::new(), @@ -358,6 +361,7 @@ pub fn submit( dry_run: false, deduplicated: false, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some("order draft is not ready for durable submit".to_owned()), job: None, issues, @@ -379,11 +383,13 @@ pub fn submit( dry_run: true, deduplicated: false, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some("dry run requested; daemon order submission skipped".to_owned()), job: Some(OrderJobView { job_id: "not_submitted".to_owned(), state: "not_submitted".to_owned(), command: Some("order.submit".to_owned()), + requested_signer_session_id: args.signer_session_id.clone(), event_id: None, event_addr: None, reason: None, @@ -442,6 +448,7 @@ pub fn submit( dry_run: false, deduplicated: result.deduplicated, idempotency_key: result.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: failed.then(|| { "daemon order request failed before relay delivery completed".to_owned() }), @@ -449,6 +456,7 @@ pub fn submit( job_id: result.job_id, state: result.status, command: Some("order.submit".to_owned()), + requested_signer_session_id: args.signer_session_id.clone(), event_id: result.event_id, event_addr: result.event_addr, reason: None, @@ -1005,6 +1013,7 @@ fn submission_job_view( job_id, state: job.state, command: Some(job.command), + requested_signer_session_id: None, event_id: job.event_id, event_addr: job.event_addr, reason: None, @@ -1013,6 +1022,7 @@ fn submission_job_view( job_id, state: "missing".to_owned(), command: submission.command.clone(), + requested_signer_session_id: None, event_id: submission.event_id.clone(), event_addr: submission.event_addr.clone(), reason: Some("recorded job id was not found in radrootsd".to_owned()), @@ -1029,6 +1039,7 @@ fn recorded_job_view(submission: &OrderDraftSubmission, job_id: String) -> Order .clone() .unwrap_or_else(|| "recorded".to_owned()), command: submission.command.clone(), + requested_signer_session_id: None, event_id: submission.event_id.clone(), event_addr: submission.event_addr.clone(), reason: None, @@ -1043,6 +1054,7 @@ fn job_view_from_error(job_id: String, error: DaemonRpcError) -> OrderJobView { job_id, state: "unconfigured".to_owned(), command: None, + requested_signer_session_id: None, event_id: None, event_addr: None, reason: Some(reason), @@ -1051,6 +1063,7 @@ fn job_view_from_error(job_id: String, error: DaemonRpcError) -> OrderJobView { job_id, state: "unavailable".to_owned(), command: None, + requested_signer_session_id: None, event_id: None, event_addr: None, reason: Some(reason), @@ -1061,6 +1074,7 @@ fn job_view_from_error(job_id: String, error: DaemonRpcError) -> OrderJobView { job_id, state: "error".to_owned(), command: None, + requested_signer_session_id: None, event_id: None, event_addr: None, reason: Some(reason), @@ -1111,6 +1125,7 @@ fn order_submit_error_view( dry_run: false, deduplicated: false, idempotency_key: args.idempotency_key.clone(), + requested_signer_session_id: args.signer_session_id.clone(), reason: Some(reason), job: None, issues: Vec::new(), @@ -1350,8 +1365,8 @@ impl From<OrderGetView> for OrderNewView { #[cfg(test)] mod tests { use super::{ - ORDER_DRAFT_KIND, OrderDraft, OrderDraftDocument, OrderDraftItem, OrderDraftSubmission, - next_order_id, + next_order_id, OrderDraft, OrderDraftDocument, OrderDraftItem, OrderDraftSubmission, + ORDER_DRAFT_KIND, }; #[test] diff --git a/tests/listing.rs b/tests/listing.rs @@ -10,7 +10,7 @@ use std::time::Duration; use assert_cmd::prelude::*; use radroots_sql_core::{SqlExecutor, SqliteExecutor}; -use serde_json::{Value, json}; +use serde_json::{json, Value}; use tempfile::tempdir; fn data_root(workdir: &Path) -> std::path::PathBuf { @@ -334,6 +334,8 @@ fn listing_publish_and_update_use_durable_bridge_publish() { "publish", "--idempotency-key", "publish-key", + "--signer-session-id", + "sess_publish_01", "--print-job", "--print-event", draft_path.to_str().expect("draft path"), @@ -349,6 +351,14 @@ fn listing_publish_and_update_use_durable_bridge_publish() { assert_eq!(publish_json["event_id"], "event_listing_01"); assert_eq!(publish_json["event"]["kind"], 30402); assert_eq!(publish_json["job"]["rpc_method"], "bridge.listing.publish"); + assert_eq!( + publish_json["requested_signer_session_id"], + "sess_publish_01" + ); + assert_eq!( + publish_json["job"]["requested_signer_session_id"], + "sess_publish_01" + ); let update_output = cli_command_in(dir.path()) .env("RADROOTS_RPC_URL", server.url()) @@ -441,6 +451,8 @@ fn listing_archive_and_dry_run_are_truthful() { "--json", "listing", "archive", + "--signer-session-id", + "sess_archive_01", draft_path.to_str().expect("draft path"), ]) .output() @@ -450,6 +462,10 @@ fn listing_archive_and_dry_run_are_truthful() { serde_json::from_slice(archive_output.stdout.as_slice()).expect("archive json"); assert_eq!(archive_json["operation"], "archive"); assert_eq!(archive_json["job_status"], "published"); + assert_eq!( + archive_json["requested_signer_session_id"], + "sess_archive_01" + ); let dry_run_output = cli_command_in(dir.path()) .args([ @@ -457,6 +473,8 @@ fn listing_archive_and_dry_run_are_truthful() { "--dry-run", "listing", "publish", + "--signer-session-id", + "sess_dry_run_01", "--print-event", "--print-job", draft_path.to_str().expect("draft path"), @@ -471,6 +489,14 @@ fn listing_archive_and_dry_run_are_truthful() { assert_eq!(dry_run_json["job"]["state"], "not_submitted"); assert_eq!(dry_run_json["event"]["kind"], 30402); assert!(dry_run_json["event"]["event_id"].is_null()); + assert_eq!( + dry_run_json["requested_signer_session_id"], + "sess_dry_run_01" + ); + assert_eq!( + dry_run_json["job"]["requested_signer_session_id"], + "sess_dry_run_01" + ); let recorded = requests.lock().expect("requests"); assert_eq!(recorded.len(), 1); diff --git a/tests/order.rs b/tests/order.rs @@ -507,6 +507,8 @@ fn order_submit_persists_submission_metadata_and_reports_job() { order_id, "--idempotency-key", "order-submit-1", + "--signer-session-id", + "sess_order_01", ]) .output() .expect("run order submit"); @@ -516,6 +518,11 @@ fn order_submit_persists_submission_metadata_and_reports_job() { assert_eq!(submit_json["state"], "accepted"); assert_eq!(submit_json["job"]["job_id"], "job_order_01"); assert_eq!(submit_json["job"]["command"], "order.submit"); + assert_eq!(submit_json["requested_signer_session_id"], "sess_order_01"); + assert_eq!( + submit_json["job"]["requested_signer_session_id"], + "sess_order_01" + ); let contents = fs::read_to_string(file).expect("read updated order draft"); assert!(contents.contains("job_id = \"job_order_01\"")); @@ -523,16 +530,12 @@ fn order_submit_persists_submission_metadata_and_reports_job() { assert!(contents.contains("command = \"order.submit\"")); let recorded_requests = requests.lock().expect("requests lock"); - assert!( - recorded_requests - .iter() - .any(|request| request.method == "bridge.order.request") - ); - assert!( - recorded_requests - .iter() - .any(|request| { request.auth_header.as_deref() == Some("Bearer test-token") }) - ); + assert!(recorded_requests + .iter() + .any(|request| request.method == "bridge.order.request")); + assert!(recorded_requests + .iter() + .any(|request| { request.auth_header.as_deref() == Some("Bearer test-token") })); } #[test] @@ -693,10 +696,8 @@ command = "order.submit" assert_eq!(output.status.code(), Some(3)); let json: Value = serde_json::from_slice(output.stdout.as_slice()).expect("cancel json"); assert_eq!(json["state"], "unconfigured"); - assert!( - json["reason"] - .as_str() - .expect("cancel reason") - .contains("trade-chain") - ); + assert!(json["reason"] + .as_str() + .expect("cancel reason") + .contains("trade-chain")); }