mod common;
#[cfg(test)]
mod tests {
use crate::common;
use beam::adapters::*;
use beam::{Config, Node, Value};
use tokio::time::{Duration, timeout};
#[tokio::test]
async fn it_doesnt_error() {
let mut db = Node::new();
let _ = db.get("Meneldor"); }
#[tokio::test]
async fn first_get_then_put() {
let mut db = Node::new();
let mut node = db.get("Anborn");
let mut sub = node.on();
node.put("Ancalagon".into()).await.unwrap();
if let Value::Text(str) = sub.recv().await.unwrap() {
assert_eq!(&str, "Ancalagon");
}
}
#[tokio::test]
async fn first_put_then_get() {
let mut db = Node::new_with_config(
Config::default(),
vec![Box::new(MemoryStorage::new())],
vec![],
);
let mut node = db.get("Finglas1").get("Finglas2"); node.put("Fingolfin".into()).await.unwrap();
let mut sub = node.on();
if let Value::Text(str) = sub.recv().await.unwrap() {
assert_eq!(&str, "Fingolfin");
}
}
#[tokio::test]
async fn once_returns_value_or_none() {
let mut db = Node::new_with_config(
Config::default(),
vec![Box::new(MemoryStorage::new())],
vec![],
);
let mut node = db.get("Finglas1").get("Finglas2");
node.put("Fingolfin".into()).await.unwrap();
let Some(Value::Text(str)) = node.once(None).await else {
panic!("once didn't find val");
};
assert_eq!(&str, "Fingolfin");
assert!(
db.get("Fin")
.get("golf")
.get("fin")
.once(None)
.await
.is_none()
);
db.get("Fin")
.get("golf")
.get("fin")
.put(Value::Null)
.await
.unwrap();
assert!(
!db.get("Fin")
.get("golf")
.get("fin")
.once(None)
.await
.is_none()
);
}
#[tokio::test]
#[allow(unreachable_code)]
async fn connect_and_sync_over_websocket() {
let config = Config::default();
let ws_server = WsServer::new(config.clone());
let mut peer1 = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![Box::new(ws_server.clone())],
);
let ws_client = OutgoingWebsocketManager::new(
config.clone(),
vec!["ws://localhost:4944/ws".to_string()],
);
let mut peer2 = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![Box::new(ws_client.clone())],
);
common::wait_for_port(4944, 5000).await;
common::wait_for_peer_count(&ws_server, 1, 5000).await;
common::wait_for_connected_count(&ws_client, 1, 5000).await;
let mut sub1 = peer1.get("beta").get("name").on();
let mut sub2 = peer2.get("alpha").get("name").on();
peer1
.get("alpha")
.get("name")
.put("Amandil".into())
.await
.unwrap();
peer2
.get("beta")
.get("name")
.put("Beregond".into())
.await
.unwrap();
let recv_val = timeout(Duration::from_secs(30), sub1.recv())
.await
.expect("timeout waiting for Beregond — mesh propagation from peer2 failed")
.expect("sub1 channel closed");
match recv_val {
Value::Text(str) => {
assert_eq!(&str, "Beregond");
}
_ => panic!("Expected Value::Text, got {:?}", recv_val),
}
let recv_val = timeout(Duration::from_secs(30), sub2.recv())
.await
.expect("timeout waiting for Amandil — mesh propagation from peer1 failed")
.expect("sub2 channel closed");
match recv_val {
Value::Text(str) => {
assert_eq!(&str, "Amandil");
}
_ => panic!("Expected Value::Text, got {:?}", recv_val),
}
peer1.stop();
peer2.stop();
}
#[tokio::test]
async fn websocket_sync_over_relay_peer() {
let config = Config::default();
let ws_server = WsServer::new_with_config(
config.clone(),
WsServerConfig {
port: 4948,
..WsServerConfig::default()
},
);
let mut relay = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![Box::new(ws_server.clone())],
);
let ws_client1 = OutgoingWebsocketManager::new(
config.clone(),
vec!["ws://localhost:4948/ws".to_string()],
);
let mut peer1 = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![Box::new(ws_client1.clone())],
);
let ws_client2 = OutgoingWebsocketManager::new(
config.clone(),
vec!["ws://localhost:4948/ws".to_string()],
);
let mut peer2 = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![Box::new(ws_client2.clone())],
);
common::wait_for_port(4948, 5000).await;
common::wait_for_peer_count(&ws_server, 2, 5000).await;
common::wait_for_connected_count(&ws_client1, 1, 5000).await;
common::wait_for_connected_count(&ws_client2, 1, 5000).await;
let mut sub1 = peer1.get("beta").get("name").on();
let mut sub2 = peer2.get("alpha").get("name").on();
peer1
.get("alpha")
.get("name")
.put("Amandil".into())
.await
.unwrap();
peer2
.get("beta")
.get("name")
.put("Beregond".into())
.await
.unwrap();
let val = timeout(Duration::from_secs(30), sub1.recv())
.await
.expect("timeout waiting for sub1 — mesh propagation from peer2 failed")
.expect("sub1 channel closed");
match val {
Value::Text(str) => {
assert_eq!(&str, "Beregond");
}
_ => panic!("Expected Value::Text, got {:?}", val),
}
let val = timeout(Duration::from_secs(30), sub2.recv())
.await
.expect("timeout waiting for sub2 — mesh propagation from peer1 failed")
.expect("sub2 channel closed");
match val {
Value::Text(str) => {
assert_eq!(&str, "Amandil");
}
_ => panic!("Expected Value::Text, got {:?}", val),
}
peer1.stop();
peer2.stop();
relay.stop();
}
#[tokio::test]
async fn websocket_sync_over_2_relay_peers() {
let config = Config::default();
let ws_server1 = WsServer::new_with_config(
config.clone(),
WsServerConfig {
port: 4950,
..WsServerConfig::default()
},
);
let ws_server2 = WsServer::new_with_config(
config.clone(),
WsServerConfig {
port: 4952,
..WsServerConfig::default()
},
);
let relay2_client = OutgoingWebsocketManager::new(
config.clone(),
vec!["ws://localhost:4950/ws".to_string()],
);
let mut relay1 = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![Box::new(ws_server1.clone())],
);
let mut relay2 = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![
Box::new(ws_server2.clone()),
Box::new(relay2_client.clone()),
],
);
let ws_client = OutgoingWebsocketManager::new(
config.clone(),
vec!["ws://localhost:4950/ws".to_string()],
);
let mut peer1 = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![Box::new(ws_client.clone())],
);
let ws_client2 = OutgoingWebsocketManager::new(
config.clone(),
vec!["ws://localhost:4952/ws".to_string()],
);
let mut peer2 = Node::new_with_config(
config.clone(),
vec![Box::new(MemoryStorage::new())],
vec![Box::new(ws_client2.clone())],
);
common::wait_for_port(4950, 5000).await;
common::wait_for_port(4952, 5000).await;
common::wait_for_peer_count(&ws_server1, 2, 5000).await;
common::wait_for_peer_count(&ws_server2, 1, 5000).await;
common::wait_for_connected_count(&ws_client, 1, 5000).await;
common::wait_for_connected_count(&ws_client2, 1, 5000).await;
common::wait_for_connected_count(&relay2_client, 1, 5000).await;
let mut sub1 = peer1.get("beta").get("name").on();
let mut sub2 = peer2.get("alpha").get("name").on();
peer1
.get("alpha")
.get("name")
.put("Amandil".into())
.await
.unwrap();
peer2
.get("beta")
.get("name")
.put("Beregond".into())
.await
.unwrap();
let val = timeout(Duration::from_secs(30), sub1.recv())
.await
.expect("timeout waiting for sub1 — mesh propagation from peer2 via relay failed")
.expect("sub1 channel closed");
match val {
Value::Text(str) => {
assert_eq!(&str, "Beregond");
}
_ => panic!("Expected Value::Text, got {:?}", val),
}
let val = timeout(Duration::from_secs(30), sub2.recv())
.await
.expect("timeout waiting for sub2 — mesh propagation from peer1 via relay failed")
.expect("sub2 channel closed");
match val {
Value::Text(str) => {
assert_eq!(&str, "Amandil");
}
_ => panic!("Expected Value::Text, got {:?}", val),
}
assert!(peer2.get("gamma").get("name").once(None).await.is_none());
assert!(peer1.get("gamma").get("name").once(None).await.is_none());
peer1
.get("gamma")
.get("name")
.put("once".into())
.await
.unwrap();
let Some(Value::Text(str)) = peer2.get("gamma").get("name").once(None).await else {
panic!("once: Expected Value::Text");
};
assert_eq!(&str, "once");
peer1.stop();
peer2.stop();
relay1.stop();
relay2.stop();
}
#[tokio::test]
async fn redb_storage_persists() {
let _ = env_logger::try_init();
use beam::adapters::RedbStorage;
use std::time::Duration;
use tokio::time::sleep;
let temp_path = std::env::temp_dir().join("beam-redb-test.ron");
let _ = std::fs::remove_file(&temp_path);
let config = Config::default();
{
let mut db = Node::new_with_config(
config.clone(),
vec![Box::new(RedbStorage::new_with_config(
config.clone(),
temp_path.to_string_lossy().as_ref(),
None,
))],
vec![],
);
db.get("Feanor").put("Noldor".into()).await.unwrap();
sleep(Duration::from_millis(500)).await;
db.stop();
sleep(Duration::from_millis(1000)).await;
}
{
let mut db2 = Node::new_with_config(
config.clone(),
vec![Box::new(RedbStorage::new_with_config(
config.clone(),
temp_path.to_string_lossy().as_ref(),
None,
))],
vec![],
);
let mut sub = db2.map();
let result = tokio::time::timeout(Duration::from_secs(3), sub.recv()).await;
let (key, value) = result
.expect("timeout waiting for map replay")
.expect("broadcast recv error");
assert_eq!(key, "Feanor"); if let Value::Text(s) = value {
assert_eq!(&s, "Noldor");
} else {
panic!("Expected Value::Text, got {:?}", value);
}
db2.stop();
}
let _ = std::fs::remove_file(&temp_path);
}
#[tokio::test]
async fn redb_storage_flush_returns_ok() {
use beam::adapters::RedbStorage;
use std::time::Duration;
use tokio::time::sleep;
let temp_path = std::env::temp_dir().join("beam-redb-flush.ron");
let _ = std::fs::remove_file(&temp_path);
let config = Config::default();
let mut db = Node::new_with_config(
config.clone(),
vec![Box::new(RedbStorage::new_with_config(
config.clone(),
temp_path.to_string_lossy().as_ref(),
None,
))],
vec![],
);
db.get("FlushTest").put("pre_flush".into()).await.unwrap();
sleep(Duration::from_millis(200)).await;
let result = db.flush_storage(Some(Duration::from_secs(3))).await;
assert!(result.is_ok(), "flush_storage returned {:?}", result);
db.stop();
let _ = std::fs::remove_file(&temp_path);
}
#[tokio::test]
async fn flush_acts_as_write_barrier() {
use beam::adapters::RedbStorage;
use std::time::Duration;
let temp_path =
std::env::temp_dir().join(format!("beam-flush-barrier-{}.redb", std::process::id()));
let _ = std::fs::remove_file(&temp_path);
let config = Config::default();
{
let mut db = Node::new_with_config(
config.clone(),
vec![Box::new(RedbStorage::new_with_config(
config.clone(),
temp_path.to_string_lossy().as_ref(),
None,
))],
vec![],
);
db.get("BarrierKey")
.put("barrier_value".into())
.await
.unwrap();
let result = db.flush_storage(Some(Duration::from_secs(5))).await;
assert!(result.is_ok(), "flush_storage returned {:?}", result);
db.stop();
tokio::time::sleep(Duration::from_millis(200)).await;
}
{
let mut db = Node::new_with_config(
config,
vec![Box::new(RedbStorage::new_with_config(
Config::default(),
temp_path.to_string_lossy().as_ref(),
None,
))],
vec![],
);
let val = db
.get("BarrierKey")
.once(Some(Duration::from_secs(3)))
.await;
assert_eq!(
val,
Some(Value::Text("barrier_value".to_string())),
"data should be persisted before flush ack returned"
);
db.stop();
}
let _ = std::fs::remove_file(&temp_path);
}
}