Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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: [])"
    );
}