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

nordisk/monetize

crates/monetize-admin/tests/common/mod.rs

//! **What the ONE box's program tests share** (feature `trials`): the inputs `cargo xtask
//! trials` builds (the guests, the fake Stripe's binary), the fake Stripe as a process, a guest
//! signed under the run's plugin root, and the one stand-in (the installer). Used by
//! `site_buy_trials.rs` and `money_flow.rs`.

#![allow(dead_code)]

use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, Instant};

use base64::Engine as _;
use monetize_server::estate_order::{
    self, InstallRequest, InstallVerdict, Installer, VERDICT_FILE,
};
use monetize_server::registry::InstallerBuild;
use serde_json::{json, Value};

/// The stand-in installer's name (`[estate_orders] pairing`).
pub const STAND_IN: &str = "trial-stand-in";

// ── the inputs ───────────────────────────────────────────────────────────────────────────

pub struct Inputs {
    pub guests: PathBuf,
    pub fake_stripe: PathBuf,
}

/// The guests and the fake's binary, or a FAILURE naming what is missing and how to make it.
pub fn inputs() -> Inputs {
    let how = "run `cargo xtask trials` in monetize: it builds the guests and fake_stripe_serve and sets both";
    let guests = std::env::var_os("MONETIZE_TRIAL_GUESTS").map(PathBuf::from).unwrap_or_else(|| panic!("MONETIZE_TRIAL_GUESTS is not set (a directory with stripe.wasm, upcloud.wasm, cloudflare.wasm, gunnar.wasm, holger.wasm): {how}"));
    for m in ["stripe", "upcloud", "cloudflare", "gunnar", "holger"] {
        let p = guests.join(format!("{m}.wasm"));
        assert!(
            p.is_file(),
            "MONETIZE_TRIAL_GUESTS={}: {} is missing: {how}",
            guests.display(),
            p.display()
        );
    }
    let fake_stripe = std::env::var_os("MONETIZE_FAKE_STRIPE").map(PathBuf::from).unwrap_or_else(|| panic!("MONETIZE_FAKE_STRIPE is not set (the fake_stripe_serve binary of monetize-impl): {how}"));
    assert!(
        fake_stripe.is_file(),
        "MONETIZE_FAKE_STRIPE={} is not a file: {how}",
        fake_stripe.display()
    );
    Inputs {
        guests,
        fake_stripe,
    }
}

pub fn now_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_millis() as u64
}

pub fn free_port() -> u16 {
    std::net::TcpListener::bind("127.0.0.1:0")
        .unwrap()
        .local_addr()
        .unwrap()
        .port()
}

pub fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

pub fn b64(b: &[u8]) -> String {
    base64::engine::general_purpose::STANDARD.encode(b)
}

/// **The fake Stripe, as a process** (`fake_stripe_serve`): the API the Stripe guest dials and
/// the control door the buyer uses. Only this child is ever stopped.
pub struct FakeStripe {
    child: std::process::Child,
    pub api: u16,
    pub control: u16,
}

impl FakeStripe {
    pub fn start(bin: &Path, sk: &str, whsec: &str) -> FakeStripe {
        Self::start_on(bin, sk, whsec, &mut free_port)
    }

    /// [`Self::start`] on the ports `pick` names (a test's own).
    ///
    /// MHUNT-2 (OX-MONHUNT 2026-10-28): a port [`free_port`] names is free when it is asked and
    /// may be taken before the child binds it (another test's fake, another lane's process on the
    /// same host): the child then exits with "Address already in use", and a bare connect test
    /// may even succeed against the OTHER process. The fake is up only when both its ports answer
    /// HTTP while the child still runs; a child that exits first is started again on new ports.
    pub fn start_on(
        bin: &Path,
        sk: &str,
        whsec: &str,
        pick: &mut dyn FnMut() -> u16,
    ) -> FakeStripe {
        const TRIES: usize = 5;
        let mut why = String::new();
        for _ in 0..TRIES {
            let (api, control) = (pick(), pick());
            let mut child = std::process::Command::new(bin)
                .args([
                    "--port",
                    &api.to_string(),
                    "--control",
                    &control.to_string(),
                    "--key",
                    sk,
                    "--webhook",
                    whsec,
                    "--ids-like-stripe",
                    "true",
                ])
                .stdout(std::process::Stdio::null())
                .stderr(std::process::Stdio::inherit())
                .spawn()
                .unwrap_or_else(|e| panic!("MONETIZE_FAKE_STRIPE={}: {e}", bin.display()));
            let until = Instant::now() + Duration::from_secs(20);
            loop {
                if let Ok(Some(st)) = child.try_wait() {
                    why = format!(
                        "the fake Stripe on {api}/{control} exited ({st}) before it listened"
                    );
                    break;
                }
                if answers_http(api)
                    && answers_http(control)
                    && matches!(child.try_wait(), Ok(None))
                {
                    return FakeStripe {
                        child,
                        api,
                        control,
                    };
                }
                if Instant::now() >= until {
                    let _ = child.kill();
                    let _ = child.wait();
                    panic!("the fake Stripe did not listen on {api}/{control} in 20 s");
                }
                std::thread::sleep(Duration::from_millis(50));
            }
        }
        panic!("{why}, {TRIES} times on new ports")
    }

    pub fn base(&self) -> String {
        format!("http://127.0.0.1:{}", self.api)
    }

    /// One control call: (status, JSON).
    pub fn control(&self, method: &str, path: &str) -> (u16, Value) {
        use std::io::{Read as _, Write as _};
        let Ok(mut s) = std::net::TcpStream::connect(("127.0.0.1", self.control)) else {
            return (0, Value::Null);
        };
        let _ = write!(s, "{method} {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
        let mut got = String::new();
        let _ = s.read_to_string(&mut got);
        let status = got
            .split_whitespace()
            .nth(1)
            .and_then(|c| c.parse().ok())
            .unwrap_or(0);
        let body = got.split("\r\n\r\n").nth(1).unwrap_or_default();
        (status, serde_json::from_str(body).unwrap_or(Value::Null))
    }

    pub fn minted(&self, cs: &str) -> bool {
        self.control("GET", &format!("/seen/{cs}")).0 == 200
    }

    /// The session as the fake serves it, with what its create form asked.
    pub fn session(&self, cs: &str) -> Value {
        self.control("GET", &format!("/session/{cs}")).1
    }
}

/// Does `port` on loopback answer an HTTP request (any status)? A port some other process holds
/// open without answering HTTP is not the fake's.
fn answers_http(port: u16) -> bool {
    use std::io::{Read as _, Write as _};
    let Ok(mut s) = std::net::TcpStream::connect_timeout(
        &std::net::SocketAddr::from(([127, 0, 0, 1], port)),
        Duration::from_millis(500),
    ) else {
        return false;
    };
    let _ = s.set_read_timeout(Some(Duration::from_millis(500)));
    if s.write_all(b"GET /healthz HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 0\r\nConnection: close\r\n\r\n").is_err() {
        return false;
    }
    let mut head = [0u8; 8];
    s.read_exact(&mut head).is_ok() && head.starts_with(b"HTTP/1.")
}

impl Drop for FakeStripe {
    fn drop(&mut self) {
        let _ = self.child.kill();
        let _ = self.child.wait();
    }
}

// ── the one stand-in: the installer ─────────────────────────────────────────────────────────

/// **The installer's stand-in**: real boxes do not boot in this test, so it writes a PASS
/// verdict for what was laid; its sale check reads nothing.
pub struct StandIn;

impl Installer for StandIn {
    fn install(&self, req: &InstallRequest) -> Result<InstallVerdict, String> {
        let v = InstallVerdict {
            verdict: "PASS".into(),
            detail: "stand-in".into(),
            receipt: json!({"verdict": "PASS"}),
            peers: Vec::new(),
            appliance_keys: Default::default(),
            ticket_key: String::new(),
        };
        std::fs::write(req.run.join(VERDICT_FILE), serde_json::to_vec(&v).unwrap())
            .map_err(|e| e.to_string())?;
        Ok(v)
    }
    fn sale_check(&self, at: &estate_order::SaleCheckAt<'_>) -> Result<String, String> {
        Ok(format!("stand-in: https://{} is not read here", at.host))
    }
}

pub fn stand_in(_: &InstallerBuild<'_>) -> Result<Arc<dyn Installer>, String> {
    Ok(Arc::new(StandIn))
}

/// A guest copied into the run and signed under the run's plugin root, as the bench does
/// (`guest_host::verify::sign`): the module and its `.p7s`.
pub fn sign_into(
    dir: &Path,
    from: &Path,
    name: &str,
    signer: &dyn guest_host::verify::sign::ArchiveSigner,
) -> (Vec<u8>, Vec<u8>) {
    let module = dir.join(format!("{name}.wasm"));
    std::fs::copy(from, &module).unwrap_or_else(|e| panic!("{}: {e}", from.display()));
    let p7s = guest_host::verify::sign::sign_module_file(&module, signer).unwrap();
    (std::fs::read(&module).unwrap(), std::fs::read(p7s).unwrap())
}