cli

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

commit d922da2e8d578d271b32ad8a85ddc5acb962c2c8
parent 06cabe28e7f045d360c01ce3cb07371452182498
Author: triesap <tyson@radroots.org>
Date:   Tue, 28 Apr 2026 00:53:45 +0000

docs: align buyer order cli contract

Diffstat:
Mtests/target_cli.rs | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 95 insertions(+), 10 deletions(-)

diff --git a/tests/target_cli.rs b/tests/target_cli.rs @@ -110,6 +110,24 @@ fn removed_command_families_are_rejected_publicly() { } #[test] +fn seller_order_decision_commands_are_deferred() { + for args in [ + ["order", "accept", "ord_deferred"].as_slice(), + ["order", "decline", "ord_deferred"].as_slice(), + ["order", "decision", "accept", "ord_deferred"].as_slice(), + ] { + let output = radroots() + .args(args) + .output() + .expect("run deferred seller order decision command"); + + assert!(!output.status.success(), "`{args:?}` should be rejected"); + let stderr = String::from_utf8(output.stderr).expect("utf8 stderr"); + assert!(stderr.contains("unrecognized subcommand")); + } +} + +#[test] fn target_outputs_do_not_suggest_removed_command_families() { let sandbox = RadrootsCliSandbox::new(); @@ -390,18 +408,33 @@ fn unsupported_ndjson_returns_structured_invalid_input() { #[test] fn offline_forbids_external_network_operations() { - let output = radroots() - .args(["--format", "json", "--offline", "sync", "pull"]) - .output() - .expect("run offline sync pull"); + for (operation_id, args) in [ + ( + "sync.pull", + ["--format", "json", "--offline", "sync", "pull"].as_slice(), + ), + ( + "market.refresh", + ["--format", "json", "--offline", "market", "refresh"].as_slice(), + ), + ( + "order.submit", + ["--format", "json", "--offline", "order", "submit"].as_slice(), + ), + ] { + let output = radroots() + .args(args) + .output() + .expect("run offline external command"); - assert!(!output.status.success()); - let value: Value = serde_json::from_slice(&output.stdout).expect("json envelope"); + assert!(!output.status.success()); + let value: Value = serde_json::from_slice(&output.stdout).expect("json envelope"); - assert_eq!(value["operation_id"], "sync.pull"); - assert_eq!(value["result"], Value::Null); - assert_eq!(value["errors"][0]["code"], "offline_forbidden"); - assert_eq!(value["errors"][0]["exit_code"], 8); + assert_eq!(value["operation_id"], operation_id); + assert_eq!(value["result"], Value::Null); + assert_eq!(value["errors"][0]["code"], "offline_forbidden"); + assert_eq!(value["errors"][0]["exit_code"], 8); + } } #[test] @@ -775,6 +808,31 @@ fn buyer_market_sync_basket_dry_runs_preflight_without_mutating_local_state() { assert_eq!(sync_push["result"]["state"], "unconfigured"); assert_eq!(sync_push["result"]["replica_db"], "missing"); + sandbox.json_success(&["--format", "json", "store", "init"]); + let relay_refresh = sandbox.json_success(&[ + "--format", + "json", + "--relay", + "ws://127.0.0.1:9", + "--dry-run", + "market", + "refresh", + ]); + assert_eq!(relay_refresh["operation_id"], "market.refresh"); + assert_eq!(relay_refresh["dry_run"], true); + assert_eq!(relay_refresh["result"]["state"], "ready"); + assert_eq!( + relay_refresh["result"]["target_relays"][0], + "ws://127.0.0.1:9" + ); + assert_eq!(relay_refresh["result"]["fetched_count"], 0); + assert_eq!(relay_refresh["result"]["ingested_count"], 0); + + let empty_search = + sandbox.json_success(&["--format", "json", "market", "product", "search", "eggs"]); + assert_eq!(empty_search["operation_id"], "market.product.search"); + assert_eq!(empty_search["result"]["state"], "empty"); + let create_dry_run = sandbox.json_success(&[ "--format", "json", @@ -1046,6 +1104,11 @@ fn buyer_target_flow_acceptance_uses_target_operations() { 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["result"]["event_id"], Value::Null); + assert_eq!(submit["result"]["event_kind"], Value::Null); + assert_eq!(submit["result"]["target_relays"], Value::Null); + assert_eq!(submit["result"]["acknowledged_relays"], Value::Null); + assert_eq!(submit["result"]["failed_relays"], Value::Null); 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"]); @@ -1172,6 +1235,28 @@ fn ready_order_submit_dry_run_validates_local_buyer_authority() { assert_eq!(mismatch["errors"][0]["detail"]["class"], "account"); assert_no_removed_command_reference(&mismatch, &["order", "submit", "--dry-run"]); assert_no_daemon_runtime_reference(&mismatch, &["order", "submit", "--dry-run"]); + + let (network_output, network_mismatch) = sandbox.json_output(&[ + "--format", + "json", + "--account-id", + second_account_id, + "--relay", + "ws://127.0.0.1:9", + "--approval-token", + "approve", + "order", + "submit", + order_id, + ]); + + assert!(!network_output.status.success()); + assert_eq!(network_output.status.code(), Some(5)); + assert_eq!(network_mismatch["operation_id"], "order.submit"); + assert_eq!(network_mismatch["result"], Value::Null); + assert_eq!(network_mismatch["errors"][0]["code"], "account_mismatch"); + assert_eq!(network_mismatch["errors"][0]["detail"]["class"], "account"); + assert_no_daemon_runtime_reference(&network_mismatch, &["order", "submit"]); } #[test]