use memsafe::Secret;
#[test]
fn new_with_writes_full_buffer() {
let mut secret = Secret::<32>::new_with(|buf| {
for (i, b) in buf.iter_mut().enumerate() {
*b = i as u8;
}
})
.unwrap();
let view = secret.read().unwrap();
for i in 0..32 {
assert_eq!(view[i], i as u8);
}
}
#[test]
fn new_with_partial_write_leaves_trailing_bytes_zero() {
let mut secret = Secret::<64>::new_with(|buf| {
buf[..5].copy_from_slice(b"hello");
})
.unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..5], b"hello");
for &byte in &view[5..] {
assert_eq!(byte, 0);
}
}
#[test]
fn new_with_no_writes_yields_zero_buffer() {
let mut secret = Secret::<16>::new_with(|_| {}).unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..], &[0u8; 16]);
}
#[test]
fn from_bytes_stores_payload() {
let raw = b"my-api-key".to_vec();
let mut secret = Secret::<32>::from_bytes(raw).unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..10], b"my-api-key");
}
#[test]
fn from_bytes_zero_fills_remaining_buffer() {
let raw = b"my-api-key".to_vec();
let mut secret = Secret::<32>::from_bytes(raw).unwrap();
let view = secret.read().unwrap();
for &byte in &view[10..] {
assert_eq!(byte, 0);
}
}
#[test]
fn from_bytes_exact_fit() {
let raw = b"my-api-key-1234".to_vec();
let mut secret = Secret::<15>::from_bytes(raw).unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..], b"my-api-key-1234");
}
#[test]
fn from_bytes_empty_input() {
let mut secret = Secret::<16>::from_bytes(Vec::<u8>::new()).unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..], &[0u8; 16]);
}
#[test]
fn from_bytes_too_long_returns_source_untouched() {
let raw = b"way-too-long-secret".to_vec();
let result = Secret::<4>::from_bytes(raw);
let (returned, _err) = match result {
Ok(_) => panic!("expected error"),
Err(e) => e,
};
assert_eq!(returned, b"way-too-long-secret");
}
#[test]
fn from_bytes_works_with_box_slice() {
let boxed: Box<[u8]> = b"my-api-key".to_vec().into_boxed_slice();
let mut secret = Secret::<32>::from_bytes(boxed).unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..10], b"my-api-key");
}
#[test]
fn from_bytes_works_with_array() {
let arr: [u8; 10] = *b"my-api-key";
let mut secret = Secret::<32>::from_bytes(arr).unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..10], b"my-api-key");
}
#[test]
fn try_from_str_works() {
let mut secret: Secret<32> = "my-api-key".try_into().unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..10], b"my-api-key");
}
#[test]
fn try_from_str_too_long_errors() {
let result: Result<Secret<4>, _> = "my-api-key".try_into();
assert!(result.is_err());
}
#[test]
fn try_from_string_works() {
let s = String::from("my-api-key");
let mut secret: Secret<32> = s.try_into().unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..10], b"my-api-key");
}
#[test]
fn try_from_string_too_long_returns_string_untouched() {
let s = String::from("way-too-long-secret");
let result: Result<Secret<4>, _> = s.try_into();
let (returned, _err) = match result {
Ok(_) => panic!("expected error"),
Err(e) => e,
};
assert_eq!(returned, "way-too-long-secret");
}
#[test]
fn write_then_read_round_trip() {
let mut secret = Secret::<16>::new_with(|buf| {
buf[..7].copy_from_slice(b"initial");
})
.unwrap();
{
let mut writer = secret.write().unwrap();
writer[..7].copy_from_slice(b"updated");
}
let view = secret.read().unwrap();
assert_eq!(&view[..7], b"updated");
}
#[test]
fn from_bytes_supports_unicode() {
let unicode_secret = "héllo-🔒-secret";
let raw = unicode_secret.as_bytes().to_vec();
let mut secret = Secret::<64>::from_bytes(raw).unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..unicode_secret.len()], unicode_secret.as_bytes());
}
#[test]
fn new_with_pattern_simulating_stream_read() {
use std::io::Read;
let source: &[u8] = b"streamed-secret";
let mut cursor = std::io::Cursor::new(source);
let mut secret = Secret::<64>::new_with(|buf| {
let _ = cursor.read(&mut buf[..]).unwrap();
})
.unwrap();
let view = secret.read().unwrap();
assert_eq!(&view[..source.len()], source);
}
#[test]
fn new_with_panic_in_init_unwinds_cleanly() {
let result = std::panic::catch_unwind(|| {
Secret::<32>::new_with(|buf| {
buf[..3].copy_from_slice(b"abc");
panic!("simulated init failure");
})
});
assert!(result.is_err(), "panic should propagate to the caller");
}
#[test]
fn read_guard_can_be_taken_repeatedly() {
let mut secret = Secret::<16>::new_with(|buf| {
buf[..6].copy_from_slice(b"stable");
})
.unwrap();
for _ in 0..100 {
let view = secret.read().unwrap();
assert_eq!(&view[..6], b"stable");
}
}
#[test]
fn interleaved_write_and_read_cycles_persist() {
let mut secret = Secret::<8>::new_with(|_| {}).unwrap();
for round in 0u8..50 {
{
let mut w = secret.write().unwrap();
w[0] = round;
assert_eq!(w[0], round);
}
let r = secret.read().unwrap();
assert_eq!(r[0], round);
}
}
#[test]
fn secrets_are_independent() {
let mut a = Secret::<16>::new_with(|b| b.fill(0xAA)).unwrap();
let mut b = Secret::<16>::new_with(|b| b.fill(0xBB)).unwrap();
{
let mut w = a.write().unwrap();
w.fill(0x11);
}
assert_eq!(b.read().unwrap()[0], 0xBB);
assert_eq!(a.read().unwrap()[0], 0x11);
}
#[test]
fn secret_is_send_across_threads() {
let mut secret = Secret::<16>::new_with(|buf| {
buf[..5].copy_from_slice(b"moved");
})
.unwrap();
let handle = std::thread::spawn(move || {
{
let view = secret.read().unwrap();
assert_eq!(&view[..5], b"moved");
}
secret
});
let mut secret = handle.join().unwrap();
assert_eq!(&secret.read().unwrap()[..5], b"moved");
}
#[test]
fn drop_after_use_does_not_panic() {
let mut secret = Secret::<32>::new_with(|b| b.fill(0x5A)).unwrap();
let _ = secret.read().unwrap();
let _ = secret.write().unwrap();
drop(secret);
let untouched = Secret::<32>::new_with(|_| {}).unwrap();
drop(untouched);
}
#[test]
fn try_from_str_leaves_borrowed_source_intact() {
let source = String::from("my-api-key");
let _secret: Secret<32> = source.as_str().try_into().unwrap();
assert_eq!(source, "my-api-key");
}
#[test]
fn try_from_str_exact_fit_and_empty() {
let mut exact: Secret<10> = "my-api-key".try_into().unwrap();
assert_eq!(&exact.read().unwrap()[..], b"my-api-key");
let mut empty: Secret<8> = "".try_into().unwrap();
assert_eq!(&empty.read().unwrap()[..], &[0u8; 8]);
}
#[test]
fn try_from_string_error_preserves_unicode_exactly() {
let s = String::from("héllo-🔒-way-too-long-for-four-bytes");
let result: Result<Secret<4>, _> = s.clone().try_into();
let (returned, _err) = match result {
Ok(_) => panic!("expected error"),
Err(e) => e,
};
assert_eq!(returned, s);
}
#[test]
fn error_kind_is_invalid_input_on_length_mismatch() {
let result: Result<Secret<4>, _> = "too-long-for-buffer".try_into();
let err = result.err().expect("expected length-mismatch error");
assert_eq!(err.inner().kind(), std::io::ErrorKind::InvalidInput);
}
#[test]
fn memory_error_source_chain_exposes_inner_io_error() {
use std::error::Error;
let result: Result<Secret<4>, _> = Secret::<4>::from_bytes(b"too-long".to_vec());
let (_returned, err) = match result {
Ok(_) => panic!("expected length-mismatch error"),
Err(e) => e,
};
let source = err.source().expect("MemoryError must expose a source");
let io_err = source
.downcast_ref::<std::io::Error>()
.expect("source should downcast to io::Error");
assert_eq!(io_err.kind(), std::io::ErrorKind::InvalidInput);
}