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

nordisk/gunnar

crates/gunnar-server/tests/lean_egress.rs

//! **The lean estate: a box behind its front leaves through the door it is fed** (owner
//! 2026-10-23, A19 rows 13-14). Main and the twin are laid with a utility address alone; the
//! shop feeds them the front's egress door (`egress`, Skidbladnir nordisk-pairing), and every
//! client of theirs that reaches the internet takes it (`nordisk_pairing::egress::proxy()`):
//! the identity provider's calls, the webhooks, the mirrors (`mirror_http`'s own test) and the
//! sign-in-again mail.
//!
//! One door for the whole binary (the fed door is process-wide, the first wins), on loopback,
//! recording each destination it was asked for and carrying it.

use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::sync::{Mutex, OnceLock};

static SEEN: Mutex<Vec<(String, u16)>> = Mutex::new(Vec::new());

/// What the door was asked for (a failed test's panic must not poison it for the others).
fn seen() -> std::sync::MutexGuard<'static, Vec<(String, u16)>> {
    SEEN.lock().unwrap_or_else(|e| e.into_inner())
}

fn record(host: &str, port: u16, resolved: &[SocketAddr]) -> Result<(), String> {
    seen().push((host.to_string(), port));
    if resolved.is_empty() {
        Err("unresolved".into())
    } else {
        Ok(())
    }
}

/// The door, served once and fed to this process as the shop feeds a lean box.
fn door() -> &'static str {
    static DOOR: OnceLock<String> = OnceLock::new();
    DOOR.get_or_init(|| {
        let at = nordisk_pairing::egress::serve(
            "127.0.0.1:0".parse().unwrap(),
            nordisk_pairing::egress::Policy {
                peer: |_| true,
                admits: record,
            },
        )
        .unwrap();
        let url = format!("http://{at}");
        let feed: BTreeMap<String, String> =
            [(gunnar_server::feed::FEED_EGRESS.to_string(), url.clone())].into();
        gunnar_server::feed::take_egress(&feed);
        url
    })
}

#[cfg(any(feature = "sso-egress", feature = "webhooks", feature = "fleet-pull"))]
fn went_through(port: u16) -> bool {
    seen().iter().any(|(h, p)| h == "127.0.0.1" && *p == port)
}

/// A one-request-at-a-time HTTP server on loopback answering `200` with `body`.
#[cfg(any(feature = "sso-egress", feature = "webhooks"))]
fn http_200(body: &'static str) -> u16 {
    use std::io::{Read as _, Write as _};
    use std::net::TcpListener;
    let l = TcpListener::bind("127.0.0.1:0").unwrap();
    let port = l.local_addr().unwrap().port();
    std::thread::spawn(move || {
        for mut c in l.incoming().flatten() {
            let mut head = Vec::new();
            let mut b = [0u8; 1];
            while !head.ends_with(b"\r\n\r\n") && c.read(&mut b).map(|n| n == 1).unwrap_or(false) {
                head.push(b[0]);
            }
            let _ = write!(c, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len());
        }
    });
    port
}

/// ★ The door the box was fed is the one its clients take.
#[test]
fn the_fed_door_is_the_one_this_box_leaves_through() {
    let d = door();
    assert_eq!(nordisk_pairing::egress::proxy(), Some(d));
}

/// ★ **The identity provider's call leaves through the door** (A19 row 14): the SSO egress
/// resolves nothing itself then (the door names the host), and its answer comes back as ever.
#[cfg(feature = "sso-egress")]
#[test]
fn an_identity_providers_call_leaves_through_the_door() {
    door();
    let idp = http_200("{\"issuer\":\"x\"}");
    let egress = gunnar_server::sso_egress::UreqEgress::new(true);
    let (status, body) = gunnar_grpc::sso::Egress::get(
        &egress,
        &format!("http://127.0.0.1:{idp}/.well-known/openid-configuration"),
    )
    .expect("the IdP through the door");
    assert_eq!((status, body.as_str()), (200, "{\"issuer\":\"x\"}"));
    assert!(went_through(idp), "the door carried it: {:?}", seen());
}

/// ★ **A webhook delivery leaves through the door** (A19 row 14).
#[cfg(feature = "webhooks")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_webhook_delivery_leaves_through_the_door() {
    use std::sync::Arc;
    door();
    let sink = http_200("");
    let dir = tempfile::tempdir().unwrap();
    let stores = Arc::new(gunnar_server::stores::ServerStores::open(dir.path()).expect("stores"));
    let bus = Arc::new(gunnar_grpc::EventBus::new(64));
    let slot = gunnar_server::webhook::WebhookSlot::new(
        Arc::clone(&stores),
        Arc::clone(&bus),
        tokio::runtime::Handle::current(),
    );
    slot.add(gunnar_server::webhook::Endpoint {
        name: "ci".into(),
        url: format!("http://127.0.0.1:{sink}/hook"),
        kinds: Vec::new(),
        namespace: String::new(),
        repo: None,
        secret: None,
        added_at_unix_ms: 0,
    })
    .expect("add endpoint");
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
    bus.publish(gunnar_proto::event::Payload::RepoCreated(
        gunnar_proto::RepoCreated {
            repo: Some(gunnar_proto::Repo {
                path: Some(gunnar_proto::RepoPath {
                    namespace: "alice".into(),
                    repo: "edda".into(),
                }),
                ..Default::default()
            }),
            principal_id: "p-1".into(),
        },
    ));
    for _ in 0..100 {
        if went_through(sink) {
            return;
        }
        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
    }
    panic!("the delivery never reached the door: {:?}", seen());
}

/// A mail relay on loopback that greets, answers `EHLO` offering no STARTTLS, and says whether
/// a client reached it. Only the sign-in-again mail test (`fleet-pull`) speaks to it.
#[cfg(feature = "fleet-pull")]
fn relay_offering_no_starttls() -> (u16, std::sync::Arc<std::sync::atomic::AtomicBool>) {
    use std::io::{Read as _, Write as _};
    use std::net::TcpListener;
    let l = TcpListener::bind("127.0.0.1:0").unwrap();
    let port = l.local_addr().unwrap().port();
    let reached = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
    let r = reached.clone();
    std::thread::spawn(move || {
        for mut c in l.incoming().flatten() {
            let _ = c.write_all(b"220 relay.mock.invalid ESMTP\r\n");
            let mut line = Vec::new();
            let mut b = [0u8; 1];
            while !line.ends_with(b"\r\n") && c.read(&mut b).map(|n| n == 1).unwrap_or(false) {
                line.push(b[0]);
            }
            if line.starts_with(b"EHLO") {
                r.store(true, std::sync::atomic::Ordering::SeqCst);
            }
            let _ = c.write_all(b"250 relay.mock.invalid\r\n");
            let _ = c.read(&mut [0u8; 64]);
        }
    });
    (port, reached)
}

/// ★ **The sign-in-again mail of a box behind its front leaves through the door** (A19 row 13,
/// the SMTP gap): lettre speaks no proxy, so the postman dials a loopback forward
/// (`nordisk_pairing::egress::Forward`) carried through the front's door as `CONNECT
/// relay:port`, and names the RELAY in its TLS. The relay here offers no STARTTLS, so the mail
/// is refused, never sent in the clear: what is proven is the road, and that TLS is still
/// required on it.
#[cfg(feature = "fleet-pull")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_sign_in_again_mail_leaves_through_the_door() {
    use gunnar_server::postman::Postman as _;
    door();
    let (relay, reached) = relay_offering_no_starttls();
    let smtp = gunnar_server::postman::Smtp::from_url(
        &format!("smtp://u:tok@127.0.0.1:{relay}"),
        "gunnar@mock.invalid",
    )
    .unwrap();
    let mail = gunnar_repair::sign_in::SignInMail {
        to: "buyer@mock.invalid".into(),
        subject: "sign in".into(),
        body: "a link".into(),
    };
    let sent = smtp.post(&mail).await;
    assert!(
        sent.is_err(),
        "no STARTTLS on offer: refused, never in the clear"
    );
    assert!(
        went_through(relay),
        "the door carried the relay's connection: {:?}",
        seen()
    );
    assert!(
        reached.load(std::sync::atomic::Ordering::SeqCst),
        "the relay was reached and greeted"
    );
}