onionwire/tests/profile.rs

166 lines
5.9 KiB
Rust
Raw Normal View History

//! M7: signed friend-visible profile frames + slash parse.
use ed25519_dalek::SigningKey;
use onionwire::Store;
use onionwire::profile::{self};
use onionwire::tui::{SlashCmd, WipeKind, parse_cmd, parse_slash};
use rand::rngs::OsRng;
fn store() -> (tempfile::TempDir, Store) {
let dir = tempfile::tempdir().expect("tempdir");
let store = Store::open_at_with_passphrase(dir.path(), "onionwire-test").expect("open");
(dir, store)
}
#[test]
fn signed_profile_upserts_friend_row_keeps_fingerprint() {
let (_dir, store) = store();
let sk = SigningKey::generate(&mut OsRng);
let pk = sk.verifying_key().to_bytes();
store
.upsert_friend(&pk, "alice.onion", Some("alice"))
.unwrap();
let before = store.get_friend(&pk).unwrap().unwrap();
let prf = profile::sign(&sk.to_bytes(), "Ali", "hi", "", 2_000_000_000).expect("sign");
assert!(
store.apply_profile(&pk, &prf).unwrap(),
"newer signed profile must apply"
);
let after = store.get_friend(&pk).unwrap().unwrap();
assert_eq!(after.fingerprint, before.fingerprint);
assert_eq!(after.petname.as_deref(), Some("alice"));
assert_eq!(after.onion, "alice.onion");
assert_eq!(store.friend_count().unwrap(), 1);
let got = store.friend_profile(&pk).unwrap().expect("row");
assert_eq!(got.display_name, "Ali");
assert_eq!(got.bio, "hi");
assert_eq!(got.xmr_addr, "");
assert_eq!(got.updated_at, 2_000_000_000);
}
#[test]
fn wrong_key_profile_does_not_change_row() {
let (_dir, store) = store();
let owner = SigningKey::generate(&mut OsRng);
let owner_pk = owner.verifying_key().to_bytes();
store.upsert_friend(&owner_pk, "old.onion", None).unwrap();
let first = profile::sign(&owner.to_bytes(), "real", "", "", 2_000_000_000).unwrap();
assert!(store.apply_profile(&owner_pk, &first).unwrap());
let other = SigningKey::generate(&mut OsRng);
let spoof = profile::sign(&other.to_bytes(), "evil", "no", "", 2_000_000_001).unwrap();
assert!(!store.apply_profile(&owner_pk, &spoof).unwrap());
let got = store.friend_profile(&owner_pk).unwrap().unwrap();
assert_eq!(got.display_name, "real");
}
#[test]
fn stale_ts_profile_does_not_change_row() {
let (_dir, store) = store();
let sk = SigningKey::generate(&mut OsRng);
let pk = sk.verifying_key().to_bytes();
store.upsert_friend(&pk, "old.onion", None).unwrap();
let newer = profile::sign(&sk.to_bytes(), "new", "", "", 100).unwrap();
assert!(store.apply_profile(&pk, &newer).unwrap());
let stale = profile::sign(&sk.to_bytes(), "old", "", "", 50).unwrap();
assert!(!store.apply_profile(&pk, &stale).unwrap());
assert_eq!(
store.friend_profile(&pk).unwrap().unwrap().display_name,
"new"
);
}
#[test]
fn unknown_pubkey_profile_is_ignored() {
let (_dir, store) = store();
let sk = SigningKey::generate(&mut OsRng);
let pk = sk.verifying_key().to_bytes();
let prf = profile::sign(&sk.to_bytes(), "ghost", "", "", 2_000_000_000).unwrap();
assert!(!store.apply_profile(&pk, &prf).unwrap());
assert!(store.friend_profile(&pk).unwrap().is_none());
assert_eq!(store.friend_count().unwrap(), 0);
}
#[test]
fn bio_over_512_rejected() {
let bio = "x".repeat(513);
let sk = [7u8; 32];
assert!(profile::sign(&sk, "n", &bio, "", 1).is_err());
let mut out = b"prf n\n".to_vec();
out.extend_from_slice(bio.as_bytes());
out.extend_from_slice(b"\n\n1\n");
out.extend_from_slice("aa".repeat(64).as_bytes());
assert!(profile::decode(&out).is_none());
}
#[test]
fn newline_in_name_rejected() {
let sk = [7u8; 32];
assert!(profile::sign(&sk, "bad\nname", "bio", "", 1).is_err());
assert!(profile::decode(b"prf bad\nname\nbio\n\n1\n00").is_none());
}
#[test]
fn chat_is_not_profile() {
assert!(profile::decode(b"hello wire").is_none());
}
const MAINNET_STD: &str = "4AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx3skxNgYeYTRj5UzqtReoS44qo9mtmXCqY45DJ852K5Jv2684Rge";
#[test]
fn self_profile_roundtrip() {
let (_dir, store) = store();
store.set_self_profile("me", "a bio", MAINNET_STD).unwrap();
let got = store.self_profile().unwrap();
assert_eq!(got.display_name, "me");
assert_eq!(got.bio, "a bio");
assert_eq!(got.xmr_addr, MAINNET_STD);
assert!(got.updated_at > 0);
store.set_self_profile("", "", "").unwrap();
let empty = store.self_profile().unwrap();
assert_eq!(empty.display_name, "");
assert_eq!(empty.xmr_addr, "");
assert!(empty.updated_at >= got.updated_at);
}
#[test]
fn self_profile_rejects_short_xmr_addr() {
let (_dir, store) = store();
assert!(store.set_self_profile("me", "a bio", "4abc").is_err());
assert!(profile::validate("me", "a bio", "4abc").is_err());
assert!(profile::validate("me", "a bio", "").is_ok());
assert!(profile::validate("me", "a bio", MAINNET_STD).is_ok());
}
#[test]
fn slash_profile_who_and_wipe_still_parse() {
assert_eq!(parse_cmd("/profile"), Some(SlashCmd::Profile));
assert_eq!(parse_cmd(" /profile "), Some(SlashCmd::Profile));
assert_eq!(parse_cmd("/who"), Some(SlashCmd::Who));
assert_eq!(parse_cmd("/who\n"), Some(SlashCmd::Who));
assert_eq!(parse_cmd("/wipe"), Some(SlashCmd::Wipe(WipeKind::Messages)));
assert_eq!(parse_slash("/wipe"), Some(WipeKind::Messages));
assert_eq!(parse_slash("/wipe-all"), Some(WipeKind::All));
assert_eq!(parse_slash("/profile"), None);
assert_eq!(parse_cmd("/rotate"), None);
}
#[test]
fn profile_save_empty_name_ok_no_confirm_phrase() {
let saved = onionwire::tui::ProfileEditor::new("", "", "").on_enter();
assert_eq!(saved.display_name, "");
assert_eq!(saved.bio, "");
assert_eq!(saved.xmr_addr, "");
}
#[test]
fn who_empty_says_no_profile_yet() {
let t = onionwire::tui::who_overlay_text("abcd", "x.onion", None);
assert!(t.contains("no profile yet"));
assert!(t.contains("abcd"));
assert!(t.contains("x.onion"));
}