use nu_plugin::Plugin;
use nu_plugin_secret::SecretString;
use nu_protocol::{Span, Value};
fn assert_redaction_behavior(display: &str, secret_content: &str) {
let show_unredacted = std::env::var("SHOW_UNREDACTED").unwrap_or_default();
let is_unredacted_mode = matches!(show_unredacted.as_str(), "1" | "true" | "True" | "TRUE");
if is_unredacted_mode {
assert!(
display.contains(secret_content),
"Expected display to contain '{}' in unredacted mode, but got: '{}'",
secret_content,
display
);
} else {
let is_redacted = display.contains("redacted")
|| display.contains("HIDDEN")
|| display.contains("***")
|| display.contains("moo") || display == "<redacted:string>" || (display.starts_with('<') && display.ends_with('>')) || display.len() < secret_content.len();
assert!(
is_redacted,
"Expected display to be redacted, but got: '{}'",
display
);
if !secret_content.is_empty() {
assert!(
!display.contains(secret_content),
"Expected display to NOT contain '{}', but got: '{}'",
secret_content,
display
);
}
}
}
#[cfg(test)]
mod secret_string_functionality_tests {
use super::*;
#[test]
fn test_secret_string_creation_and_display() {
let secret = SecretString::new("test-secret".to_string());
let display = format!("{}", secret);
assert_redaction_behavior(&display, "test-secret");
}
#[test]
fn test_secret_string_reveal() {
let secret = SecretString::new("my-api-key".to_string());
assert_eq!(secret.reveal(), "my-api-key");
}
#[test]
fn test_secret_string_empty() {
let secret = SecretString::new("".to_string());
assert_eq!(secret.reveal(), "");
let display = format!("{}", secret);
assert_redaction_behavior(&display, "");
}
#[test]
fn test_secret_string_special_characters() {
let test_string = "password123!@#$%^&*()_+-=[]{}|;':\",./<>?`~";
let secret = SecretString::new(test_string.to_string());
assert_eq!(secret.reveal(), test_string);
let display = format!("{}", secret);
assert_redaction_behavior(&display, test_string);
}
#[test]
fn test_secret_string_unicode() {
let test_string = "🔐 secret with émojis and ñoñ-ASCII 中文";
let secret = SecretString::new(test_string.to_string());
assert_eq!(secret.reveal(), test_string);
let display = format!("{}", secret);
assert_redaction_behavior(&display, test_string);
}
#[test]
fn test_secret_string_long_content() {
let test_string = "a".repeat(10000);
let secret = SecretString::new(test_string.clone());
assert_eq!(secret.reveal(), &test_string);
assert_eq!(secret.reveal().len(), 10000);
let display = format!("{}", secret);
assert_redaction_behavior(&display, &test_string);
}
#[test]
fn test_secret_string_debug_format() {
let secret = SecretString::new("debug-secret".to_string());
let debug = format!("{:?}", secret);
assert!(!debug.contains("debug-secret"));
assert!(debug.contains("SecretString") || debug.contains("redacted"));
}
#[test]
fn test_secret_string_clone() {
let original = SecretString::new("clone-test".to_string());
let cloned = original.clone();
assert_eq!(original.reveal(), cloned.reveal());
let original_display = format!("{}", original);
let cloned_display = format!("{}", cloned);
assert_redaction_behavior(&original_display, "clone-test");
assert_redaction_behavior(&cloned_display, "clone-test");
}
#[test]
fn test_secret_string_equality() {
let secret1 = SecretString::new("same-content".to_string());
let secret2 = SecretString::new("same-content".to_string());
let secret3 = SecretString::new("different-content".to_string());
assert_eq!(secret1, secret2);
assert_ne!(secret1, secret3);
assert_ne!(secret2, secret3);
}
#[test]
fn test_secret_string_custom_value_conversion() {
let secret = SecretString::new("custom-value-test".to_string());
let custom_value = Value::custom(Box::new(secret), Span::test_data());
match custom_value {
Value::Custom { .. } => {
}
_ => panic!("Expected Custom value"),
}
}
#[test]
fn test_multiple_secret_strings() {
let secrets: Vec<SecretString> = (0..100)
.map(|i| SecretString::new(format!("secret-{}", i)))
.collect();
for (i, secret) in secrets.iter().enumerate() {
assert_eq!(secret.reveal(), &format!("secret-{}", i));
let display = format!("{}", secret);
assert_redaction_behavior(&display, &format!("secret-{}", i));
}
}
}
#[cfg(test)]
mod command_functionality_tests {
use super::*;
#[test]
fn test_plugin_has_unified_wrap_command() {
let plugin = nu_plugin_secret::SecretPlugin::default();
let commands = plugin.commands();
let command_names: Vec<&str> = commands.iter().map(|cmd| cmd.name()).collect();
assert!(command_names.contains(&"secret wrap"));
}
#[test]
fn test_plugin_has_unwrap_command() {
let plugin = nu_plugin_secret::SecretPlugin::default();
let commands = plugin.commands();
let command_names: Vec<&str> = commands.iter().map(|cmd| cmd.name()).collect();
assert!(command_names.contains(&"secret unwrap"));
}
#[test]
fn test_unified_wrap_command_signature() {
let plugin = nu_plugin_secret::SecretPlugin::default();
let commands = plugin.commands();
let wrap_command = commands
.iter()
.find(|cmd| cmd.name() == "secret wrap")
.expect("unified wrap command should exist");
let signature = wrap_command.signature();
assert_eq!(signature.name, "secret wrap");
assert_eq!(signature.category, nu_protocol::Category::Conversions);
assert!(!signature.input_output_types.is_empty());
assert!(signature.input_output_types.len() >= 8); }
#[test]
fn test_unwrap_command_signature() {
let plugin = nu_plugin_secret::SecretPlugin::default();
let commands = plugin.commands();
let unwrap_command = commands
.iter()
.find(|cmd| cmd.name() == "secret unwrap")
.expect("unwrap command should exist");
let signature = unwrap_command.signature();
assert_eq!(signature.name, "secret unwrap");
assert_eq!(signature.category, nu_protocol::Category::Conversions);
assert!(signature.input_output_types.len() >= 8); }
#[test]
fn test_unified_wrap_command_description() {
let plugin = nu_plugin_secret::SecretPlugin::default();
let commands = plugin.commands();
let wrap_command = commands
.iter()
.find(|cmd| cmd.name() == "secret wrap")
.expect("unified wrap command should exist");
let description = wrap_command.description();
assert!(!description.is_empty());
assert!(description.contains("secret") || description.contains("Secret"));
assert!(description.contains("value") || description.contains("type"));
}
#[test]
fn test_unwrap_command_description() {
let plugin = nu_plugin_secret::SecretPlugin::default();
let commands = plugin.commands();
let unwrap_command = commands
.iter()
.find(|cmd| cmd.name() == "secret unwrap")
.expect("unwrap command should exist");
let description = unwrap_command.description();
assert!(!description.is_empty());
assert!(description.contains("WARNING") || description.contains("warning"));
assert!(description.contains("sensitive") || description.contains("expose"));
}
#[test]
fn test_command_examples() {
let plugin = nu_plugin_secret::SecretPlugin::default();
let commands = plugin.commands();
let wrap_command = commands
.iter()
.find(|cmd| cmd.name() == "secret wrap")
.expect("unified wrap command should exist");
let unwrap_command = commands
.iter()
.find(|cmd| cmd.name() == "secret unwrap")
.expect("unwrap command should exist");
assert!(!wrap_command.examples().is_empty());
assert!(!unwrap_command.examples().is_empty());
for example in wrap_command.examples() {
if let Some(result) = &example.result {
let display = format!("{:?}", result);
assert!(!display.contains("secret") || display.contains("redacted"));
}
}
}
}
#[cfg(test)]
mod round_trip_tests {
use super::*;
#[test]
fn test_wrap_unwrap_round_trip_concept() {
let long_string = "x".repeat(1000);
let original_values = vec![
"simple-secret",
"",
"🔐 unicode secret with émojis 中文",
"special!@#$%^&*()_+-=[]{}|;':\",./<>?`~chars",
&long_string, ];
for original in original_values {
let secret = SecretString::new(original.to_string());
let display = format!("{}", secret);
assert!(display.contains("redacted"));
assert!(!display.contains(original) || original.is_empty());
let revealed = secret.reveal();
assert_eq!(revealed, original);
let secret2 = SecretString::new(revealed.to_string());
assert_eq!(secret2.reveal(), original);
assert_eq!(secret, secret2);
}
}
#[test]
fn test_multiple_round_trips() {
let mut current_value = "initial-value".to_string();
for i in 0..10 {
current_value = format!("{}-{}", current_value, i);
let secret = SecretString::new(current_value.clone());
let display = format!("{}", secret);
assert_redaction_behavior(&display, ¤t_value);
let revealed = secret.reveal();
assert_eq!(revealed, ¤t_value);
}
}
#[test]
fn test_concurrent_secrets() {
use std::collections::HashMap;
let mut secrets = HashMap::new();
let test_data = vec![
("api_key", "sk-1234567890abcdef"),
("password", "MyS3cur3P@ssw0rd!"),
("token", "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0"),
("database_url", "postgresql://user:pass@localhost/db"),
("private_key", "-----BEGIN PRIVATE KEY-----\nMIIEvQ..."),
];
for (key, value) in &test_data {
secrets.insert(*key, SecretString::new(value.to_string()));
}
for (key, secret) in &secrets {
let display = format!("{}", secret);
assert!(display.contains("redacted"), "Secret {} not redacted", key);
let original_value = test_data
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| *v)
.unwrap();
assert!(
!display.contains(original_value),
"Secret {} leaked in display",
key
);
}
for (key, secret) in &secrets {
let original_value = test_data
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| *v)
.unwrap();
assert_eq!(
secret.reveal(),
original_value,
"Secret {} revelation failed",
key
);
}
}
}
#[cfg(test)]
mod security_tests {
use super::*;
#[test]
fn test_secret_string_no_leakage_in_debug() {
let sensitive_data = "super-secret-password-12345";
let secret = SecretString::new(sensitive_data.to_string());
let debug_output = format!("{:?}", secret);
assert!(!debug_output.contains(sensitive_data));
}
#[test]
fn test_secret_string_no_leakage_in_display() {
let sensitive_data = "api-key-abcdef123456";
let secret = SecretString::new(sensitive_data.to_string());
let display_output = format!("{}", secret);
assert_redaction_behavior(&display_output, sensitive_data);
}
#[test]
fn test_secret_string_consistent_redaction() {
let secret = SecretString::new("consistent-test".to_string());
let display1 = format!("{}", secret);
let display2 = format!("{}", secret);
let debug1 = format!("{:?}", secret);
let debug2 = format!("{:?}", secret);
assert_eq!(display1, display2);
assert_eq!(debug1, debug2);
assert!(!display1.contains("consistent-test"));
assert!(!display2.contains("consistent-test"));
assert!(!debug1.contains("consistent-test"));
assert!(!debug2.contains("consistent-test"));
}
#[test]
#[cfg(not(miri))] fn test_secret_string_memory_safety() {
for _ in 0..1000 {
let secret = SecretString::new(format!("secret-{}", rand::random::<u64>() as u32));
let _ = format!("{}", secret); }
}
#[test]
#[cfg(miri)] fn test_secret_string_memory_safety_miri() {
for i in 0..100 {
let secret = SecretString::new(format!("secret-{}", i));
let _ = format!("{}", secret); }
}
}
mod rand {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::time::{SystemTime, UNIX_EPOCH};
#[allow(dead_code)]
pub fn random<T: From<u64>>() -> T {
let mut hasher = DefaultHasher::new();
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
.hash(&mut hasher);
T::from(hasher.finish())
}
}