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())
}