mod common;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use tokio::time::timeout;
use truffle_core::synced_store::StoreEvent;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
struct Slice {
owner: String,
count: u32,
note: String,
}
const CONVERGE_TIMEOUT: Duration = Duration::from_secs(15);
#[tokio::test]
async fn test_pair_converges_on_simple_write() {
let Some(authkey) = common::require_authkey("test_pair_converges_on_simple_write") else {
return;
};
common::init_test_tracing();
let pair = common::make_truffle_pair(&authkey).await;
let alpha_store = pair.alpha.synced_store::<Slice>("convergence-simple");
let beta_store = pair.beta.synced_store::<Slice>("convergence-simple");
let value = Slice {
owner: "alpha".to_string(),
count: 1,
note: "hello from alpha".to_string(),
};
alpha_store.set(value.clone()).await;
let alpha_id = &pair.alpha_device_id;
eprintln!(" waiting for beta to see alpha's slice ({alpha_id})...");
let observed = common::wait_for(CONVERGE_TIMEOUT, || async {
beta_store.get(alpha_id).await.map(|s| s.data)
})
.await;
assert_eq!(
observed.as_ref(),
Some(&value),
"beta should see alpha's slice within {CONVERGE_TIMEOUT:?}"
);
pair.stop().await;
}
#[tokio::test]
async fn test_pair_both_write_both_see() {
let Some(authkey) = common::require_authkey("test_pair_both_write_both_see") else {
return;
};
common::init_test_tracing();
let pair = common::make_truffle_pair(&authkey).await;
let alpha_store = pair.alpha.synced_store::<Slice>("convergence-both");
let beta_store = pair.beta.synced_store::<Slice>("convergence-both");
let alpha_val = Slice {
owner: "alpha".to_string(),
count: 100,
note: "alpha's slice".to_string(),
};
let beta_val = Slice {
owner: "beta".to_string(),
count: 200,
note: "beta's slice".to_string(),
};
tokio::join!(
alpha_store.set(alpha_val.clone()),
beta_store.set(beta_val.clone())
);
let alpha_id = pair.alpha_device_id.clone();
let beta_id = pair.beta_device_id.clone();
let beta_sees_alpha = common::wait_for(CONVERGE_TIMEOUT, || async {
beta_store.get(&alpha_id).await.map(|s| s.data)
})
.await;
assert_eq!(
beta_sees_alpha.as_ref(),
Some(&alpha_val),
"beta should see alpha's slice"
);
let alpha_sees_beta = common::wait_for(CONVERGE_TIMEOUT, || async {
alpha_store.get(&beta_id).await.map(|s| s.data)
})
.await;
assert_eq!(
alpha_sees_beta.as_ref(),
Some(&beta_val),
"alpha should see beta's slice"
);
let alpha_devices = alpha_store.device_ids().await;
let beta_devices = beta_store.device_ids().await;
assert!(
alpha_devices.contains(&alpha_id) && alpha_devices.contains(&beta_id),
"alpha should track both device_ids, got {alpha_devices:?}"
);
assert!(
beta_devices.contains(&alpha_id) && beta_devices.contains(&beta_id),
"beta should track both device_ids, got {beta_devices:?}"
);
pair.stop().await;
}
#[tokio::test]
async fn test_pair_converges_after_many_updates() {
let Some(authkey) = common::require_authkey("test_pair_converges_after_many_updates") else {
return;
};
common::init_test_tracing();
let pair = common::make_truffle_pair(&authkey).await;
let alpha_store = pair.alpha.synced_store::<Slice>("convergence-rapid");
let beta_store = pair.beta.synced_store::<Slice>("convergence-rapid");
for i in 1..=20 {
alpha_store
.set(Slice {
owner: "alpha".to_string(),
count: i,
note: format!("update {i}"),
})
.await;
tokio::task::yield_now().await;
}
let alpha_id = pair.alpha_device_id.clone();
let observed = common::wait_for(CONVERGE_TIMEOUT, || async {
let slice = beta_store.get(&alpha_id).await?;
if slice.data.count == 20 {
Some(slice.data)
} else {
None
}
})
.await;
assert_eq!(
observed.as_ref().map(|s| s.count),
Some(20),
"beta should converge on alpha's final value (count=20) within {CONVERGE_TIMEOUT:?}"
);
pair.stop().await;
}
#[tokio::test]
async fn test_pair_subscribe_receives_remote_event() {
let Some(authkey) = common::require_authkey("test_pair_subscribe_receives_remote_event") else {
return;
};
common::init_test_tracing();
let pair = common::make_truffle_pair(&authkey).await;
let alpha_store = pair.alpha.synced_store::<Slice>("convergence-events");
let beta_store = pair.beta.synced_store::<Slice>("convergence-events");
let mut beta_events = beta_store.subscribe();
tokio::time::sleep(Duration::from_millis(100)).await;
let value = Slice {
owner: "alpha".to_string(),
count: 42,
note: "eventful".to_string(),
};
alpha_store.set(value.clone()).await;
let alpha_id = pair.alpha_device_id.clone();
let found = timeout(CONVERGE_TIMEOUT, async {
loop {
match beta_events.recv().await {
Ok(StoreEvent::PeerUpdated {
device_id, data, ..
}) if device_id == alpha_id => {
if data == value {
break Some(data);
}
}
Ok(other) => {
eprintln!(" beta observed other event: {other:?}");
}
Err(e) => {
eprintln!(" beta event channel err: {e}");
break None;
}
}
}
})
.await
.unwrap_or(None);
assert_eq!(
found.as_ref(),
Some(&value),
"beta should receive a PeerUpdated event for alpha's write"
);
pair.stop().await;
}