onionwire/tests/qr.rs

133 lines
4.2 KiB
Rust
Raw Permalink Normal View History

//! M2 QR: roundtrip, garbage → Err, same-k rescan does not duplicate.
use std::sync::{Mutex, MutexGuard};
use onionwire::qr;
use onionwire::Store;
static ENV_LOCK: Mutex<()> = Mutex::new(());
struct TempHome {
_dir: tempfile::TempDir,
_guard: MutexGuard<'static, ()>,
}
impl TempHome {
fn new() -> Self {
let dir = tempfile::tempdir().expect("tempdir");
let guard = ENV_LOCK.lock().expect("env lock");
unsafe {
std::env::set_var("ONIONWIRE_HOME", dir.path());
}
Self {
_dir: dir,
_guard: guard,
}
}
}
impl Drop for TempHome {
fn drop(&mut self) {
unsafe {
std::env::remove_var("ONIONWIRE_HOME");
}
}
}
fn v3onion(tag: &str) -> String {
format!("{tag}.onion")
}
#[test]
fn encode_decode_roundtrip() {
let _home = TempHome::new();
let store = Store::open().expect("open");
let me = store.self_identity().expect("self");
let spk = [7u8; 32];
let onion = v3onion("alice-locator");
let raw = qr::encode(&me.identity_sk, &onion, &spk).expect("encode");
assert!(raw.starts_with("onionwire:v1:k="), "payload prefix: {raw}");
assert!(raw.contains(":o="));
assert!(raw.contains(":spk="));
assert!(raw.contains(":sig="));
let p = qr::decode(&raw).expect("decode");
assert_eq!(p.pubkey, me.identity_pk);
assert_eq!(p.onion, onion);
assert_eq!(p.signed_prekey, spk);
}
#[test]
fn garbage_is_err() {
assert!(qr::decode("not a qr").is_err());
assert!(qr::decode("").is_err());
assert!(qr::decode("onionwire:v1:k=aa").is_err());
assert!(qr::decode("🔥").is_err());
}
#[test]
fn missing_sig_is_err() {
let _home = TempHome::new();
let store = Store::open().expect("open");
let me = store.self_identity().expect("self");
let raw = qr::encode(&me.identity_sk, &v3onion("x"), &[1u8; 32]).expect("encode");
let stripped = raw.rsplit_once(":sig=").expect("sig field").0;
assert!(qr::decode(stripped).is_err());
assert!(qr::decode(&format!("{stripped}:sig=")).is_err());
}
#[test]
fn bad_sig_is_err() {
let _home = TempHome::new();
let store = Store::open().expect("open");
let me = store.self_identity().expect("self");
let raw = qr::encode(&me.identity_sk, &v3onion("x"), &[1u8; 32]).expect("encode");
let (head, sig) = raw.rsplit_once(":sig=").expect("sig field");
let mut chars: Vec<char> = sig.chars().collect();
let last = chars.last_mut().expect("sig chars");
*last = if *last == '0' { '1' } else { '0' };
let flipped: String = chars.into_iter().collect();
assert!(qr::decode(&format!("{head}:sig={flipped}")).is_err());
}
#[test]
fn unicode_qr_renders() {
let _home = TempHome::new();
let store = Store::open().expect("open");
let me = store.self_identity().expect("self");
let raw = qr::encode(&me.identity_sk, &v3onion("x"), &[1u8; 32]).expect("encode");
let art = qr::render_unicode(&raw).expect("render");
assert!(
art.contains('█') || art.contains('▀') || art.contains('▄') || art.contains('▌'),
"expected unicode blocks, got {art:?}"
);
}
#[test]
fn same_k_rescan_updates_onion_not_duplicate() {
let _home = TempHome::new();
let store = Store::open().expect("open");
let me = store.self_identity().expect("self");
let spk = [9u8; 32];
let p1 = qr::decode(&qr::encode(&me.identity_sk, &v3onion("old"), &spk).unwrap()).unwrap();
store
.upsert_friend(&p1.pubkey, &p1.onion, Some("alice"))
.unwrap();
let p2 = qr::decode(&qr::encode(&me.identity_sk, &v3onion("new"), &spk).unwrap()).unwrap();
store.upsert_friend(&p2.pubkey, &p2.onion, None).unwrap();
assert_eq!(store.friend_count().unwrap(), 1);
let f = store.get_friend(&p2.pubkey).unwrap().unwrap();
assert_eq!(f.onion, v3onion("new"));
assert_eq!(f.petname.as_deref(), Some("alice"));
}
#[test]
fn unknown_k_decode_does_not_insert() {
let _home = TempHome::new();
let store = Store::open().expect("open");
let me = store.self_identity().expect("self");
let raw = qr::encode(&me.identity_sk, &v3onion("x"), &[1u8; 32]).unwrap();
let _p = qr::decode(&raw).unwrap();
assert_eq!(store.friend_count().unwrap(), 0);
}