myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

commit cee3ab7d493abcf4cd9ec9350c6aff437037a6db
parent 1404b34245f6b2d0387bb893d321aa5b3760d5dd
Author: triesap <tyson@radroots.org>
Date:   Sun, 22 Mar 2026 13:03:56 +0000

tests: add discovery cli smoke coverage

- add a binary-backed integration test for discovery export-bundle and verify-bundle
- generate identities and config at runtime so the smoke path stays repo-local and deterministic
- assert that the cli writes the expected bundle files and verifies the exported manifest
- validate with cargo test --locked --test discovery_cli and cargo test --locked

Diffstat:
Atests/discovery_cli.rs | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 151 insertions(+), 0 deletions(-)

diff --git a/tests/discovery_cli.rs b/tests/discovery_cli.rs @@ -0,0 +1,151 @@ +use std::fs; +use std::path::Path; +use std::process::Command; + +use radroots_identity::RadrootsIdentity; +use serde_json::Value; + +type TestResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>; + +fn write_identity(path: &Path, secret_key: &str) { + RadrootsIdentity::from_secret_key_str(secret_key) + .expect("identity") + .save_json(path) + .expect("save identity"); +} + +fn write_config( + path: &Path, + state_dir: &Path, + signer_identity_path: &Path, + user_identity_path: &Path, + app_identity_path: &Path, +) { + let config = format!( + r#"[service] +instance_name = "myc" + +[logging] +filter = "info,myc=info" + +[paths] +state_dir = "{state_dir}" +signer_identity_path = "{signer_identity_path}" +user_identity_path = "{user_identity_path}" + +[audit] +default_read_limit = 200 +max_active_file_bytes = 262144 +max_archived_files = 8 + +[discovery] +enabled = true +domain = "signer.example.com" +handler_identifier = "myc" +app_identity_path = "{app_identity_path}" +public_relays = ["wss://relay.example.com"] +publish_relays = ["wss://relay.example.com"] +nostrconnect_url_template = "https://signer.example.com/connect?uri=<nostrconnect>" +nip05_output_path = "{nip05_output_path}" + +[discovery.metadata] +name = "myc" +display_name = "Mycorrhiza" +about = "NIP-46 signer" +website = "https://signer.example.com" +picture = "https://signer.example.com/logo.png" + +[policy] +connection_approval = "explicit_user" + +[transport] +enabled = false +connect_timeout_secs = 10 +relays = [] +"#, + state_dir = state_dir.display(), + signer_identity_path = signer_identity_path.display(), + user_identity_path = user_identity_path.display(), + app_identity_path = app_identity_path.display(), + nip05_output_path = state_dir.join("public/.well-known/nostr.json").display(), + ); + fs::write(path, config).expect("write config"); +} + +#[test] +fn export_bundle_and_verify_bundle_work_through_the_cli() -> TestResult<()> { + let temp = tempfile::tempdir()?; + let config_path = temp.path().join("config.toml"); + let state_dir = temp.path().join("state"); + let signer_identity_path = temp.path().join("signer.json"); + let user_identity_path = temp.path().join("user.json"); + let app_identity_path = temp.path().join("app.json"); + let bundle_dir = temp.path().join("bundle"); + + write_identity( + &signer_identity_path, + "1111111111111111111111111111111111111111111111111111111111111111", + ); + write_identity( + &user_identity_path, + "2222222222222222222222222222222222222222222222222222222222222222", + ); + write_identity( + &app_identity_path, + "3333333333333333333333333333333333333333333333333333333333333333", + ); + write_config( + &config_path, + &state_dir, + &signer_identity_path, + &user_identity_path, + &app_identity_path, + ); + + let export = Command::new(env!("CARGO_BIN_EXE_myc")) + .arg("--config") + .arg(&config_path) + .arg("discovery") + .arg("export-bundle") + .arg("--out") + .arg(&bundle_dir) + .output()?; + + assert!( + export.status.success(), + "export-bundle failed: {}", + String::from_utf8_lossy(&export.stderr) + ); + let export_output: Value = serde_json::from_slice(&export.stdout)?; + assert_eq!(export_output["manifest"]["domain"], "signer.example.com"); + assert!(bundle_dir.join("bundle.json").exists()); + assert!(bundle_dir.join(".well-known/nostr.json").exists()); + assert!(bundle_dir.join("nip89-handler.json").exists()); + + let verify = Command::new(env!("CARGO_BIN_EXE_myc")) + .arg("--config") + .arg(&config_path) + .arg("discovery") + .arg("verify-bundle") + .arg("--dir") + .arg(&bundle_dir) + .output()?; + + assert!( + verify.status.success(), + "verify-bundle failed: {}", + String::from_utf8_lossy(&verify.stderr) + ); + let verify_output: Value = serde_json::from_slice(&verify.stdout)?; + assert_eq!(verify_output["manifest"]["domain"], "signer.example.com"); + assert_eq!( + verify_output["manifest"]["nip05_relative_path"], + ".well-known/nostr.json" + ); + assert_eq!( + verify_output["manifest"]["nip89_relative_path"], + "nip89-handler.json" + ); + + Ok(()) +}