#![cfg(feature = "mobile-storage")]
use secure_data::mobile_storage::{BackupExclusion, MobileStoragePolicy, SensitiveBuffer};
use security_core::classification::DataClassification;
#[test]
fn given_sensitive_buffer_holding_secret_when_dropped_then_memory_zeroed() {
let buf = SensitiveBuffer::new(vec![0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(buf.expose(), &[0xDE, 0xAD, 0xBE, 0xEF]);
drop(buf);
}
#[test]
fn given_sensitive_buffer_when_debug_formatted_then_shows_redacted() {
let buf = SensitiveBuffer::new(vec![0x42; 16]);
let debug_output = format!("{:?}", buf);
assert!(
debug_output.contains("[REDACTED]"),
"Debug output must contain [REDACTED], got: {debug_output}"
);
assert!(
!debug_output.contains("42"),
"Debug output must not contain secret hex, got: {debug_output}"
);
}
#[test]
fn given_sensitive_buffer_when_display_formatted_then_shows_redacted() {
let buf = SensitiveBuffer::new(b"secret-pin-1234".to_vec());
let display_output = format!("{}", buf);
assert!(
display_output.contains("[REDACTED]"),
"Display output must contain [REDACTED], got: {display_output}"
);
assert!(
!display_output.contains("secret-pin-1234"),
"Display must not contain secret content"
);
}
#[test]
fn given_sensitive_buffer_holding_pin_when_wipe_called_then_zeroed() {
let mut buf = SensitiveBuffer::new(vec![1, 2, 3, 4]);
buf.wipe();
assert!(
buf.expose().iter().all(|&b| b == 0),
"Buffer must be zeroed after wipe(), got: {:?}",
buf.expose()
);
}
#[test]
fn given_sensitive_buffer_with_ttl_when_expired_then_auto_wipes() {
use std::time::Duration;
let buf = SensitiveBuffer::with_ttl(vec![0xAB; 8], Duration::from_secs(0));
std::thread::sleep(Duration::from_millis(10));
assert!(buf.is_expired(), "Buffer should be expired after TTL");
}
#[test]
fn given_sensitive_buffer_with_ttl_when_not_expired_then_data_available() {
use std::time::Duration;
let buf = SensitiveBuffer::with_ttl(vec![0xCD; 4], Duration::from_secs(60));
assert!(!buf.is_expired(), "Buffer should not be expired yet");
assert_eq!(buf.expose(), &[0xCD; 4]);
}
#[test]
fn given_sensitive_buffer_without_ttl_then_never_expired() {
let buf = SensitiveBuffer::new(vec![0x01]);
assert!(!buf.is_expired(), "Buffer without TTL should never expire");
}
#[test]
fn given_empty_sensitive_buffer_then_valid() {
let buf = SensitiveBuffer::new(vec![]);
assert!(buf.expose().is_empty());
}
#[test]
fn given_backup_exclusion_exclude_when_checked_then_excluded() {
let policy = BackupExclusion::Exclude;
assert!(
policy.should_exclude_from_backup(),
"Exclude policy must return true for should_exclude_from_backup()"
);
}
#[test]
fn given_default_backup_exclusion_when_checked_then_excluded() {
let policy = BackupExclusion::default();
assert!(
policy.should_exclude_from_backup(),
"Default backup exclusion must be Exclude (secure by default)"
);
}
#[test]
fn given_backup_exclusion_allow_when_checked_then_not_excluded() {
let policy = BackupExclusion::Allow;
assert!(
!policy.should_exclude_from_backup(),
"Allow policy must return false for should_exclude_from_backup()"
);
}
#[test]
fn given_backup_exclusion_when_serialized_then_valid_json() {
let exclude = BackupExclusion::Exclude;
let json = serde_json::to_string(&exclude).expect("must serialize");
assert!(
!json.is_empty(),
"Serialized BackupExclusion must produce non-empty JSON"
);
let deserialized: BackupExclusion = serde_json::from_str(&json).expect("must deserialize");
assert_eq!(deserialized, exclude);
}
#[test]
fn given_backup_exclusion_allow_when_serialized_then_round_trips() {
let allow = BackupExclusion::Allow;
let json = serde_json::to_string(&allow).expect("must serialize");
let deserialized: BackupExclusion = serde_json::from_str(&json).expect("must deserialize");
assert_eq!(deserialized, allow);
}
#[test]
fn given_encrypted_policy_when_confidential_data_then_requires_encryption() {
let policy = MobileStoragePolicy::encrypted(DataClassification::Confidential);
assert!(
policy.requires_encryption(),
"Encrypted policy for Confidential data must require encryption"
);
}
#[test]
fn given_hardware_backed_policy_when_checked_then_requires_hardware_keystore() {
let policy = MobileStoragePolicy::hardware_backed(DataClassification::Secret);
assert!(
policy.requires_hardware_keystore(),
"Hardware-backed policy must require hardware keystore"
);
assert!(
policy.requires_encryption(),
"Hardware-backed policy must also require encryption"
);
}
#[test]
fn given_public_data_policy_when_checked_then_no_hardware_keystore() {
let policy = MobileStoragePolicy::for_classification(DataClassification::Public);
assert!(
!policy.requires_hardware_keystore(),
"Public data policy must not require hardware keystore"
);
assert!(
!policy.requires_encryption(),
"Public data policy must not require encryption"
);
}
#[test]
fn given_encryption_policy_when_unencrypted_attempted_then_violation_event() {
use security_events::kind::EventKind;
let policy = MobileStoragePolicy::encrypted(DataClassification::Confidential);
let violations = policy.check_compliance(false, false);
assert!(
!violations.is_empty(),
"Must produce at least one violation"
);
for event in &violations {
assert_eq!(
event.kind,
EventKind::StoragePolicyViolation,
"Violation event kind must be StoragePolicyViolation"
);
}
}
#[test]
fn given_encryption_policy_when_encrypted_then_no_violations() {
let policy = MobileStoragePolicy::encrypted(DataClassification::Confidential);
let violations = policy.check_compliance(true, false);
assert!(
violations.is_empty(),
"Compliant storage must produce no violations"
);
}
#[test]
fn given_hardware_policy_when_no_hardware_then_violation() {
let policy = MobileStoragePolicy::hardware_backed(DataClassification::Secret);
let violations = policy.check_compliance(true, false);
assert!(
!violations.is_empty(),
"Must produce violation when hardware keystore is missing"
);
}
#[test]
fn given_hardware_policy_when_hardware_and_encrypted_then_no_violations() {
let policy = MobileStoragePolicy::hardware_backed(DataClassification::Secret);
let violations = policy.check_compliance(true, true);
assert!(
violations.is_empty(),
"Fully compliant storage must produce no violations"
);
}
#[test]
fn given_confidential_classification_when_auto_policy_then_requires_encryption() {
let policy = MobileStoragePolicy::for_classification(DataClassification::Confidential);
assert!(policy.requires_encryption());
}
#[test]
fn given_secret_classification_when_auto_policy_then_requires_hardware() {
let policy = MobileStoragePolicy::for_classification(DataClassification::Secret);
assert!(policy.requires_hardware_keystore());
assert!(policy.requires_encryption());
}
#[test]
fn given_credentials_classification_when_auto_policy_then_requires_hardware() {
let policy = MobileStoragePolicy::for_classification(DataClassification::Credentials);
assert!(policy.requires_hardware_keystore());
assert!(policy.requires_encryption());
}