mod common;
use std::time::Duration;
use tokio::time::timeout;
use beam::adapters::RedbStorage;
use beam::sea;
use beam::sea::session::InMemorySessionStorage;
use beam::{Config, Node, Value};
#[tokio::test]
async fn quickstart_put_subscribe() {
let mut db = Node::new();
let mut sub = db.get("greeting").on();
db.get("greeting")
.put(Value::Text("hello".into()))
.await
.unwrap();
let value = timeout(Duration::from_secs(3), sub.recv())
.await
.expect("timeout waiting for on() callback")
.expect("broadcast recv error");
assert_eq!(value, Value::Text("hello".into()));
db.stop();
}
#[tokio::test]
async fn nested_graph_map() {
let mut db = Node::new();
db.batch_put(vec![
(
vec!["users".into(), "alice".into()],
Value::Text("admin".into()),
),
(
vec!["users".into(), "bob".into()],
Value::Text("user".into()),
),
])
.await
.unwrap();
let mut sub = db.get("users").map();
let mut received = std::collections::HashMap::new();
for _ in 0..2 {
let (key, value) = timeout(Duration::from_secs(3), sub.recv())
.await
.expect("timeout waiting for map() replay")
.expect("broadcast recv error");
received.insert(key, value);
}
assert_eq!(received.get("alice"), Some(&Value::Text("admin".into())));
assert_eq!(received.get("bob"), Some(&Value::Text("user".into())));
db.stop();
}
#[tokio::test]
async fn user_auth_lifecycle() {
let mut db = Node::new();
let storage = InMemorySessionStorage::new();
let alice = db
.user()
.create("testuser", "testpass")
.await
.expect("user creation failed");
assert!(alice.is_authenticated());
assert_eq!(alice.alias().as_deref(), Some("testuser"));
let authed = db
.user()
.auth("testuser", "testpass")
.await
.expect("auth failed");
assert!(authed.is_authenticated());
assert_eq!(authed.pub_key(), alice.pub_key());
alice.save_to(&storage).await.expect("save_to failed");
let recalled = sea::User::recall("testuser", &storage)
.await
.expect("recall failed");
assert!(recalled.is_authenticated());
assert_eq!(recalled.pub_key(), alice.pub_key());
let alice_clone = alice.clone();
alice.leave();
assert!(!alice_clone.is_authenticated());
db.stop();
}
#[tokio::test]
async fn encrypt_decrypt_roundtrip() {
let alice = sea::generate_pair().await.expect("generate_pair failed");
let bob = sea::generate_pair().await.expect("generate_pair failed");
let payload = serde_json::json!({ "message": "Hello from Alice!" });
let signed = sea::sign(&payload, &alice).await.expect("sign failed");
let verified = sea::verify(&signed, &alice.pub_key)
.await
.expect("verify failed");
assert_eq!(verified, payload);
let wrong = sea::verify(&signed, &bob.pub_key).await;
assert!(wrong.is_err());
let secret_data = serde_json::json!({ "message": "secret" });
let encrypted = sea::encrypt(&secret_data, &alice, bob.epub_key.as_deref())
.await
.expect("encrypt failed");
let decrypted = sea::decrypt(&encrypted, &bob, alice.epub_key.as_deref())
.await
.expect("decrypt failed");
assert_eq!(decrypted, secret_data);
let sym_key = [0u8; 32];
let sym_enc = sea::encrypt_symmetric(&payload, &sym_key)
.await
.expect("sym encrypt failed");
let sym_dec = sea::decrypt_symmetric(&sym_enc, &sym_key)
.await
.expect("sym decrypt failed");
assert_eq!(sym_dec, payload);
}
#[tokio::test]
async fn persistent_storage_restart() {
let temp_path =
std::env::temp_dir().join(format!("beam-e2e-persist-{}.redb", std::process::id()));
let _ = std::fs::remove_file(&temp_path);
let path_str = temp_path.to_string_lossy().to_string();
let config = Config::default();
{
let mut db = Node::new_with_config(
config.clone(),
vec![Box::new(RedbStorage::new_with_config(
config.clone(),
&path_str,
None,
))],
vec![],
);
db.get("persisted_key")
.put(Value::Text("persisted_value".into()))
.await
.unwrap();
db.flush_storage(Some(Duration::from_secs(5)))
.await
.expect("flush failed");
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(),
&path_str,
None,
))],
vec![],
);
let value = db
.get("persisted_key")
.once(Some(Duration::from_secs(3)))
.await;
assert_eq!(
value,
Some(Value::Text("persisted_value".into())),
"data should survive node restart via RedbStorage"
);
db.stop();
}
let _ = std::fs::remove_file(&temp_path);
}