restructure tests

This commit is contained in:
otsmr 2026-04-21 21:04:29 +02:00
parent bcb2403059
commit 1cf4239149
3 changed files with 232 additions and 205 deletions

View file

@ -1,5 +1,6 @@
pub mod error; pub mod error;
pub mod stores; pub mod stores;
#[cfg(test)]
pub mod tests; pub mod tests;
pub mod traits; pub mod traits;
@ -256,6 +257,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
} }
} }
#[cfg(test)]
pub(crate) async fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> { pub(crate) async fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
self.store.get_contact_version(contact_id).await self.store.get_contact_version(contact_id).await
} }

View file

@ -1,24 +1,193 @@
use crate::user_discovery::stores::InMemoryStore;
use crate::user_discovery::traits::tests::TestingUtils; use crate::user_discovery::traits::tests::TestingUtils;
use crate::user_discovery::{UserDiscovery, UserDiscoveryStore, UserDiscoveryVersion, UserID}; use crate::user_discovery::{UserDiscovery, UserDiscoveryStore, UserDiscoveryVersion, UserID};
use prost::Message; use prost::Message;
use rand::seq::SliceRandom;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::vec; use std::vec;
fn get_version_bytes(announcement: u32, promotion: u32) -> Vec<u8> { struct TestUsers<S: UserDiscoveryStore> {
UserDiscoveryVersion { names: Vec<&'static str>,
announcement, friends: Vec<Vec<usize>>,
promotion, message_flows: Vec<(usize, usize)>,
announced_users_expected: Vec<Vec<(usize, Vec<usize>)>>,
uds: Vec<UserDiscovery<S, TestingUtils>>,
}
async fn get_with_five_users<S: UserDiscoveryStore + Default + Clone>() -> TestUsers<S> {
const ALICE: usize = 0;
const BOB: usize = 1;
const CHARLIE: usize = 2;
const DAVID: usize = 3;
const FRANK: usize = 4;
let names = vec!["ALICE", "BOB", "CHARLIE", "DAVID", "FRANK"];
let mut uds = vec![];
for index in 0..names.len() {
let store = S::default();
uds.push(get_ud(index, 2, store).await);
}
TestUsers {
names,
uds,
friends: vec![
vec![BOB, CHARLIE],
vec![ALICE, CHARLIE, DAVID],
vec![ALICE, BOB, DAVID, FRANK],
vec![BOB, CHARLIE],
vec![CHARLIE],
],
message_flows: vec![
(ALICE, 1), // ALICE: own announcement sending to BOB and CHARLIE
(BOB, 2), // BOB: own announcement + promotion for ALICE
(BOB, 0), // BOBs version should not have any new messages for his friends
(ALICE, 1), // ALICE: promotion for BOB
(CHARLIE, 3), // CHARLIE: own announcement + promotion for ALICE, BOB
(DAVID, 3), // DAVID: own announcement + promotion for BOB, CHARLIE
(BOB, 2), // BOB: promotion for CHARLIE, DAVID
(CHARLIE, 1), // CHARLIE: promotion for DAVID
(FRANK, 2), // FRANK: own announcement + promotion for CHARLIE
(CHARLIE, 1), // CHARLIE: promotion for FRANK
(ALICE, 1), // ALICE: promotion for CHARLIE
],
announced_users_expected: vec![
// ALICE should now know that BOB and CHARLIE, BOB and DAVID and CHARLIE and DAVID are friends.
// Alice should also have one protected share from Frank.
vec![
(BOB, vec![CHARLIE]), // ALICE knows Bob and that CHARLIE is connected with BOB
(CHARLIE, vec![BOB]), // ALICE knows CHARLIE and that BOB is connected with CHARLIE
(DAVID, vec![BOB, CHARLIE]), // ALICE knows DAVID and that BOB and CHARLIE are connected with DAVID
],
vec![
(ALICE, vec![CHARLIE]),
(CHARLIE, vec![ALICE, DAVID]),
(DAVID, vec![CHARLIE]),
],
vec![
(ALICE, vec![BOB]),
(BOB, vec![ALICE, DAVID]),
(DAVID, vec![BOB]),
(FRANK, vec![]),
],
vec![
(ALICE, vec![BOB, CHARLIE]),
(BOB, vec![CHARLIE]),
(CHARLIE, vec![BOB]),
],
vec![(CHARLIE, vec![])],
],
}
}
#[tokio::test]
async fn test_user_discovery_in_memory_store() {
let _ = pretty_env_logger::try_init();
let users = get_with_five_users().await;
step0_exchange_in_order::<InMemoryStore>(&users).await;
step1_verify_no_new_messages::<InMemoryStore>(&users).await;
step2_verify_announced_users_expected::<InMemoryStore>(&users).await;
}
#[tokio::test]
async fn test_user_discovery_random_order_in_memory_store() {
let _ = pretty_env_logger::try_init();
let users = get_with_five_users().await;
step0_exchange_random::<InMemoryStore>(&users).await;
step1_verify_no_new_messages::<InMemoryStore>(&users).await;
step2_verify_announced_users_expected::<InMemoryStore>(&users).await;
}
async fn step0_exchange_in_order<S: UserDiscoveryStore + Clone + Default>(users: &TestUsers<S>) {
for (i, (from, count)) in users.message_flows.iter().enumerate() {
tracing::debug!("MESSAGE FLOW: {i}");
to_all_friends(*from, *count, &users).await;
}
}
async fn step0_exchange_random<S: UserDiscoveryStore + Clone + Default>(users: &TestUsers<S>) {
let mut user_ids: Vec<usize> = (0..users.names.len()).collect();
for _ in 0..100 {
user_ids.shuffle(&mut rand::rng());
for from in user_ids.clone() {
for friend in &users.friends[from] {
request_and_handle_messages(
(from, &users.uds[from]),
(*friend, &users.uds[*friend]),
None,
)
.await;
}
}
}
}
async fn step1_verify_no_new_messages<S: UserDiscoveryStore + Clone + Default>(
users: &TestUsers<S>,
) {
tracing::debug!("Now all users should have the newest version.");
for from in 0..users.names.len() {
for to in &users.friends[from] {
tracing::debug!(
"Does {} has open messages for {}?",
&users.names[from],
&users.names[*to]
);
assert_new_messages((from, &users.uds[from]), (*to, &users.uds[*to]), false).await;
}
}
}
async fn step2_verify_announced_users_expected<S: UserDiscoveryStore + Clone + Default>(
users: &TestUsers<S>,
) {
tracing::debug!("Test if all exchanges where successful.");
for (user, announcements) in users.announced_users_expected.iter().enumerate() {
let announced_users2 = users.uds[user].get_all_announced_users().await.unwrap();
let mut announced_users = HashMap::new();
for a in announced_users2 {
announced_users.insert(a.0.user_id, a.1.iter().map(|x| x.0).collect::<Vec<_>>());
}
tracing::debug!("{} knows now: {}", users.names[user], announced_users.len());
assert_eq!(announced_users.len(), announcements.len());
for (contact_id, announced_users_expected) in announcements {
let announced_users = announced_users.get(&(*contact_id as i64)).unwrap();
tracing::debug!(
"{} knows now that {} has the following friends: {}",
users.names[user],
users.names[*contact_id],
announced_users
.iter()
.map(|x| users.names[*x as usize])
.collect::<Vec<_>>()
.join(", ")
);
let announced_users: HashSet<i64> = announced_users.iter().cloned().collect();
let announced_users_expected: HashSet<i64> = announced_users_expected
.iter()
.cloned()
.map(|x| x as i64)
.collect();
assert_eq!(announced_users, announced_users_expected);
}
} }
.encode_to_vec()
} }
async fn get_ud<S: UserDiscoveryStore + Clone + Default>( async fn get_ud<S: UserDiscoveryStore + Clone + Default>(
user_id: usize, user_id: usize,
threshold: u8,
store: S,
) -> UserDiscovery<S, TestingUtils> { ) -> UserDiscovery<S, TestingUtils> {
let store = S::default();
let ud = UserDiscovery::new(store.to_owned(), TestingUtils::default()).unwrap(); let ud = UserDiscovery::new(store.to_owned(), TestingUtils::default()).unwrap();
ud.initialize_or_update(2, user_id as UserID, vec![user_id as u8; 32]) ud.initialize_or_update(threshold, user_id as UserID, vec![user_id as u8; 32])
.await .await
.unwrap(); .unwrap();
@ -47,17 +216,20 @@ async fn assert_new_messages<S: UserDiscoveryStore>(
async fn request_and_handle_messages<S: UserDiscoveryStore>( async fn request_and_handle_messages<S: UserDiscoveryStore>(
from: (usize, &UserDiscovery<S, TestingUtils>), from: (usize, &UserDiscovery<S, TestingUtils>),
to: (usize, &UserDiscovery<S, TestingUtils>), to: (usize, &UserDiscovery<S, TestingUtils>),
messages_count: usize, messages_count: Option<usize>,
) { ) {
// From sends a message with his current version to To // From sends a message with his current version to To
let to_received_version = &from.1.get_current_version().await.unwrap();
assert_eq!( if messages_count.is_some() {
to.1.should_request_new_messages(from.0 as UserID, to_received_version) let to_received_version = &from.1.get_current_version().await.unwrap();
.await assert_eq!(
.unwrap() to.1.should_request_new_messages(from.0 as UserID, to_received_version)
.is_some(), .await
true .unwrap()
); .is_some(),
true
);
}
// As To has a older version stored he sends a request to From: Give me all messages since version. // As To has a older version stored he sends a request to From: Give me all messages since version.
let from_request_version_from_to = let from_request_version_from_to =
@ -72,206 +244,58 @@ async fn request_and_handle_messages<S: UserDiscoveryStore>(
.await .await
.unwrap(); .unwrap();
assert!(new_messages.len() <= messages_count); if let Some(messages_count) = messages_count {
assert!(new_messages.len() <= messages_count);
}
to.1.handle_new_messages(from.0 as UserID, new_messages) to.1.handle_new_messages(from.0 as UserID, new_messages)
.await .await
.unwrap(); .unwrap();
assert_eq!( if messages_count.is_some() {
to.1.should_request_new_messages( assert_eq!(
from.0 as UserID, to.1.should_request_new_messages(
&from.1.get_current_version().await.unwrap() from.0 as UserID,
) &from.1.get_current_version().await.unwrap()
.await )
.unwrap() .await
.is_some(), .unwrap()
false .is_some(),
); false
);
}
} }
const ALICE: usize = 0; async fn to_all_friends<S: UserDiscoveryStore + Clone>(
const BOB: usize = 1; from: usize,
const CHARLIE: usize = 2; message_count: usize,
const DAVID: usize = 3; users: &TestUsers<S>,
const FRANK: usize = 4; ) {
const TEST_USER_COUNT: usize = 5; for friend in &users.friends[from] {
struct TestUsers<S: UserDiscoveryStore> { tracing::debug!("From {} to {}", users.names[from], users.names[*friend]);
names: [&'static str; TEST_USER_COUNT],
friends: [Vec<usize>; TEST_USER_COUNT],
uds: Vec<UserDiscovery<S, TestingUtils>>,
}
impl<S: UserDiscoveryStore + Clone + Default> TestUsers<S> { if message_count == 0 {
async fn get() -> Self { assert_new_messages(
let names = ["ALICE", "BOB", "CHARLIE", "DAVID", "FRANK"]; (from, &users.uds[from]),
let mut uds = vec![]; (*friend, &users.uds[*friend]),
for index in 0..names.len() { false,
uds.push(get_ud(index).await); )
} .await;
let friends = [ } else {
vec![BOB, CHARLIE], request_and_handle_messages(
vec![ALICE, CHARLIE, DAVID], (from, &users.uds[from]),
vec![ALICE, BOB, DAVID, FRANK], (*friend, &users.uds[*friend]),
vec![BOB, CHARLIE], Some(message_count),
vec![CHARLIE], )
]; .await;
Self {
names,
uds,
friends,
} }
} }
} }
pub async fn test_initialize_user_discovery<S: UserDiscoveryStore + Clone + Default>() { fn get_version_bytes(announcement: u32, promotion: u32) -> Vec<u8> {
#[cfg(test)] UserDiscoveryVersion {
let _ = pretty_env_logger::try_init(); announcement,
promotion,
let users = TestUsers::<S>::get().await;
async fn to_all_friends<S: UserDiscoveryStore + Clone>(
from: usize,
message_count: usize,
users: &TestUsers<S>,
) {
for friend in &users.friends[from] {
tracing::debug!("From {} to {}", users.names[from], users.names[*friend]);
if message_count == 0 {
assert_new_messages(
(from, &users.uds[from]),
(*friend, &users.uds[*friend]),
false,
)
.await;
} else {
request_and_handle_messages(
(from, &users.uds[from]),
(*friend, &users.uds[*friend]),
message_count,
)
.await;
}
}
}
let message_flows = [
// ALICE: own announcement sending to BOB and CHARLIE
(ALICE, 1),
// BOB: own announcement + promotion for ALICE
(BOB, 2),
// BOBs version should not have any new messages for his friends
(BOB, 0),
// ALICE: promotion for BOB
(ALICE, 1),
// CHARLIE: own announcement + promotion for ALICE, BOB
(CHARLIE, 3),
// DAVID: own announcement + promotion for BOB, CHARLIE
(DAVID, 3),
// BOB: promotion for CHARLIE, DAVID
(BOB, 2),
// CHARLIE: promotion for DAVID
(CHARLIE, 1),
// FRANK: own announcement + promotion for CHARLIE
(FRANK, 2),
// CHARLIE: promotion for FRANK
(CHARLIE, 1),
// ALICE: promotion for CHARLIE
(ALICE, 1),
];
for (i, (from, count)) in message_flows.into_iter().enumerate() {
tracing::debug!("MESSAGE FLOW: {i}");
to_all_friends(from, count, &users).await;
}
tracing::debug!("Now all users should have the newest version.");
for from in 0..TEST_USER_COUNT {
for to in &users.friends[from] {
tracing::debug!(
"Does {} has open messages for {}?",
&users.names[from],
&users.names[*to]
);
assert_new_messages((from, &users.uds[from]), (*to, &users.uds[*to]), false).await;
}
}
tracing::debug!("Test if all exchanges where successful.");
let announced_users_expected = [
// ALICE should now know that BOB and CHARLIE, BOB and DAVID and CHARLIE and DAVID are friends.
// Alice should also have one protected share from Frank.
(
ALICE,
vec![
(BOB, vec![CHARLIE]), // ALICE knows Bob and that CHARLIE is connected with BOB
(CHARLIE, vec![BOB]), // ALICE knows CHARLIE and that BOB is connected with CHARLIE
(DAVID, vec![BOB, CHARLIE]), // ALICE knows DAVID and that BOB and CHARLIE are connected with DAVID
],
),
(
BOB,
vec![
(ALICE, vec![CHARLIE]),
(CHARLIE, vec![ALICE, DAVID]),
(DAVID, vec![CHARLIE]),
],
),
(
CHARLIE,
vec![
(ALICE, vec![BOB]),
(BOB, vec![ALICE, DAVID]),
(DAVID, vec![BOB]),
(FRANK, vec![]),
],
),
(
DAVID,
vec![
(ALICE, vec![BOB, CHARLIE]),
(BOB, vec![CHARLIE]),
(CHARLIE, vec![BOB]),
],
),
(FRANK, vec![(CHARLIE, vec![])]),
];
for (user, announcements) in announced_users_expected {
let announced_users2 = users.uds[user].get_all_announced_users().await.unwrap();
let mut announced_users = HashMap::new();
for a in announced_users2 {
announced_users.insert(a.0.user_id, a.1.iter().map(|x| x.0).collect::<Vec<_>>());
}
tracing::debug!("{} knows now: {}", users.names[user], announced_users.len());
assert_eq!(announced_users.len(), announcements.len());
for (contact_id, announced_users_expected) in announcements {
let announced_users = announced_users.get(&(contact_id as i64)).unwrap();
tracing::debug!(
"{} knows now that {} has the following friends: {}",
users.names[user],
users.names[contact_id],
announced_users
.iter()
.map(|x| users.names[*x as usize])
.collect::<Vec<_>>()
.join(", ")
);
let announced_users: HashSet<i64> = announced_users.iter().cloned().collect();
let announced_users_expected: HashSet<i64> = announced_users_expected
.iter()
.cloned()
.map(|x| x as i64)
.collect();
assert_eq!(announced_users, announced_users_expected);
}
} }
} .encode_to_vec()
#[tokio::test]
async fn test_initialize_user_discovery_in_memory_store() {
test_initialize_user_discovery::<crate::user_discovery::stores::InMemoryStore>().await;
} }

View file

@ -97,6 +97,7 @@ pub trait UserDiscoveryUtils {
) -> impl Future<Output = Result<bool>> + Send; ) -> impl Future<Output = Result<bool>> + Send;
} }
#[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use crate::user_discovery::traits::UserDiscoveryUtils; use crate::user_discovery::traits::UserDiscoveryUtils;