onionwire/tests/wipe.rs

157 lines
5.2 KiB
Rust
Raw Permalink Normal View History

//! M5: wipe messages (keep identity + friends); wipe-all is a new person.
use onionwire::Store;
use onionwire::tui::{
QuitDecision, QuitPrompt, WipeDecision, WipeKind, WipePrompt, parse_slash, quit_screen_text,
wipe_screen_text,
};
fn pk(tag: u8) -> [u8; 32] {
let mut k = [0u8; 32];
k[0] = tag;
k
}
fn db_contains(home: &std::path::Path, needle: &[u8]) -> bool {
let Ok(bytes) = std::fs::read(home.join("onionwire.db")) else {
return false;
};
bytes.windows(needle.len()).any(|w| w == needle)
}
#[test]
fn wipe_clears_messages_keeps_self_and_friends() {
let dir = tempfile::tempdir().expect("tempdir");
let store = Store::open_at_with_passphrase(dir.path(), "onionwire-test").expect("open");
let me = store.self_identity().unwrap();
store
.upsert_friend(&pk(1), "a.onion", Some("alice"))
.unwrap();
store
.append_message(&pk(1), "out", b"secret-log-line-xyz")
.unwrap();
store.append_message(&pk(1), "in", b"reply").unwrap();
assert_eq!(store.list_messages(&pk(1)).unwrap().len(), 2);
store.wipe_messages().expect("wipe");
assert!(store.list_messages(&pk(1)).unwrap().is_empty());
assert_eq!(store.friend_count().unwrap(), 1);
let f = store.get_friend(&pk(1)).unwrap().expect("friend");
assert_eq!(f.petname.as_deref(), Some("alice"));
assert_eq!(f.onion, "a.onion");
let me2 = store.self_identity().unwrap();
assert_eq!(me.identity_pk, me2.identity_pk);
assert_eq!(me.identity_sk, me2.identity_sk);
drop(store);
assert!(
!db_contains(dir.path(), b"secret-log-line-xyz"),
"wipe must overwrite plaintext before VACUUM"
);
}
#[test]
fn wipe_all_removes_dir_so_next_open_is_new_identity() {
let dir = tempfile::tempdir().expect("tempdir");
let home = dir.path().join("ow");
let store = Store::open_at_with_passphrase(&home, "onionwire-test").expect("open");
let old_pk = store.self_identity().unwrap().identity_pk;
store.upsert_friend(&pk(1), "a.onion", None).unwrap();
store.append_message(&pk(1), "out", b"gone").unwrap();
drop(store);
assert!(home.join("onionwire.db").is_file());
Store::wipe_all(&home).expect("wipe-all");
assert!(!home.exists(), "data dir gone");
let store2 = Store::open_at_with_passphrase(&home, "onionwire-test").expect("reopen");
let new_pk = store2.self_identity().unwrap().identity_pk;
assert_ne!(old_pk, new_pk, "new identity key = new person");
assert_eq!(store2.friend_count().unwrap(), 0);
}
#[test]
fn slash_wipe_commands() {
assert_eq!(parse_slash("/wipe"), Some(WipeKind::Messages));
assert_eq!(parse_slash(" /wipe "), Some(WipeKind::Messages));
assert_eq!(parse_slash("/wipe-all"), Some(WipeKind::All));
assert_eq!(parse_slash("/wipe-all\n"), Some(WipeKind::All));
assert_eq!(parse_slash("wipe"), None);
assert_eq!(parse_slash("/rotate"), None);
}
#[test]
fn wipe_requires_typing_not_enter() {
let mut p = WipePrompt::messages();
assert_eq!(p.on_esc(), WipeDecision::Cancel);
assert_eq!(
p.on_char('\n'),
WipeDecision::Pending,
"Enter must not wipe"
);
for c in "WIP".chars() {
assert_eq!(p.on_char(c), WipeDecision::Pending);
}
assert_eq!(p.on_char('E'), WipeDecision::Confirm);
}
#[test]
fn wipe_all_requires_typing_wipeall() {
let mut p = WipePrompt::all();
assert_eq!(p.on_char('\n'), WipeDecision::Pending);
for c in "WIPEAL".chars() {
assert_eq!(p.on_char(c), WipeDecision::Pending);
}
assert_eq!(p.on_char('L'), WipeDecision::Confirm);
}
#[test]
fn wipe_prompt_text_matches_spec() {
let m = wipe_screen_text(WipeKind::Messages);
assert!(m.contains("Wipe message log?"));
assert!(m.contains("Identity key and friends stay."));
assert!(m.contains("Type WIPE to confirm"));
assert!(m.contains("Esc to cancel"));
let a = wipe_screen_text(WipeKind::All);
assert!(a.contains("Wipe ALL local data?"));
assert!(a.contains("This deletes your identity key. You become a new person."));
assert!(a.contains("Type WIPEALL to confirm"));
assert!(a.contains("Esc to cancel"));
}
#[test]
fn quit_requires_clear_or_quit_then_enter() {
let mut p = QuitPrompt::new();
assert_eq!(p.on_esc(), QuitDecision::Cancel);
assert_eq!(
p.on_char('\n'),
QuitDecision::Pending,
"Enter alone must not quit"
);
for c in "CLEA".chars() {
assert_eq!(p.on_char(c), QuitDecision::Pending);
}
assert_eq!(p.on_char('R'), QuitDecision::Pending, "CLEAR without Enter");
assert_eq!(p.on_char('\n'), QuitDecision::ClearAndQuit);
let mut q = QuitPrompt::new();
for c in "QUIT".chars() {
assert_eq!(q.on_char(c), QuitDecision::Pending);
}
assert_eq!(q.on_char('\n'), QuitDecision::Quit);
}
#[test]
fn quit_prompt_text_matches_spec() {
let t = quit_screen_text();
assert!(
t.contains("message history can be cleared")
|| t.contains("Message history can be cleared")
);
assert!(t.contains("identity") && t.contains("friends"));
assert!(t.contains("not") && t.contains("/wipe-all"));
assert!(t.contains("CLEAR"));
assert!(t.contains("QUIT"));
assert!(t.contains("Esc"));
}