server/lib/tests/estate_over_the_wire.rs
//! **A twin pulls its primary's estate over the wire, and a new main is
//! recharged from its twin the same way** (owner plan 2b, 2026-09-27).
//!
//! A real `EstateService` on a real tonic server over a data root; the
//! follower is `estate_wire::pull_remote`. What is proven: the config and the
//! side stores cross the network (chunked for a large audit segment), a second
//! pull moves nothing, nothing outside the estate is served, and the SAME calls
//! the other way round put a wiped main back.
use std::path::{Path, PathBuf};
use std::sync::Arc;
use holger_server_lib::estate_wire;
use holger_server_lib::exposed::fast_routes::FastRoutes;
use holger_server_lib::grpc::holger_estate_proto::estate_service_client::EstateServiceClient;
use holger_server_lib::grpc::holger_estate_proto::estate_service_server::EstateServiceServer;
use holger_server_lib::grpc::holger_estate_proto::FetchRequest;
use holger_server_lib::grpc::HolgerGrpc;
fn dir(name: &str) -> PathBuf {
let p = std::env::temp_dir().join(format!(
"estate-over-the-wire-{name}-{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&p);
std::fs::create_dir_all(&p).unwrap();
p
}
fn put(root: &Path, rel: &str, bytes: &[u8]) {
let p = root.join(rel);
std::fs::create_dir_all(p.parent().unwrap()).unwrap();
std::fs::write(p, bytes).unwrap();
}
async fn serve(root: &Path) -> String {
let svc = Arc::new(HolgerGrpc::new(FastRoutes::new(Vec::new())).with_data_root(root));
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let at = listener.local_addr().unwrap();
tokio::spawn(async move {
tonic::transport::Server::builder()
.add_service(EstateServiceServer::new(svc).max_encoding_message_size(16 << 20))
.serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
.await
.unwrap();
});
format!("http://{at}")
}
#[tokio::test]
async fn a_twin_follows_over_the_wire_and_recharges_a_wiped_main() {
let (main, twin) = (dir("main"), dir("twin"));
put(&main, "holger.ron", b"(repositories: [])");
put(
&main,
"holger-audit/seg-000001.arrow",
&vec![3u8; 9_000_000],
);
put(&main, "holger-passkeys/credentials.json", b"{\"admin\":1}");
put(
&main,
"holger-entitlements/crates.json",
b"{\"state\":\"Paid\"}",
);
put(&main, "secrets-not-in-the-estate/key", b"never served");
// FOLLOW: the twin pulls the main's estate.
let at = serve(&main).await;
let r = estate_wire::pull_remote(&at, None, &twin).await.unwrap();
assert_eq!((r.fetched, r.failed.len()), (4, 0), "{r:?}");
assert_eq!(
std::fs::read(twin.join("holger-audit/seg-000001.arrow"))
.unwrap()
.len(),
9_000_000
);
assert!(!twin.join("secrets-not-in-the-estate").exists());
let again = estate_wire::pull_remote(&at, None, &twin).await.unwrap();
assert_eq!(
(again.fetched, again.already),
(0, 4),
"a second follow moves nothing"
);
// Nothing outside the estate is served, by name.
let mut c = EstateServiceClient::connect(at.clone()).await.unwrap();
let refused = c
.fetch(FetchRequest {
path: "secrets-not-in-the-estate/key".into(),
offset: 0,
max_bytes: 0,
})
.await
.unwrap_err();
assert_eq!(refused.code(), tonic::Code::InvalidArgument, "{refused}");
assert!(c
.fetch(FetchRequest {
path: "../../etc/passwd".into(),
offset: 0,
max_bytes: 0
})
.await
.is_err());
// RECHARGE: the main dies (wiped); the new main pulls the twin's estate.
std::fs::remove_dir_all(&main).unwrap();
let new_main = dir("new-main");
let from_twin = serve(&twin).await;
let back = estate_wire::pull_remote(&from_twin, None, &new_main)
.await
.unwrap();
assert_eq!((back.fetched, back.failed.len()), (4, 0), "{back:?}");
assert_eq!(
std::fs::read(new_main.join("holger-entitlements/crates.json")).unwrap(),
b"{\"state\":\"Paid\"}"
);
assert_eq!(
std::fs::read(new_main.join("holger.ron")).unwrap(),
b"(repositories: [])"
);
}