commit 39d391e2bec5e3fad32b2d1b8e82a5753622cc91
parent d7b6a67dc1b5532371760afc2c7da566dd50b35a
Author: triesap <tyson@radroots.org>
Date: Tue, 7 Apr 2026 04:07:48 +0000
reconcile signer operator surfaces
Diffstat:
13 files changed, 160 insertions(+), 67 deletions(-)
diff --git a/src/cli.rs b/src/cli.rs
@@ -36,7 +36,7 @@ pub struct CliArgs {
#[arg(long, global = true)]
pub identity_path: Option<PathBuf>,
#[arg(long, global = true)]
- pub signer_backend: Option<String>,
+ pub signer: Option<String>,
#[arg(long, global = true)]
pub myc_executable: Option<PathBuf>,
#[command(subcommand)]
@@ -381,7 +381,7 @@ mod tests {
"--log-stdout",
"--identity-path",
"identity.local.json",
- "--signer-backend",
+ "--signer",
"myc",
"--myc-executable",
"bin/myc",
@@ -413,7 +413,7 @@ mod tests {
.and_then(|path| path.to_str()),
Some("identity.local.json")
);
- assert_eq!(parsed.signer_backend.as_deref(), Some("myc"));
+ assert_eq!(parsed.signer.as_deref(), Some("myc"));
assert_eq!(
parsed
.myc_executable
diff --git a/src/commands/doctor.rs b/src/commands/doctor.rs
@@ -160,17 +160,13 @@ fn account_check(config: &RuntimeConfig) -> Result<EvaluatedCheck, RuntimeError>
fn signer_check(signer: &crate::domain::runtime::SignerStatusView) -> EvaluatedCheck {
let (severity, detail, action) = match signer.state.as_str() {
- "ready" => (
- DoctorSeverity::Ok,
- format!("{} ready", signer.backend),
- None,
- ),
+ "ready" => (DoctorSeverity::Ok, format!("{} ready", signer.mode), None),
"unconfigured" => (
DoctorSeverity::Warn,
signer
.reason
.clone()
- .unwrap_or_else(|| format!("{} signer is not configured", signer.backend)),
+ .unwrap_or_else(|| format!("{} signer is not configured", signer.mode)),
Some("radroots signer status"),
),
"degraded" | "unavailable" => (
@@ -178,8 +174,8 @@ fn signer_check(signer: &crate::domain::runtime::SignerStatusView) -> EvaluatedC
signer
.reason
.clone()
- .unwrap_or_else(|| format!("{} signer is unavailable", signer.backend)),
- Some(if signer.backend == "myc" {
+ .unwrap_or_else(|| format!("{} signer is unavailable", signer.mode)),
+ Some(if signer.mode == "myc" {
"radroots myc status"
} else {
"radroots signer status"
@@ -190,7 +186,7 @@ fn signer_check(signer: &crate::domain::runtime::SignerStatusView) -> EvaluatedC
signer
.reason
.clone()
- .unwrap_or_else(|| format!("{} signer reported an internal error", signer.backend)),
+ .unwrap_or_else(|| format!("{} signer reported an internal error", signer.mode)),
Some("radroots signer status --json"),
),
};
diff --git a/src/commands/runtime.rs b/src/commands/runtime.rs
@@ -44,7 +44,7 @@ pub fn show(config: &RuntimeConfig, logging: &LoggingState) -> ConfigShowView {
legacy_identity_path: config.identity.path.display().to_string(),
},
signer: SignerRuntimeView {
- backend: config.signer.backend.as_str().to_owned(),
+ mode: config.signer.backend.as_str().to_owned(),
},
myc: MycRuntimeView {
executable: config.myc.executable.display().to_string(),
diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs
@@ -131,7 +131,7 @@ pub struct AccountRuntimeView {
#[derive(Debug, Clone, Serialize)]
pub struct SignerRuntimeView {
- pub backend: String,
+ pub mode: String,
}
#[derive(Debug, Clone, Serialize)]
@@ -247,10 +247,16 @@ pub struct AccountListView {
#[derive(Debug, Clone, Serialize)]
pub struct SignerStatusView {
- pub backend: String,
+ pub mode: String,
pub state: String,
+ pub source: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub account_id: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub local: Option<LocalSignerStatusView>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub myc: Option<MycStatusView>,
}
@@ -278,8 +284,10 @@ pub struct LocalSignerStatusView {
pub struct MycStatusView {
pub executable: String,
pub state: String,
+ pub source: String,
pub service_status: Option<String>,
pub ready: bool,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reasons: Vec<String>,
diff --git a/src/render/mod.rs b/src/render/mod.rs
@@ -69,7 +69,7 @@ fn render_human_to(stdout: &mut dyn Write, output: &CommandOutput) -> Result<(),
writeln!(stdout, "source: {}", view.source)?;
}
CommandView::MycStatus(view) => {
- render_myc_status(stdout, view)?;
+ render_myc_status(stdout, view, true)?;
}
CommandView::ConfigShow(view) => {
render_config_show(stdout, view)?;
@@ -78,17 +78,35 @@ fn render_human_to(stdout: &mut dyn Write, output: &CommandOutput) -> Result<(),
render_doctor(stdout, view)?;
}
CommandView::SignerStatus(view) => {
- writeln!(stdout, "signer")?;
- writeln!(stdout, " backend: {}", view.backend)?;
- writeln!(stdout, " state: {}", view.state)?;
+ write_context(
+ stdout,
+ match view.state.as_str() {
+ "ready" => "signer · active",
+ "unconfigured" => "signer · unconfigured",
+ "degraded" => "signer · degraded",
+ "unavailable" => "signer · unavailable",
+ _ => "signer · error",
+ },
+ )?;
+ let mut signer_rows = vec![
+ ("mode", view.mode.as_str()),
+ ("status", view.state.as_str()),
+ ];
+ if let Some(account_id) = &view.account_id {
+ signer_rows.push(("account id", account_id.as_str()));
+ }
+ render_pairs(stdout, "signer", signer_rows.as_slice())?;
if let Some(reason) = &view.reason {
- writeln!(stdout, " reason: {reason}")?;
+ writeln!(stdout, "reason: {reason}")?;
}
+ writeln!(stdout, "source: {}", view.source)?;
if let Some(local) = &view.local {
- render_local_signer(stdout, "local signer", local)?;
+ writeln!(stdout)?;
+ render_local_signer(stdout, "local account", local)?;
}
if let Some(myc) = &view.myc {
- render_myc_status(stdout, myc)?;
+ writeln!(stdout)?;
+ render_myc_status(stdout, myc, false)?;
}
}
}
@@ -255,11 +273,7 @@ fn render_config_show(
account_rows.insert(0, ("selector", selector.as_str()));
}
render_pairs(stdout, "account", account_rows.as_slice())?;
- render_pairs(
- stdout,
- "signer",
- &[("backend", view.signer.backend.as_str())],
- )?;
+ render_pairs(stdout, "signer", &[("mode", view.signer.mode.as_str())])?;
render_pairs(
stdout,
"myc",
@@ -388,24 +402,33 @@ fn render_local_signer(
fn render_myc_status(
stdout: &mut dyn Write,
view: &crate::domain::runtime::MycStatusView,
+ standalone: bool,
) -> Result<(), RuntimeError> {
- writeln!(stdout, "myc")?;
- writeln!(stdout, " executable: {}", view.executable)?;
- writeln!(stdout, " state: {}", view.state)?;
- writeln!(stdout, " ready: {}", yes_no(view.ready))?;
+ if standalone {
+ write_context(stdout, format!("myc · {}", view.state).as_str())?;
+ }
+ let mut rows = vec![
+ ("executable", view.executable.as_str()),
+ ("status", view.state.as_str()),
+ ("ready", yes_no(view.ready)),
+ ];
if let Some(service_status) = &view.service_status {
- writeln!(stdout, " service status: {service_status}")?;
+ rows.push(("service status", service_status.as_str()));
}
+ render_pairs(stdout, "myc", rows.as_slice())?;
if let Some(reason) = &view.reason {
- writeln!(stdout, " reason: {reason}")?;
+ writeln!(stdout, "reason: {reason}")?;
}
if !view.reasons.is_empty() {
- writeln!(stdout, " reasons: {}", view.reasons.join(" | "))?;
+ writeln!(stdout, "reasons: {}", view.reasons.join(" | "))?;
}
+ writeln!(stdout, "source: {}", view.source)?;
if let Some(local_signer) = &view.local_signer {
+ writeln!(stdout)?;
render_local_signer(stdout, "myc local signer", local_signer)?;
}
if let Some(custody) = &view.custody {
+ writeln!(stdout)?;
render_myc_custody_identity(stdout, "myc custody signer", &custody.signer)?;
render_myc_custody_identity(stdout, "myc custody user", &custody.user)?;
if let Some(discovery_app) = &custody.discovery_app {
@@ -560,6 +583,7 @@ mod tests {
let output = CommandOutput::success(CommandView::MycStatus(MycStatusView {
executable: "myc".to_owned(),
state: "unavailable".to_owned(),
+ source: "myc status command · local first".to_owned(),
service_status: None,
ready: false,
reason: None,
diff --git a/src/runtime/config.rs b/src/runtime/config.rs
@@ -21,7 +21,7 @@ const ENV_LOG_DIR: &str = "RADROOTS_LOG_DIR";
const ENV_LOG_STDOUT: &str = "RADROOTS_LOG_STDOUT";
const ENV_ACCOUNT: &str = "RADROOTS_ACCOUNT";
const ENV_IDENTITY_PATH: &str = "RADROOTS_IDENTITY_PATH";
-const ENV_SIGNER_BACKEND: &str = "RADROOTS_SIGNER_BACKEND";
+const ENV_SIGNER: &str = "RADROOTS_SIGNER";
const ENV_MYC_EXECUTABLE: &str = "RADROOTS_MYC_EXECUTABLE";
const SUPPORTED_ENV_FILE_KEYS: &[&str] = &[
ENV_OUTPUT,
@@ -33,7 +33,7 @@ const SUPPORTED_ENV_FILE_KEYS: &[&str] = &[
ENV_LOG_STDOUT,
ENV_ACCOUNT,
ENV_IDENTITY_PATH,
- ENV_SIGNER_BACKEND,
+ ENV_SIGNER,
ENV_MYC_EXECUTABLE,
];
@@ -229,10 +229,10 @@ impl RuntimeConfig {
},
signer: SignerConfig {
backend: args
- .signer_backend
+ .signer
.clone()
- .or_else(|| env_value(env, env_file, &[ENV_SIGNER_BACKEND]))
- .map(parse_signer_backend)
+ .or_else(|| env_value(env, env_file, &[ENV_SIGNER]))
+ .map(parse_signer_mode)
.transpose()?
.unwrap_or(SignerBackend::Local),
},
@@ -428,12 +428,12 @@ fn parse_output_format(value: &str) -> Result<OutputFormat, RuntimeError> {
}
}
-fn parse_signer_backend(value: String) -> Result<SignerBackend, RuntimeError> {
+fn parse_signer_mode(value: String) -> Result<SignerBackend, RuntimeError> {
match value.trim().to_ascii_lowercase().as_str() {
"local" => Ok(SignerBackend::Local),
"myc" => Ok(SignerBackend::Myc),
other => Err(RuntimeError::Config(format!(
- "{ENV_SIGNER_BACKEND} or --signer-backend must be `local` or `myc`, got `{other}`"
+ "{ENV_SIGNER} or --signer must be `local` or `myc`, got `{other}`"
))),
}
}
@@ -502,7 +502,7 @@ mod tests {
"--log-stdout",
"--identity-path",
"custom-identity.json",
- "--signer-backend",
+ "--signer",
"local",
"--myc-executable",
"bin/myc-cli",
@@ -517,7 +517,7 @@ mod tests {
"RADROOTS_IDENTITY_PATH".to_owned(),
"env-identity.json".to_owned(),
),
- ("RADROOTS_SIGNER_BACKEND".to_owned(), "myc".to_owned()),
+ ("RADROOTS_SIGNER".to_owned(), "myc".to_owned()),
("RADROOTS_MYC_EXECUTABLE".to_owned(), "env-myc".to_owned()),
]));
@@ -576,7 +576,7 @@ mod tests {
"RADROOTS_IDENTITY_PATH".to_owned(),
"state/identity.json".to_owned(),
),
- ("RADROOTS_SIGNER_BACKEND".to_owned(), "myc".to_owned()),
+ ("RADROOTS_SIGNER".to_owned(), "myc".to_owned()),
("RADROOTS_MYC_EXECUTABLE".to_owned(), "bin/myc".to_owned()),
]));
@@ -671,7 +671,7 @@ RADROOTS_CLI_LOGGING_OUTPUT_DIR=/tmp/radroots-cli-logs
RADROOTS_CLI_LOGGING_STDOUT=false
RADROOTS_ACCOUNT=acct_env_file
RADROOTS_IDENTITY_PATH=state/identity.json
-RADROOTS_SIGNER_BACKEND=myc
+RADROOTS_SIGNER=myc
RADROOTS_MYC_EXECUTABLE=bin/myc
"#,
Path::new(".env.test"),
diff --git a/src/runtime/myc.rs b/src/runtime/myc.rs
@@ -126,6 +126,7 @@ pub fn resolve_status(config: &MycConfig) -> MycStatusView {
MycStatusView {
executable,
state: state.to_owned(),
+ source: "myc status command · local first".to_owned(),
service_status: Some(payload.status),
ready: payload.ready,
reason,
@@ -220,6 +221,7 @@ fn unavailable_status(executable: String, state: &str, reason: String) -> MycSta
MycStatusView {
executable,
state: state.to_owned(),
+ source: "myc status command · local first".to_owned(),
service_status: None,
ready: false,
reason: Some(reason),
diff --git a/src/runtime/signer.rs b/src/runtime/signer.rs
@@ -28,8 +28,10 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView {
.expect("local signer capability")
.clone();
SignerStatusView {
- backend: config.signer.backend.as_str().to_owned(),
+ mode: config.signer.backend.as_str().to_owned(),
state: "ready".to_owned(),
+ source: "local account store · local first".to_owned(),
+ account_id: Some(local.account_id.to_string()),
reason: None,
local: Some(LocalSignerStatusView {
account_id: local.account_id.to_string(),
@@ -43,8 +45,10 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView {
}
}
Ok(RadrootsNostrSelectedAccountStatus::PublicOnly { account }) => SignerStatusView {
- backend: config.signer.backend.as_str().to_owned(),
+ mode: config.signer.backend.as_str().to_owned(),
state: "unconfigured".to_owned(),
+ source: "local account store · local first".to_owned(),
+ account_id: Some(account.account_id.to_string()),
reason: Some(format!(
"local account {} is present but not secret-backed",
account.account_id
@@ -59,8 +63,10 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView {
myc: None,
},
Ok(RadrootsNostrSelectedAccountStatus::NotConfigured) => SignerStatusView {
- backend: config.signer.backend.as_str().to_owned(),
+ mode: config.signer.backend.as_str().to_owned(),
state: "unconfigured".to_owned(),
+ source: "local account store · local first".to_owned(),
+ account_id: None,
reason: Some(format!(
"no local account is selected in {}",
config.account.store_path.display()
@@ -69,8 +75,10 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView {
myc: None,
},
Err(error) => SignerStatusView {
- backend: config.signer.backend.as_str().to_owned(),
+ mode: config.signer.backend.as_str().to_owned(),
state: "error".to_owned(),
+ source: "local account store · local first".to_owned(),
+ account_id: None,
reason: Some(error.to_string()),
local: None,
myc: None,
@@ -81,8 +89,13 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView {
fn resolve_myc_signer_status(config: &RuntimeConfig) -> SignerStatusView {
let myc = crate::runtime::myc::resolve_status(&config.myc);
SignerStatusView {
- backend: config.signer.backend.as_str().to_owned(),
+ mode: config.signer.backend.as_str().to_owned(),
state: myc.state.clone(),
+ source: "myc status command · local first".to_owned(),
+ account_id: myc
+ .local_signer
+ .as_ref()
+ .map(|local| local.account_id.clone()),
reason: myc.reason.clone(),
local: None,
myc: Some(myc),
diff --git a/tests/doctor.rs b/tests/doctor.rs
@@ -21,7 +21,7 @@ fn doctor_command_in(workdir: &Path) -> Command {
"RADROOTS_LOG_STDOUT",
"RADROOTS_ACCOUNT",
"RADROOTS_IDENTITY_PATH",
- "RADROOTS_SIGNER_BACKEND",
+ "RADROOTS_SIGNER",
"RADROOTS_MYC_EXECUTABLE",
] {
command.env_remove(key);
@@ -81,7 +81,7 @@ fn doctor_reports_ready_local_bootstrap_state() {
#[test]
fn doctor_reports_external_failure_for_missing_myc() {
let dir = tempdir().expect("tempdir");
- fs::write(dir.path().join(".env"), "RADROOTS_SIGNER_BACKEND=myc\n").expect("write env file");
+ fs::write(dir.path().join(".env"), "RADROOTS_SIGNER=myc\n").expect("write env file");
let output = doctor_command_in(dir.path())
.args(["--json", "--myc-executable", "missing-myc", "doctor"])
diff --git a/tests/identity_commands.rs b/tests/identity_commands.rs
@@ -20,7 +20,7 @@ fn cli_command_in(workdir: &Path) -> Command {
"RADROOTS_LOG_STDOUT",
"RADROOTS_ACCOUNT",
"RADROOTS_IDENTITY_PATH",
- "RADROOTS_SIGNER_BACKEND",
+ "RADROOTS_SIGNER",
"RADROOTS_MYC_EXECUTABLE",
] {
command.env_remove(key);
diff --git a/tests/myc_status.rs b/tests/myc_status.rs
@@ -24,7 +24,7 @@ fn cli_command_in(workdir: &Path) -> Command {
"RADROOTS_LOG_STDOUT",
"RADROOTS_ACCOUNT",
"RADROOTS_IDENTITY_PATH",
- "RADROOTS_SIGNER_BACKEND",
+ "RADROOTS_SIGNER",
"RADROOTS_MYC_EXECUTABLE",
] {
command.env_remove(key);
@@ -56,6 +56,7 @@ fn myc_status_reports_ready_for_valid_full_status_payload() {
let stdout = String::from_utf8(output.stdout).expect("utf8 stdout");
let json: Value = serde_json::from_str(stdout.as_str()).expect("json output");
assert_eq!(json["state"], "ready");
+ assert_eq!(json["source"], "myc status command · local first");
assert_eq!(json["ready"], true);
assert_eq!(json["service_status"], "healthy");
assert_eq!(json["local_signer"]["availability"], "secret_backed");
@@ -140,7 +141,7 @@ fn signer_status_reports_degraded_myc_backend_as_external_unavailable() {
let output = cli_command_in(dir.path())
.args([
"--json",
- "--signer-backend",
+ "--signer",
"myc",
"--myc-executable",
executable.to_str().expect("executable path"),
@@ -153,8 +154,13 @@ fn signer_status_reports_degraded_myc_backend_as_external_unavailable() {
assert_eq!(output.status.code(), Some(4));
let stdout = String::from_utf8(output.stdout).expect("utf8 stdout");
let json: Value = serde_json::from_str(stdout.as_str()).expect("json output");
- assert_eq!(json["backend"], "myc");
+ assert_eq!(json["mode"], "myc");
assert_eq!(json["state"], "degraded");
+ assert_eq!(json["source"], "myc status command · local first");
+ assert_eq!(
+ json["account_id"],
+ json["myc"]["local_signer"]["account_id"]
+ );
assert_eq!(json["myc"]["state"], "degraded");
assert_eq!(json["myc"]["service_status"], "degraded");
assert!(
diff --git a/tests/runtime_show.rs b/tests/runtime_show.rs
@@ -21,7 +21,7 @@ fn runtime_show_command_in(workdir: &Path) -> Command {
"RADROOTS_LOG_STDOUT",
"RADROOTS_ACCOUNT",
"RADROOTS_IDENTITY_PATH",
- "RADROOTS_SIGNER_BACKEND",
+ "RADROOTS_SIGNER",
"RADROOTS_MYC_EXECUTABLE",
] {
command.env_remove(key);
@@ -92,7 +92,7 @@ fn config_show_json_reports_default_bootstrap_state() {
.to_string()
);
assert_eq!(json["account"]["legacy_identity_path"], "identity.json");
- assert_eq!(json["signer"]["backend"], "local");
+ assert_eq!(json["signer"]["mode"], "local");
assert_eq!(json["myc"]["executable"], "myc");
}
@@ -106,7 +106,7 @@ fn config_show_json_reflects_environment_configuration() {
.env("RADROOTS_LOG_STDOUT", "false")
.env("RADROOTS_ACCOUNT", "acct_demo")
.env("RADROOTS_IDENTITY_PATH", "state/identity.json")
- .env("RADROOTS_SIGNER_BACKEND", "myc")
+ .env("RADROOTS_SIGNER", "myc")
.env("RADROOTS_MYC_EXECUTABLE", "bin/myc")
.args(["config", "show"])
.output()
@@ -123,7 +123,7 @@ fn config_show_json_reflects_environment_configuration() {
json["account"]["legacy_identity_path"],
"state/identity.json"
);
- assert_eq!(json["signer"]["backend"], "myc");
+ assert_eq!(json["signer"]["mode"], "myc");
assert_eq!(json["myc"]["executable"], "bin/myc");
}
diff --git a/tests/signer_status.rs b/tests/signer_status.rs
@@ -21,7 +21,7 @@ fn cli_command_in(workdir: &Path) -> Command {
"RADROOTS_LOG_STDOUT",
"RADROOTS_ACCOUNT",
"RADROOTS_IDENTITY_PATH",
- "RADROOTS_SIGNER_BACKEND",
+ "RADROOTS_SIGNER",
"RADROOTS_MYC_EXECUTABLE",
] {
command.env_remove(key);
@@ -40,15 +40,17 @@ fn signer_status_reports_local_ready_when_account_exists() {
assert!(init.status.success());
let output = cli_command_in(dir.path())
- .args(["--json", "--signer-backend", "local", "signer", "status"])
+ .args(["--json", "--signer", "local", "signer", "status"])
.output()
.expect("run signer status");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("utf8 stdout");
let json: Value = serde_json::from_str(stdout.as_str()).expect("json output");
- assert_eq!(json["backend"], "local");
+ assert_eq!(json["mode"], "local");
assert_eq!(json["state"], "ready");
+ assert_eq!(json["source"], "local account store · local first");
+ assert_eq!(json["account_id"], json["local"]["account_id"]);
assert_eq!(json["reason"], Value::Null);
assert_eq!(json["local"]["availability"], "secret_backed");
assert_eq!(json["local"]["secret_backed"], true);
@@ -59,14 +61,14 @@ fn signer_status_reports_local_unconfigured_when_no_account_is_selected() {
let dir = tempdir().expect("tempdir");
let output = cli_command_in(dir.path())
- .args(["--json", "--signer-backend", "local", "signer", "status"])
+ .args(["--json", "--signer", "local", "signer", "status"])
.output()
.expect("run signer status");
assert_eq!(output.status.code(), Some(3));
let stdout = String::from_utf8(output.stdout).expect("utf8 stdout");
let json: Value = serde_json::from_str(stdout.as_str()).expect("json output");
- assert_eq!(json["backend"], "local");
+ assert_eq!(json["mode"], "local");
assert_eq!(json["state"], "unconfigured");
assert!(
json["reason"]
@@ -84,15 +86,57 @@ fn signer_status_reports_internal_error_for_invalid_account_store_file() {
fs::write(accounts_dir.join("store.json"), "{ not valid json").expect("write invalid store");
let output = cli_command_in(dir.path())
- .args(["--json", "--signer-backend", "local", "signer", "status"])
+ .args(["--json", "--signer", "local", "signer", "status"])
.output()
.expect("run signer status");
assert_eq!(output.status.code(), Some(1));
let stdout = String::from_utf8(output.stdout).expect("utf8 stdout");
let json: Value = serde_json::from_str(stdout.as_str()).expect("json output");
- assert_eq!(json["backend"], "local");
+ assert_eq!(json["mode"], "local");
assert_eq!(json["state"], "error");
assert!(json["reason"].as_str().is_some());
assert_eq!(json["local"], Value::Null);
}
+
+#[test]
+fn signer_status_honors_explicit_account_selector_over_default_account() {
+ let dir = tempdir().expect("tempdir");
+
+ let first = cli_command_in(dir.path())
+ .args(["--json", "account", "new"])
+ .output()
+ .expect("run first account new");
+ assert!(first.status.success());
+ let first_json: Value = serde_json::from_slice(first.stdout.as_slice()).expect("first json");
+ let first_id = first_json["account"]["id"]
+ .as_str()
+ .expect("first account id")
+ .to_owned();
+
+ let second = cli_command_in(dir.path())
+ .args(["--json", "account", "new"])
+ .output()
+ .expect("run second account new");
+ assert!(second.status.success());
+
+ let output = cli_command_in(dir.path())
+ .args([
+ "--json",
+ "--signer",
+ "local",
+ "--account",
+ first_id.as_str(),
+ "signer",
+ "status",
+ ])
+ .output()
+ .expect("run signer status");
+
+ assert!(output.status.success());
+ let json: Value = serde_json::from_slice(output.stdout.as_slice()).expect("signer json");
+ assert_eq!(json["mode"], "local");
+ assert_eq!(json["state"], "ready");
+ assert_eq!(json["account_id"], first_id);
+ assert_eq!(json["local"]["account_id"], first_id);
+}