2026-09-10 02:49:42 -04:00
|
|
|
//! M5: wipe messages (keep identity + friends); wipe-all is a new person.
|
|
|
|
|
|
|
|
|
|
use onionwire::Store;
|
2026-09-10 17:19:50 -04:00
|
|
|
use onionwire::tui::{
|
|
|
|
|
QuitDecision, QuitPrompt, WipeDecision, WipeKind, WipePrompt, parse_slash, quit_screen_text,
|
|
|
|
|
wipe_screen_text,
|
|
|
|
|
};
|
2026-09-10 02:49:42 -04:00
|
|
|
|
|
|
|
|
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");
|
2026-09-10 17:19:50 -04:00
|
|
|
let store = Store::open_at_with_passphrase(dir.path(), "onionwire-test").expect("open");
|
2026-09-10 02:49:42 -04:00
|
|
|
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");
|
2026-09-10 17:19:50 -04:00
|
|
|
let store = Store::open_at_with_passphrase(&home, "onionwire-test").expect("open");
|
2026-09-10 02:49:42 -04:00
|
|
|
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");
|
|
|
|
|
|
2026-09-10 17:19:50 -04:00
|
|
|
let store2 = Store::open_at_with_passphrase(&home, "onionwire-test").expect("reopen");
|
2026-09-10 02:49:42 -04:00
|
|
|
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"));
|
|
|
|
|
}
|
2026-09-10 17:19:50 -04:00
|
|
|
|
|
|
|
|
#[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"));
|
|
|
|
|
}
|