#![cfg(feature = "fjall")]
use beam::actor::Actor;
use beam::types::Value;
use std::time::Duration;
fn unique_fjall_path(test_name: &str) -> String {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
std::env::temp_dir()
.join(format!(
"beam-fjall-{}-{}-{}",
test_name,
std::process::id(),
nanos
))
.to_str()
.expect("temp path must be utf-8")
.to_string()
}
fn node_with_fjall(path: &str) -> beam::Node {
use beam::Config;
use beam::adapters::FjallStorage;
let storage = FjallStorage::new_with_config(Config::default(), path);
beam::Node::new_with_config(
Config::default(),
vec![Box::new(storage) as Box<dyn Actor>],
vec![],
)
}
#[tokio::test]
async fn e2e_fjall_put_get_roundtrip() {
let path = unique_fjall_path("roundtrip");
let mut node = node_with_fjall(&path);
node.get("k")
.put("v".into())
.await
.expect("fjall put should ack after insert");
let got = node.get("k").once(Some(Duration::from_secs(2))).await;
assert_eq!(got, Some(Value::Text("v".to_string())));
let _ = std::fs::remove_dir_all(&path);
}
#[tokio::test]
async fn e2e_fjall_sequential_puts_serialize_correctly() {
let path = unique_fjall_path("sequential");
let mut node = node_with_fjall(&path);
for i in 0..25 {
let key = format!("conc_{}", i);
let val = format!("val_{}", i);
node.get(&key)
.put(val.clone().into())
.await
.expect("put should ack");
let got = node.get(&key).once(Some(Duration::from_secs(2))).await;
assert_eq!(
got,
Some(Value::Text(val.clone())),
"put {} → get observed {:?}, expected {:?}",
i,
got,
val
);
}
let _ = std::fs::remove_dir_all(&path);
}
#[tokio::test]
async fn e2e_fjall_nested_children_roundtrip() {
let path = unique_fjall_path("nested");
let mut node = node_with_fjall(&path);
for (child, val) in [
("child_a", "a_value"),
("child_b", "b_value"),
("child_c", "c_value"),
] {
node.get("parent")
.get(child)
.put(val.into())
.await
.expect("child put");
}
for (child, expected) in [
("child_a", "a_value"),
("child_b", "b_value"),
("child_c", "c_value"),
] {
let got = node
.get("parent")
.get(child)
.once(Some(Duration::from_secs(2)))
.await;
assert_eq!(got, Some(Value::Text(expected.to_string())));
}
let _ = std::fs::remove_dir_all(&path);
}
#[tokio::test]
async fn e2e_fjall_lww_prefers_newer_value() {
let path = unique_fjall_path("lww");
let mut node = node_with_fjall(&path);
node.get("lww_key").put("older".into()).await.unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;
node.get("lww_key").put("newer".into()).await.unwrap();
let got = node.get("lww_key").once(Some(Duration::from_secs(2))).await;
assert_eq!(got, Some(Value::Text("newer".to_string())));
let _ = std::fs::remove_dir_all(&path);
}
#[tokio::test]
async fn e2e_fjall_flush_persists_data() {
let path = unique_fjall_path("flush");
let mut node = node_with_fjall(&path);
node.get("f1").put("v1".into()).await.unwrap();
node.get("f2").put("v2".into()).await.unwrap();
node.flush_storage(Some(Duration::from_secs(5)))
.await
.expect("flush should ack after persist");
let got1 = node.get("f1").once(Some(Duration::from_secs(2))).await;
let got2 = node.get("f2").once(Some(Duration::from_secs(2))).await;
assert_eq!(got1, Some(Value::Text("v1".to_string())));
assert_eq!(got2, Some(Value::Text("v2".to_string())));
let _ = std::fs::remove_dir_all(&path);
}
#[tokio::test]
async fn e2e_fjall_isolated_persistence_two_dirs() {
let path_a = unique_fjall_path("iso_a");
let path_b = unique_fjall_path("iso_b");
{
let mut node = node_with_fjall(&path_a);
node.get("alpha").put("1".into()).await.unwrap();
let got = node.get("alpha").once(Some(Duration::from_secs(2))).await;
assert_eq!(got, Some(Value::Text("1".to_string())));
}
{
let mut node = node_with_fjall(&path_b);
node.get("beta").put("2".into()).await.unwrap();
let got = node.get("beta").once(Some(Duration::from_secs(2))).await;
assert_eq!(got, Some(Value::Text("2".to_string())));
}
let _ = std::fs::remove_dir_all(&path_a);
let _ = std::fs::remove_dir_all(&path_b);
}