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

nordisk/holger

holger-estate-front/tests/holgers_site_runs_the_shared_buy_flow.rs

//! ★ **Holger's site runs the shared buy flow** (owner 2026-10-23: "make integration tests for
//! shared lib"): the front's REAL provider (`buy::HolgerSite`, mounted by `buy::relay_router`
//! from the front's feed) over the shared relay, against the shared fake ONE box and fake
//! Stripe (monetize-site-buy `testkit`). The primary's buy doors (`/-/buy/me`, `/-/buy/ticket`,
//! `/-/buy/account`) are stood in on loopback with the shapes holger main answers
//! (server/lib `room::buy_me`, `buy_ticket`, `signup_account`).
//!
//! One visitor flow (a paid plan, welcomed on the site) and one signed-in flow (grow the
//! account, then a private instance with no e-mail). Addresses are `*.mock.invalid`.

#![cfg(all(feature = "console", feature = "metered"))]

use std::collections::BTreeMap;

use axum::http::{HeaderMap, StatusCode};
use monetize_site_buy::testkit::{self, FakeOneBox, What, BUNDLE, BUNDLE_BYTES, PAID_ROW};
use serde_json::{json, Value};
use tower::ServiceExt as _;

const BEARER: &str = "holger-site-bearer-for-tests";
const PERSON: &str = "Bearer person-token-of-x14";
const CONSOLE: &str = "console-token-for-tests";
const ORIGIN: &str = "https://holger.mock.invalid";

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

/// The primary's buy doors, as holger main answers them: `x14` signed in with [`PERSON`],
/// its caps the fake ONE box's; its tickets its own JSON (the fleet key's signature stood in);
/// a paid sign-up's account held once the ONE box made it.
async fn primary(fake: FakeOneBox) -> String {
    use axum::routing::{get, post};
    let me = {
        let fake = fake.clone();
        move |h: HeaderMap| {
            let fake = fake.clone();
            async move {
                if h.get("authorization").and_then(|v| v.to_str().ok()) != Some(PERSON) {
                    return axum::Json(json!({"who": "visitor"}));
                }
                let caps = fake
                    .book()
                    .accounts
                    .get("x14")
                    .map(|a| a.caps.clone())
                    .unwrap_or_default();
                axum::Json(
                    json!({"who": "person", "account": "x14", "tenant": "x14", "bound": true, "caps": caps}),
                )
            }
        }
    };
    let account = {
        let fake = fake.clone();
        move |h: HeaderMap,
              axum::extract::Query(q): axum::extract::Query<BTreeMap<String, String>>| {
            let fake = fake.clone();
            async move {
                if h.get("authorization").and_then(|v| v.to_str().ok())
                    != Some(&format!("Bearer {CONSOLE}"))
                {
                    return (
                        StatusCode::UNAUTHORIZED,
                        axum::Json(json!({"error": "the console asks this"})),
                    );
                }
                let held = fake
                    .book()
                    .accounts
                    .contains_key(q.get("user").map(String::as_str).unwrap_or(""));
                (
                    StatusCode::OK,
                    axum::Json(json!({"account": if held { "no_passkey" } else { "none" }})),
                )
            }
        }
    };
    let app = axum::Router::new()
        .route("/-/buy/me", get(me))
        .route("/-/buy/account", get(account))
        .route(
            "/-/buy/ticket",
            post(|h: HeaderMap, axum::Json(b): axum::Json<Value>| async move {
                if h.get("authorization").and_then(|v| v.to_str().ok()) != Some(PERSON) {
                    return (StatusCode::UNAUTHORIZED, axum::Json(json!({"error": "sign in to buy"})));
                }
                // A fresh nonce per ticket, as holger main's: the ONE box spends each once.
                static NONCE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
                let nonce = NONCE.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
                let t = json!({"subject": "x14", "purpose": b["purpose"], "caps": b["caps"], "nonce": nonce, "by": "holger-fleet-key"});
                (StatusCode::OK, axum::Json(json!({"ticket": hex(t.to_string().as_bytes())})))
            }),
        );
    let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let at = l.local_addr().unwrap();
    tokio::spawn(async move { axum::serve(l, app).await });
    at.to_string()
}

/// The front's relay, from its feed as monetize-shop-pairing feeds an operator site.
async fn front() -> (axum::Router, FakeOneBox) {
    let fake = FakeOneBox::spawn("holger", BEARER).await;
    // A ONE box that checks Holger's tickets: its grow doors need them (owner 2026-10-26).
    fake.verify_tickets();
    let door = primary(fake.clone()).await;
    let feed: BTreeMap<String, String> = [
        (
            holger_estate_front::buy::FEED_MONETIZE_BEARER,
            BEARER.to_string(),
        ),
        (
            holger_estate_front::buy::FEED_CONSOLE_PRODUCT,
            "holger".into(),
        ),
        (holger_estate_front::buy::FEED_FLEET, fake.url()),
        (
            holger_estate_front::buy::FEED_PASSKEY_RP,
            "holger.mock.invalid".into(),
        ),
    ]
    .into_iter()
    .map(|(k, v)| (k.to_string(), v))
    .collect();
    let shop = holger_estate_front::buy::shop_feed_of(&feed)
        .expect("the feed sells here")
        .expect("a usable feed");
    (
        holger_estate_front::buy::relay_router(&shop, &door, Some(CONSOLE.into()))
            .expect("the relay"),
        fake,
    )
}

async fn ask(
    app: &axum::Router,
    method: &str,
    uri: &str,
    auth: Option<&str>,
    body: Option<Value>,
) -> (StatusCode, Value) {
    let mut req = axum::http::Request::builder()
        .method(method)
        .uri(uri)
        .header("x-forwarded-for", "203.0.113.9");
    if let Some(a) = auth {
        req = req.header("authorization", a);
    }
    let req = match body {
        Some(b) => req
            .header("content-type", "application/json")
            .body(axum::body::Body::from(b.to_string()))
            .unwrap(),
        None => req.body(axum::body::Body::empty()).unwrap(),
    };
    let res = app.clone().oneshot(req).await.unwrap();
    let status = res.status();
    let bytes = axum::body::to_bytes(res.into_body(), 1 << 20)
        .await
        .unwrap();
    (
        status,
        serde_json::from_slice(&bytes).unwrap_or(Value::Null),
    )
}

#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_visitor_buys_a_plan_on_holgers_site_and_is_welcomed_there() {
    let (app, fake) = front().await;
    let (st, plans) = ask(&app, "GET", "/auth/signup/ladder", None, None).await;
    assert_eq!(st, StatusCode::OK, "{plans}");
    let plan = format!("holger/{PAID_ROW}");
    assert_eq!(plans["plans"][1]["id"], plan);
    let (st, started) = ask(&app, "POST", "/auth/signup/start", None, Some(json!({"username": "newbie", "email": "newbie@mock.invalid", "plan": plan, "next": "/"}))).await;
    assert_eq!(st, StatusCode::OK, "{started}");
    let url = started["url"].as_str().unwrap().to_owned();
    let session = url.rsplit('/').next().unwrap().to_owned();
    {
        let b = fake.book();
        assert_eq!(b.checkouts.len(), 1, "one Stripe page");
        assert_eq!(
            b.checkouts[0].success_url,
            format!("{ORIGIN}/welcome?session={{session}}&next=%2F"),
            "the way back is Holger's own welcome"
        );
        assert!(
            b.bearers.iter().all(|x| x == &format!("Bearer {BEARER}")),
            "every call carries the site's own bearer"
        );
    }
    let (_, pending) = ask(
        &app,
        "GET",
        &format!("/auth/signup/status?session={session}"),
        None,
        None,
    )
    .await;
    assert_eq!(pending["state"], "pending");
    assert!(fake.pay(&url));
    let (st, made) = ask(
        &app,
        "GET",
        &format!("/auth/signup/status?session={session}"),
        None,
        None,
    )
    .await;
    assert_eq!(st, StatusCode::OK, "{made}");
    assert_eq!(
        made["invite_url"],
        format!("/login?signup={session}&user=newbie"),
        "the primary holds the account, no passkey yet: {made}"
    );
    assert_eq!(
        fake.book().mails.len(),
        1,
        "one sign-in mail, at account creation"
    );
}

#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_signed_in_person_grows_the_account_and_buys_an_instance_on_holgers_site() {
    let (app, fake) = front().await;
    fake.add_account("x14", "x14@mock.invalid", &[("disk_bytes", 5 << 30)]);
    let (st, window) = ask(
        &app,
        "GET",
        "/api/account/subscription/bundles",
        Some(PERSON),
        None,
    )
    .await;
    assert_eq!(st, StatusCode::OK, "{window}");
    let (st, buy) = ask(
        &app,
        "POST",
        "/api/account/subscription/buy-bundle",
        Some(PERSON),
        Some(json!({"bundle": BUNDLE})),
    )
    .await;
    assert_eq!(st, StatusCode::OK, "{buy}");
    assert_eq!(
        buy["before"]["disk_bytes"].as_u64(),
        Some(5 << 30),
        "the held caps are the primary's word"
    );
    assert_eq!(
        buy["after"]["disk_bytes"].as_u64(),
        Some((5 << 30) + BUNDLE_BYTES)
    );
    let order = fake
        .book()
        .tickets
        .iter()
        .find(|(c, _)| c == "Purchase.Start")
        .unwrap()
        .1
        .clone();
    let order: Value = serde_json::from_slice(&order).unwrap();
    assert_eq!(
        (
            order["purpose"].as_str(),
            order["caps"]["disk_bytes"].as_u64()
        ),
        (Some("order"), Some((5 << 30) + BUNDLE_BYTES)),
        "the primary's order ticket covers the exact target"
    );
    assert!(fake.pay(buy["url"].as_str().unwrap()));
    let (_, done) = ask(
        &app,
        "GET",
        &format!(
            "/api/account/subscription/status?reference={}",
            buy["reference"].as_str().unwrap()
        ),
        Some(PERSON),
        None,
    )
    .await;
    assert_eq!(
        (done["state"].as_str(), done["phase"].as_str()),
        (Some("paid"), Some("done")),
        "{done}"
    );

    let (st, private) = ask(&app, "POST", "/auth/private/start", Some(PERSON), Some(json!({"company": "acme", "email": "other@mock.invalid", "front_plan": "f-mini", "main_plan": "m-mini", "main_data_gib": 40}))).await;
    assert_eq!(st, StatusCode::OK, "{private}");
    {
        let b = fake.book();
        let c = b.checkouts_of("estate")[0].clone();
        let What::Estate {
            account,
            email,
            new_account,
            ..
        } = &c.what
        else {
            panic!("an estate order")
        };
        assert_eq!(
            (account.as_str(), email.as_str(), *new_account),
            ("x14", "", false),
            "signed in: the account's own order, no e-mail"
        );
        let estate = &b
            .tickets
            .iter()
            .find(|(x, _)| x == "EstateOrder.Start")
            .unwrap()
            .1;
        assert_eq!(
            serde_json::from_slice::<Value>(estate).unwrap()["purpose"],
            "estate"
        );
        assert_eq!(b.checkouts.len(), 2, "two buys, two Stripe pages");
    }
    let (st, _) = ask(&app, "GET", "/api/account/subscription/bundles", None, None).await;
    assert_eq!(st, StatusCode::UNAUTHORIZED, "a visitor grows nothing");
    let _ = testkit::STRIPE;
}