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

nordisk/gunnar

crates/gunnar-grpc/tests/topology.rs

//! **`Admin.Topology` over the service seam** (2026-10-04): a solo gunnar is
//! one box; the console that asks is the second; a twin that polls is the
//! third — counted by the SERVER, so the console only draws what it is told.
//!
//! Red-first: with the handler's caller-log mark removed, the second row has
//! no console; with `twin_rows()` left out of the input, the third has no twin.

use std::net::SocketAddr;
use std::sync::Arc;

use gunnar_grpc::{
    AdminService, Caller, ControlPlane, EventBus, MemoryControlPlane, ServerConfig, ServiceContext,
};
use gunnar_proto::admin_server::Admin as _;
use gunnar_proto::*;
use tonic::transport::server::TcpConnectInfo;
use tonic::{Code, Request};

struct Rig {
    plane: Arc<MemoryControlPlane>,
    ctx: ServiceContext,
}

impl Rig {
    fn new() -> Rig {
        let plane = Arc::new(MemoryControlPlane::new());
        let control: Arc<dyn ControlPlane> = plane.clone();
        let ctx = ServiceContext::new(
            control,
            Arc::new(EventBus::new(64)),
            ServerConfig::default(),
        );
        Rig { plane, ctx }
    }

    fn principal(&self, name: &str, kind: PrincipalKind, level: Option<GrantLevel>) -> Caller {
        let p = self
            .plane
            .add_principal(name, name, kind)
            .expect("principal added");
        if let Some(level) = level {
            self.plane.grant_server(&p.id, level);
        }
        Caller {
            principal_id: p.id,
            fingerprint: format!("SHA256:{name}"),
            machine: kind == PrincipalKind::Machine,
            authenticated: true,
        }
    }

    /// A request as the gate hands it on: the verified caller, and the peer
    /// the connection came from.
    fn from<T>(&self, caller: &Caller, peer: &str, message: T) -> Request<T> {
        let mut request = Request::new(message);
        request.extensions_mut().insert(caller.clone());
        let remote: SocketAddr = peer.parse().expect("peer");
        request.extensions_mut().insert(TcpConnectInfo {
            local_addr: None,
            remote_addr: Some(remote),
        });
        request
    }

    fn admin(&self) -> AdminService {
        AdminService::new(self.ctx.clone())
    }
}

fn roles(r: &TopologyResponse) -> Vec<(String, String)> {
    r.boxes
        .iter()
        .map(|b| (b.role.clone(), b.state.clone()))
        .collect()
}

#[tokio::test]
async fn solo_then_a_console_then_a_twin_one_box_each() {
    let rig = Rig::new();
    let admin = rig.admin();

    // The operator's laptop asks: a PERSON is not a box. One box.
    let operator = rig.principal("operator", PrincipalKind::User, Some(GrantLevel::Admin));
    let r = admin
        .topology(rig.from(&operator, "192.0.2.10:55000", TopologyRequest {}))
        .await
        .expect("topology")
        .into_inner();
    assert_eq!(
        roles(&r),
        [("primary".into(), "up".into())],
        "a solo gunnar is one box"
    );
    assert!(r.boxes[0].this_box);

    // The console asks — it is a machine, and the picture it gets has it on it.
    let console = rig.principal("gunnar-ui", PrincipalKind::Machine, Some(GrantLevel::Admin));
    let r = admin
        .topology(rig.from(&console, "127.0.0.1:41234", TopologyRequest {}))
        .await
        .expect("topology")
        .into_inner();
    assert_eq!(
        roles(&r),
        [
            ("primary".into(), "up".into()),
            ("console".into(), "up".into())
        ],
        "front + server = 2"
    );
    assert_eq!(r.boxes[1].name, "gunnar-ui");
    assert_eq!(r.boxes[1].address, "127.0.0.1:41234");

    // A twin polls (the primary half of the heartbeat records it).
    let report = TwinReport {
        applied_seq: 7,
        disk_total_bytes: 10 << 30,
        disk_used_bytes: 1 << 30,
        state: "following".into(),
        ..TwinReport::default()
    };
    rig.ctx.heartbeats.record(
        "gunnar-twin",
        "10.13.8.122:5222",
        &report,
        7,
        estate_clock::now_unix_ms(),
    );
    let r = admin
        .topology(rig.from(&console, "127.0.0.1:41234", TopologyRequest {}))
        .await
        .expect("topology")
        .into_inner();
    assert_eq!(
        roles(&r),
        [
            ("primary".into(), "up".into()),
            ("twin".into(), "up".into()),
            ("console".into(), "up".into())
        ],
        "+ twin = 3"
    );
    let twin = &r.boxes[1];
    assert!(
        twin.disk_measured && twin.disk_total_bytes == 10 << 30,
        "{twin:?}"
    );
    assert!(twin.sources.iter().any(|s| s == "heartbeat"));
}

/// Any machine's ordinary call is recorded (it is the evidence a front key is
/// up), but only a machine that ASKED for the picture is drawn as a console.
#[tokio::test]
async fn an_ordinary_machine_call_is_recorded_but_is_not_a_console() {
    let rig = Rig::new();
    let builder = rig.principal(
        "dwarves-oden",
        PrincipalKind::Machine,
        Some(GrantLevel::Read),
    );
    rig.admin()
        .stats(rig.from(&builder, "10.0.0.9:5000", StatsRequest {}))
        .await
        .expect("stats");
    let seen = rig.ctx.callers.sightings();
    assert_eq!(seen.len(), 1);
    assert_eq!(
        (seen[0].address.as_str(), seen[0].asked_topology),
        ("10.0.0.9:5000", false)
    );
    let operator = rig.principal("operator", PrincipalKind::User, Some(GrantLevel::Admin));
    let r = rig
        .admin()
        .topology(rig.from(&operator, "192.0.2.10:1", TopologyRequest {}))
        .await
        .unwrap()
        .into_inner();
    assert_eq!(
        r.boxes.len(),
        1,
        "a builder's key is not a box of the estate: {:?}",
        r.boxes
    );
}

/// A credential with no grant is refused: the topology is a server-wide read.
#[tokio::test]
async fn a_caller_with_no_server_read_is_refused() {
    let rig = Rig::new();
    let nobody = rig.principal("nobody", PrincipalKind::Machine, None);
    let err = rig
        .admin()
        .topology(rig.from(&nobody, "10.0.0.1:1", TopologyRequest {}))
        .await
        .expect_err("refused");
    assert_eq!(err.code(), Code::PermissionDenied);
}