use std::fs;
use std::io::Cursor;
use std::path::Path;
use assert_cmd::Command;
use runewarp::client_identity_from_certificate_der;
use rustls_pemfile::certs;
use tempfile::tempdir;
#[test]
fn client_identity_init_writes_identity_artifacts_to_the_requested_directory() {
let tempdir = tempdir().unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
assert_exists(tempdir.path().join("client-identity/client.key").as_path());
assert_exists(tempdir.path().join("client-identity/client.crt").as_path());
assert_exists(
tempdir
.path()
.join("client-identity/client-identity.txt")
.as_path(),
);
}
#[test]
fn client_identity_init_uses_the_xdg_default_directory_when_dir_is_omitted() {
let tempdir = tempdir().unwrap();
let xdg_config_home = tempdir.path().join("xdg-config");
let xdg_data_home = tempdir.path().join("xdg-data");
fs::create_dir_all(xdg_config_home.join("runewarp")).unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.env("XDG_CONFIG_HOME", &xdg_config_home)
.env("XDG_DATA_HOME", &xdg_data_home)
.args(["client", "identity", "init"])
.assert()
.success();
assert_exists(
xdg_data_home
.join("runewarp/client/identity/client.key")
.as_path(),
);
assert_exists(
xdg_data_home
.join("runewarp/client/identity/client.crt")
.as_path(),
);
assert_exists(
xdg_data_home
.join("runewarp/client/identity/client-identity.txt")
.as_path(),
);
}
#[test]
fn client_identity_init_uses_the_configured_material_dir_when_config_is_provided() {
let tempdir = tempdir().unwrap();
let configured_dir = tempdir.path().join("configured/client-identity");
fs::create_dir_all(tempdir.path().join("configured")).unwrap();
fs::write(
tempdir.path().join("client.toml"),
r#"
[client]
identity-dir = "configured/client-identity"
"#,
)
.unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--config", "client.toml"])
.assert()
.success();
assert_exists(configured_dir.join("client.key").as_path());
assert_exists(configured_dir.join("client.crt").as_path());
assert_exists(configured_dir.join("client-identity.txt").as_path());
}
#[test]
fn client_identity_init_accepts_config_before_the_leaf_subcommand() {
let tempdir = tempdir().unwrap();
let configured_dir = tempdir.path().join("configured/client-identity");
fs::create_dir_all(tempdir.path().join("configured")).unwrap();
fs::write(
tempdir.path().join("client.toml"),
r#"
[client]
identity-dir = "configured/client-identity"
"#,
)
.unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "--config", "client.toml", "init"])
.assert()
.success();
assert_exists(configured_dir.join("client.key").as_path());
assert_exists(configured_dir.join("client.crt").as_path());
assert_exists(configured_dir.join("client-identity.txt").as_path());
}
#[test]
fn client_identity_init_rejects_the_legacy_identity_directory_key_in_config() {
let tempdir = tempdir().unwrap();
let xdg_data_home = tempdir.path().join("xdg-data");
fs::write(
tempdir.path().join("client.toml"),
r#"
[client]
identity-directory = "legacy/client-identity"
"#,
)
.unwrap();
let assert = Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.env("XDG_DATA_HOME", &xdg_data_home)
.args(["client", "identity", "init", "--config", "client.toml"])
.assert()
.failure();
let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap();
assert!(stderr.contains("unknown field `identity-directory`"));
assert!(
!xdg_data_home
.join("runewarp/client/identity/client.key")
.exists()
);
}
#[test]
fn client_identity_init_writes_pem_artifacts_and_a_client_identity() {
let tempdir = tempdir().unwrap();
let assert = Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
let private_key =
fs::read_to_string(tempdir.path().join("client-identity/client.key")).unwrap();
let certificate =
fs::read_to_string(tempdir.path().join("client-identity/client.crt")).unwrap();
let client_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt")).unwrap();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(private_key.starts_with("-----BEGIN PRIVATE KEY-----"));
assert!(certificate.starts_with("-----BEGIN CERTIFICATE-----"));
assert_eq!(client_identity.trim().len(), 64);
assert!(
client_identity
.trim()
.chars()
.all(|ch| ch.is_ascii_hexdigit() && !ch.is_ascii_uppercase())
);
assert!(stdout.contains("90 days"));
assert!(stdout.contains("60 days"));
}
#[test]
fn client_identity_init_succeeds_when_identity_artifacts_already_exist() {
let tempdir = tempdir().unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
}
#[test]
fn client_identity_init_matches_the_generated_certificate_subject_public_key_info() {
let tempdir = tempdir().unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
let certificate_pem = fs::read(tempdir.path().join("client-identity/client.crt")).unwrap();
let certificate = certs(&mut Cursor::new(certificate_pem))
.next()
.expect("generated certificate")
.expect("parse generated certificate");
let stored_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt")).unwrap();
let derived_identity = client_identity_from_certificate_der(certificate.as_ref())
.expect("derive client identity from certificate");
assert_eq!(stored_identity.trim(), derived_identity.to_string());
}
#[test]
fn client_identity_init_rejects_runtime_routing_flags() -> Result<(), Box<dyn std::error::Error>> {
let tempdir = tempdir()?;
let assert = Command::cargo_bin("runewarp")?
.current_dir(tempdir.path())
.args([
"client",
"--server-address",
"tunnel.example.test",
"--backend-address",
"localhost:443",
"identity",
"init",
])
.assert()
.failure();
let stderr = String::from_utf8(assert.get_output().stderr.clone())?;
assert!(stderr.contains("runewarp client identity"));
assert!(stderr.contains("--server-address"));
assert!(stderr.contains("--backend-address"));
Ok(())
}
#[test]
fn client_identity_renew_reuses_the_existing_key_and_identity() {
let tempdir = tempdir().unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
let original_private_key = fs::read(tempdir.path().join("client-identity/client.key")).unwrap();
let original_certificate = fs::read(tempdir.path().join("client-identity/client.crt")).unwrap();
let original_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt")).unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "renew", "--dir", "client-identity"])
.assert()
.success();
let renewed_private_key = fs::read(tempdir.path().join("client-identity/client.key")).unwrap();
let renewed_certificate = fs::read(tempdir.path().join("client-identity/client.crt")).unwrap();
let renewed_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt")).unwrap();
let renewed_certificate_der = certs(&mut Cursor::new(renewed_certificate.clone()))
.next()
.expect("renewed certificate")
.expect("parse renewed certificate");
assert_eq!(renewed_private_key, original_private_key);
assert_ne!(renewed_certificate, original_certificate);
assert_eq!(renewed_identity, original_identity);
assert_eq!(
client_identity_from_certificate_der(renewed_certificate_der.as_ref())
.unwrap()
.to_string(),
renewed_identity.trim(),
);
}
#[test]
fn client_identity_rotate_replaces_the_key_and_client_identity() {
let tempdir = tempdir().unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
let original_private_key = fs::read(tempdir.path().join("client-identity/client.key")).unwrap();
let original_certificate = fs::read(tempdir.path().join("client-identity/client.crt")).unwrap();
let original_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt")).unwrap();
Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args(["client", "identity", "rotate", "--dir", "client-identity"])
.assert()
.success();
let rotated_private_key = fs::read(tempdir.path().join("client-identity/client.key")).unwrap();
let rotated_certificate = fs::read(tempdir.path().join("client-identity/client.crt")).unwrap();
let rotated_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt")).unwrap();
let rotated_certificate_der = certs(&mut Cursor::new(rotated_certificate.clone()))
.next()
.expect("rotated certificate")
.expect("parse rotated certificate");
assert_ne!(rotated_private_key, original_private_key);
assert_ne!(rotated_certificate, original_certificate);
assert_ne!(rotated_identity, original_identity);
assert_eq!(
client_identity_from_certificate_der(rotated_certificate_der.as_ref())
.unwrap()
.to_string(),
rotated_identity.trim(),
);
}
#[test]
fn client_identity_show_prints_only_the_fingerprint() -> Result<(), Box<dyn std::error::Error>> {
let tempdir = tempdir()?;
Command::cargo_bin("runewarp")?
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
let expected_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt"))?;
let assert = Command::cargo_bin("runewarp")?
.current_dir(tempdir.path())
.args(["client", "identity", "show", "--dir", "client-identity"])
.assert()
.success();
assert_eq!(
String::from_utf8(assert.get_output().stdout.clone())?,
format!("{}\n", expected_identity.trim())
);
assert!(assert.get_output().stderr.is_empty());
Ok(())
}
#[test]
fn client_identity_init_is_idempotent_when_material_already_exists()
-> Result<(), Box<dyn std::error::Error>> {
let tempdir = tempdir()?;
Command::cargo_bin("runewarp")?
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
let original_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt"))?;
let assert = Command::cargo_bin("runewarp")?
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone())?;
let current_identity =
fs::read_to_string(tempdir.path().join("client-identity/client-identity.txt"))?;
assert_eq!(current_identity, original_identity);
assert!(stdout.contains("Client identity already exists"));
assert!(stdout.contains("Issued at (UTC):"));
assert!(stdout.contains("Renew after (UTC):"));
assert!(stdout.contains("Expires at (UTC):"));
assert!(!stdout.contains("os error"));
Ok(())
}
#[test]
fn client_identity_init_reports_repair_guidance_for_partial_material()
-> Result<(), Box<dyn std::error::Error>> {
let tempdir = tempdir()?;
fs::create_dir_all(tempdir.path().join("client-identity"))?;
fs::write(
tempdir.path().join("client-identity/client.crt"),
"placeholder certificate",
)?;
let assert = Command::cargo_bin("runewarp")?
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.failure();
let stderr = String::from_utf8(assert.get_output().stderr.clone())?;
assert!(stderr.contains("incomplete or inconsistent"));
assert!(stderr.contains("runewarp client identity init"));
assert!(stderr.contains("client-identity/client.crt"));
assert!(!stderr.contains("os error"));
Ok(())
}
#[test]
fn client_identity_init_reports_paths_and_utc_timestamps() -> Result<(), Box<dyn std::error::Error>>
{
let tempdir = tempdir()?;
let assert = Command::cargo_bin("runewarp")?
.current_dir(tempdir.path())
.args(["client", "identity", "init", "--dir", "client-identity"])
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone())?;
assert!(stdout.contains("Identity directory: client-identity"));
assert!(stdout.contains("Issued at (UTC):"));
assert!(stdout.contains("Renew after (UTC):"));
assert!(stdout.contains("Expires at (UTC):"));
Ok(())
}
#[test]
fn client_identity_show_help_keeps_runewarp_branding_and_examples()
-> Result<(), Box<dyn std::error::Error>> {
let assert = Command::cargo_bin("runewarp")?
.args(["client", "identity", "show", "--help"])
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone())?;
assert!(stdout.starts_with("Runewarp Client Identity"));
assert!(stdout.contains("Examples:"));
assert!(stdout.contains("runewarp client identity show"));
assert!(!stdout.contains("Config defaults:"));
Ok(())
}
fn assert_exists(path: &Path) {
assert!(
path.exists(),
"expected {} to exist after `runewarp client identity init`",
path.display()
);
}
#[test]
fn client_identity_init_help_shows_the_new_dir_flag() {
let assert = Command::cargo_bin("runewarp")
.unwrap()
.args(["client", "identity", "init", "--help"])
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(stdout.contains("Usage:"));
assert!(stdout.contains("runewarp client identity init"));
assert!(stdout.contains("--dir"));
assert!(!stdout.contains("--directory"));
}
#[test]
fn client_identity_help_shows_the_config_flag() {
let assert = Command::cargo_bin("runewarp")
.unwrap()
.args(["client", "identity", "--help"])
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(stdout.contains("Usage:"));
assert!(stdout.contains("runewarp client identity"));
assert!(stdout.contains("--config"));
}
#[test]
fn client_identity_init_rejects_the_removed_directory_flag() {
let tempdir = tempdir().unwrap();
let assert = Command::cargo_bin("runewarp")
.unwrap()
.current_dir(tempdir.path())
.args([
"client",
"identity",
"init",
"--directory",
"client-identity",
])
.assert()
.failure();
let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap();
assert!(stderr.contains("unexpected argument '--directory'"));
assert!(stderr.contains("--dir"));
}