#![expect(missing_docs, reason = "Test code")]
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use data_privacy::simple_redactor::{SimpleRedactor, SimpleRedactorMode};
use data_privacy::{Classified, RedactionEngine, Sensitive};
use data_privacy_macros::taxonomy;
#[taxonomy(test)]
#[derive(Debug)]
#[expect(clippy::upper_case_acronyms, reason = "PII is a well-known acronym")]
enum TestTaxonomy {
PII,
EUII,
}
#[test]
fn test_classified_wrapper() {
let classified = Sensitive::new(42, TestTaxonomy::PII);
assert_eq!(classified.data_class(), TestTaxonomy::PII);
assert_eq!(
format!("{classified:?}"),
"Sensitive { value: \"***\", data_class: DataClass { taxonomy: \"test\", name: \"p_i_i\" } }"
);
}
#[test]
fn test_declassification() {
let mut classified = Sensitive::new(42, TestTaxonomy::PII);
assert_eq!(*classified.declassify_ref(), 42);
assert_eq!(*classified.declassify_mut(), 42);
assert_eq!(classified.declassify_into(), 42);
}
#[test]
fn test_reclassify() {
let classified = Sensitive::new(42, TestTaxonomy::PII).reclassify(TestTaxonomy::EUII);
assert_eq!(classified.data_class(), TestTaxonomy::EUII);
}
#[test]
fn test_clone_and_equality() {
let classified1 = Sensitive::new(42, TestTaxonomy::PII);
let classified2 = classified1.clone();
let classified3 = Sensitive::new(12, TestTaxonomy::PII);
assert_eq!(classified1, classified2);
assert_ne!(classified1, classified3);
}
#[test]
fn test_hash() {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let classified = Sensitive::new(42, TestTaxonomy::PII);
classified.hash(&mut hasher);
let hash1 = hasher.finish();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let classified2 = Sensitive::new(42, TestTaxonomy::PII);
classified2.hash(&mut hasher);
let hash2 = hasher.finish();
assert_eq!(hash1, hash2, "Hashes should be equal for the same classified value");
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let classified3 = Sensitive::new(12, TestTaxonomy::PII);
classified3.hash(&mut hasher);
let hash3 = hasher.finish();
assert_ne!(hash1, hash3, "Hashes of data with different values should not be equal");
}
#[test]
fn test_ordering() {
let classified1 = Sensitive::new(42, TestTaxonomy::PII);
let classified2 = Sensitive::new(12, TestTaxonomy::PII);
assert_eq!(classified1.partial_cmp(&classified2).unwrap(), Ordering::Greater);
assert_eq!(classified2.partial_cmp(&classified1).unwrap(), Ordering::Less);
assert_eq!(classified1.partial_cmp(&classified1).unwrap(), Ordering::Equal);
}
#[test]
fn test_as_declassified_mut_allows_mutation() {
let classified = Sensitive::new(vec![1, 2, 3], TestTaxonomy::PII);
assert_eq!(classified.data_class(), TestTaxonomy::PII);
}
#[test]
fn test_redacted_debug_and_display_replace_mode_string() {
let engine = RedactionEngine::builder()
.add_class_redactor(TestTaxonomy::PII, SimpleRedactor::with_mode(SimpleRedactorMode::Replace('*')))
.build();
let wrapper = Sensitive::new("secret".to_string(), TestTaxonomy::PII);
let mut debug_out = String::new();
engine.redacted_debug(&wrapper, &mut debug_out).unwrap();
assert_eq!(
debug_out, "********",
"Debug redaction should produce 8 asterisks (including quotes)"
);
let mut display_out = String::new();
engine.redacted_display(&wrapper, &mut display_out).unwrap();
assert_eq!(display_out, "******", "Display redaction should produce 6 asterisks (no quotes)");
let to_string_out = engine.redacted_to_string(&wrapper);
assert_eq!(to_string_out, "******", "RedactedToString should match Display redaction");
}
#[test]
fn test_redacted_debug_and_display_replace_mode_numeric() {
let engine = RedactionEngine::builder()
.add_class_redactor(TestTaxonomy::PII, SimpleRedactor::with_mode(SimpleRedactorMode::Replace('*')))
.build();
let wrapper = Sensitive::new(42u32, TestTaxonomy::PII);
let mut debug_out = String::new();
engine.redacted_debug(&wrapper, &mut debug_out).unwrap();
assert_eq!(debug_out, "**");
let mut display_out = String::new();
engine.redacted_display(&wrapper, &mut display_out).unwrap();
assert_eq!(display_out, "**");
let to_string_out = engine.redacted_to_string(&wrapper);
assert_eq!(to_string_out, "**");
}
#[test]
fn test_redacted_passthrough_and_tag_mode() {
let engine = RedactionEngine::builder()
.add_class_redactor(TestTaxonomy::PII, SimpleRedactor::with_mode(SimpleRedactorMode::PassthroughAndTag))
.build();
let wrapper = Sensitive::new("secret".to_string(), TestTaxonomy::PII);
let mut debug_out = String::new();
engine.redacted_debug(&wrapper, &mut debug_out).unwrap();
assert_eq!(
debug_out, "<test/p_i_i:\"secret\">",
"PassthroughAndTag debug should include quotes inside tag"
);
let mut display_out = String::new();
engine.redacted_display(&wrapper, &mut display_out).unwrap();
assert_eq!(
display_out, "<test/p_i_i:secret>",
"PassthroughAndTag display should not include quotes"
);
let to_string_out = engine.redacted_to_string(&wrapper);
assert_eq!(to_string_out, "<test/p_i_i:secret>");
}
#[test]
fn test_redacted_long_value_fallback_path() {
let engine = RedactionEngine::builder()
.add_class_redactor(TestTaxonomy::PII, SimpleRedactor::with_mode(SimpleRedactorMode::Replace('*')))
.build();
let long_plain = "a".repeat(140);
let wrapper = Sensitive::new(long_plain.clone(), TestTaxonomy::PII);
let mut debug_out = String::new();
engine.redacted_debug(&wrapper, &mut debug_out).unwrap();
assert_eq!(debug_out.len(), long_plain.len() + 2);
assert!(debug_out.chars().all(|c| c == '*'));
let mut display_out = String::new();
engine.redacted_display(&wrapper, &mut display_out).unwrap();
assert_eq!(display_out.len(), long_plain.len());
assert!(display_out.chars().all(|c| c == '*'));
let to_string_out = engine.redacted_to_string(&wrapper);
assert_eq!(to_string_out.len(), long_plain.len());
assert!(to_string_out.chars().all(|c| c == '*'));
}