commit 70335dcc0cabd9e3a94fc16c42ae64958a3f0ea8
parent 0c42bc124066d5f728c5408bc51113dc9676f2f5
Author: triesap <tyson@radroots.org>
Date: Mon, 27 Apr 2026 05:08:12 +0000
cli: add signer runtime test harness
- add shared cli sandbox integration helpers
- add fake myc executable fixture support
- prove local and myc signer status smoke paths
- reuse sandbox setup in target cli integration tests
Diffstat:
3 files changed, 147 insertions(+), 36 deletions(-)
diff --git a/tests/signer_runtime_modes.rs b/tests/signer_runtime_modes.rs
@@ -0,0 +1,32 @@
+mod support;
+
+use support::RadrootsCliSandbox;
+
+#[test]
+fn harness_runs_local_signer_status_with_json_envelope() {
+ let sandbox = RadrootsCliSandbox::new();
+
+ let value = sandbox.json_success(&["--format", "json", "signer", "status", "get"]);
+
+ assert_eq!(value["schema_version"], "radroots.cli.output.v1");
+ assert_eq!(value["operation_id"], "signer.status.get");
+ assert_eq!(value["kind"], "signer.status.get");
+ assert_eq!(value["result"]["mode"], "local");
+}
+
+#[cfg(unix)]
+#[test]
+fn harness_runs_myc_signer_status_with_fake_executable() {
+ let sandbox = RadrootsCliSandbox::new();
+ let myc = sandbox.write_fake_myc("myc-invalid-json", "printf 'not json\\n'");
+ sandbox.write_app_config(&format!(
+ "[signer]\nmode = \"myc\"\n\n[myc]\nexecutable = \"{}\"\n",
+ myc.display()
+ ));
+
+ let value = sandbox.json_success(&["--format", "json", "signer", "status", "get"]);
+
+ assert_eq!(value["operation_id"], "signer.status.get");
+ assert_eq!(value["result"]["mode"], "myc");
+ assert_eq!(value["result"]["myc"]["state"], "unavailable");
+}
diff --git a/tests/support/mod.rs b/tests/support/mod.rs
@@ -0,0 +1,98 @@
+#![allow(dead_code)]
+
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::process::{Command, Output};
+
+use assert_cmd::prelude::*;
+use serde_json::Value;
+use tempfile::TempDir;
+
+#[cfg(unix)]
+use std::os::unix::fs::PermissionsExt;
+
+pub fn radroots() -> Command {
+ Command::cargo_bin("radroots").expect("binary")
+}
+
+pub fn json_from_stdout(output: &Output) -> Value {
+ serde_json::from_slice(&output.stdout).unwrap_or_else(|error| {
+ panic!(
+ "stdout was not json: {error}; stderr `{}`; stdout `{}`",
+ String::from_utf8_lossy(&output.stderr),
+ String::from_utf8_lossy(&output.stdout)
+ )
+ })
+}
+
+pub struct RadrootsCliSandbox {
+ root: TempDir,
+}
+
+impl RadrootsCliSandbox {
+ pub fn new() -> Self {
+ Self {
+ root: TempDir::new().expect("tempdir"),
+ }
+ }
+
+ pub fn root(&self) -> &Path {
+ self.root.path()
+ }
+
+ pub fn command(&self) -> Command {
+ let mut command = radroots();
+ self.apply_base_env(&mut command);
+ command
+ }
+
+ pub fn json_success(&self, args: &[&str]) -> Value {
+ let output = self.command().args(args).output().expect("run command");
+ assert!(
+ output.status.success(),
+ "`{args:?}` failed with stderr `{}` and stdout `{}`",
+ String::from_utf8_lossy(&output.stderr),
+ String::from_utf8_lossy(&output.stdout)
+ );
+ json_from_stdout(&output)
+ }
+
+ pub fn json_output(&self, args: &[&str]) -> (Output, Value) {
+ let output = self.command().args(args).output().expect("run command");
+ let value = json_from_stdout(&output);
+ (output, value)
+ }
+
+ pub fn write_workspace_config(&self, raw: &str) -> PathBuf {
+ let path = self.root.path().join("config.toml");
+ fs::write(&path, raw).expect("write workspace config");
+ path
+ }
+
+ pub fn write_app_config(&self, raw: &str) -> PathBuf {
+ let path = self.root.path().join("config/apps/cli/config.toml");
+ fs::create_dir_all(path.parent().expect("app config parent")).expect("app config dir");
+ fs::write(&path, raw).expect("write app config");
+ path
+ }
+
+ #[cfg(unix)]
+ pub fn write_fake_myc(&self, name: &str, body: &str) -> PathBuf {
+ let path = self.root.path().join("bin").join(name);
+ fs::create_dir_all(path.parent().expect("fake myc parent")).expect("fake myc dir");
+ fs::write(&path, format!("#!/bin/sh\nset -eu\n{body}\n")).expect("write fake myc");
+ let mut permissions = fs::metadata(&path)
+ .expect("fake myc metadata")
+ .permissions();
+ permissions.set_mode(0o755);
+ fs::set_permissions(&path, permissions).expect("fake myc executable");
+ path
+ }
+
+ fn apply_base_env(&self, command: &mut Command) {
+ command.env("RADROOTS_CLI_PATHS_PROFILE", "repo_local");
+ command.env("RADROOTS_CLI_PATHS_REPO_LOCAL_ROOT", self.root.path());
+ command.env("RADROOTS_ACCOUNT_SECRET_BACKEND", "encrypted_file");
+ command.env("RADROOTS_ACCOUNT_SECRET_FALLBACK", "none");
+ }
+}
diff --git a/tests/target_cli.rs b/tests/target_cli.rs
@@ -1,33 +1,14 @@
-use std::process::Command;
+mod support;
-use assert_cmd::prelude::*;
use serde_json::Value;
-use tempfile::TempDir;
+
+use support::{RadrootsCliSandbox, radroots};
const LISTING_ADDR: &str =
"30402:1111111111111111111111111111111111111111111111111111111111111111:AAAAAAAAAAAAAAAAAAAAAg";
-fn radroots() -> Command {
- Command::cargo_bin("radroots").expect("binary")
-}
-
-fn radroots_in(root: &TempDir) -> Command {
- let mut command = radroots();
- command.env("RADROOTS_CLI_PATHS_PROFILE", "repo_local");
- command.env("RADROOTS_CLI_PATHS_REPO_LOCAL_ROOT", root.path());
- command
-}
-
-fn json_success(root: &TempDir, args: &[&str]) -> Value {
- let output = radroots_in(root).args(args).output().expect("run command");
-
- assert!(
- output.status.success(),
- "`{args:?}` failed with stderr `{}` and stdout `{}`",
- String::from_utf8_lossy(&output.stderr),
- String::from_utf8_lossy(&output.stdout)
- );
- serde_json::from_slice(&output.stdout).expect("json envelope")
+fn json_success(sandbox: &RadrootsCliSandbox, args: &[&str]) -> Value {
+ sandbox.json_success(args)
}
#[test]
@@ -162,24 +143,24 @@ fn required_approval_missing_token_returns_structured_error() {
#[test]
fn buyer_mvp_flow_acceptance_uses_target_operations() {
- let root = TempDir::new().expect("tempdir");
+ let sandbox = RadrootsCliSandbox::new();
let search = json_success(
- &root,
+ &sandbox,
&["--format", "json", "market", "product", "search", "eggs"],
);
assert_eq!(search["operation_id"], "market.product.search");
assert_eq!(search["errors"].as_array().expect("errors").len(), 0);
let create = json_success(
- &root,
+ &sandbox,
&["--format", "json", "basket", "create", "basket_flow"],
);
assert_eq!(create["operation_id"], "basket.create");
assert_eq!(create["result"]["basket_id"], "basket_flow");
let add = json_success(
- &root,
+ &sandbox,
&[
"--format",
"json",
@@ -199,7 +180,7 @@ fn buyer_mvp_flow_acceptance_uses_target_operations() {
assert_eq!(add["result"]["ready_for_quote"], true);
let quote = json_success(
- &root,
+ &sandbox,
&[
"--format",
"json",
@@ -216,7 +197,7 @@ fn buyer_mvp_flow_acceptance_uses_target_operations() {
.expect("order id");
let submit = json_success(
- &root,
+ &sandbox,
&["--format", "json", "--dry-run", "order", "submit", order_id],
);
assert_eq!(submit["operation_id"], "order.submit");
@@ -226,12 +207,12 @@ fn buyer_mvp_flow_acceptance_uses_target_operations() {
#[test]
fn seller_mvp_flow_acceptance_uses_target_operations() {
- let root = TempDir::new().expect("tempdir");
- let listing_file = root.path().join("listing.toml");
+ let sandbox = RadrootsCliSandbox::new();
+ let listing_file = sandbox.root().join("listing.toml");
let listing_file = listing_file.to_string_lossy().into_owned();
let create = json_success(
- &root,
+ &sandbox,
&[
"--format",
"json",
@@ -265,7 +246,7 @@ fn seller_mvp_flow_acceptance_uses_target_operations() {
assert_eq!(create["result"]["file"], listing_file);
let validate = json_success(
- &root,
+ &sandbox,
&[
"--format",
"json",
@@ -278,7 +259,7 @@ fn seller_mvp_flow_acceptance_uses_target_operations() {
assert!(validate["result"]["valid"].is_boolean());
let publish = json_success(
- &root,
+ &sandbox,
&[
"--format",
"json",
@@ -291,7 +272,7 @@ fn seller_mvp_flow_acceptance_uses_target_operations() {
assert_eq!(publish["operation_id"], "listing.publish");
assert_eq!(publish["result"]["state"], "dry_run");
- let orders = json_success(&root, &["--format", "json", "order", "list"]);
+ let orders = json_success(&sandbox, &["--format", "json", "order", "list"]);
assert_eq!(orders["operation_id"], "order.list");
assert_eq!(orders["errors"].as_array().expect("errors").len(), 0);
}