holger-estate-front/tests/holgers_grow_doors_need_checked_tickets.rs
//! ★ **Holger's grow doors need a checked, unspent ticket** (owner decision 2026-10-26, item 1;
//! monetize A32-9: every site's grow doors do, there is no knob).
//!
//! The account's grow doors (the window, the grow, the status poll and the cancel) ask the ONE
//! box first whether it verifies Holger's tenant tickets (`Products.List` `tenant_renewal`), as
//! Prolong always did. A shop that does not check them would take an order whose tenant nobody
//! checked (a proto3 shop drops the ticket): there the doors answer 501 with the buyer's words
//! and no order reaches the shop. Where the shop checks them, a fresh ticket is honoured and is
//! then spent: the same ticket presented again buys nothing.
//!
//! The front's REAL provider (`buy::HolgerSite`, mounted by `buy::relay_router`) over the shared
//! relay and the shared fake ONE box (monetize-site-buy `testkit`). The primary's buy doors are
//! stood in on loopback, minting a fresh nonce per ticket, as holger main does. Addresses are
//! `*.mock.invalid`.
#![cfg(all(feature = "console", feature = "metered"))]
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use axum::http::{HeaderMap, StatusCode};
use monetize_site_buy::relay::RENEWAL_NOT_VERIFIED;
use monetize_site_buy::testkit::{FakeOneBox, BUNDLE};
use serde_json::{json, Value};
use tower::ServiceExt as _;
const BEARER: &str = "holger-site-bearer-for-the-ticket-gate";
const PERSON: &str = "Bearer person-token-of-x14";
const CONSOLE: &str = "console-token-for-the-ticket-gate";
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
/// The primary's buy doors: `x14` signed in with [`PERSON`]; each ticket carries a fresh nonce,
/// unless `replay` is set, when it hands out the last ORDER ticket again (a ticket presented twice).
async fn primary(fake: FakeOneBox, replay: Arc<AtomicBool>) -> 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 nonce = Arc::new(AtomicU64::new(0));
let last_order = Arc::new(Mutex::new(None::<String>));
let ticket = move |h: HeaderMap, axum::Json(b): axum::Json<Value>| {
let (nonce, replay, last_order) = (nonce.clone(), replay.clone(), last_order.clone());
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"})),
);
}
let order = b["purpose"] == "order";
if order && replay.load(Ordering::SeqCst) {
if let Some(t) = last_order.lock().unwrap().clone() {
return (StatusCode::OK, axum::Json(json!({"ticket": t})));
}
}
let n = nonce.fetch_add(1, Ordering::SeqCst) + 1;
let t = hex(json!({"subject": "x14", "purpose": b["purpose"], "caps": b["caps"], "nonce": n, "by": "holger-fleet-key"}).to_string().as_bytes());
if order {
*last_order.lock().unwrap() = Some(t.clone());
}
(StatusCode::OK, axum::Json(json!({"ticket": t})))
}
};
let app = axum::Router::new()
.route("/-/buy/me", get(me))
.route("/-/buy/ticket", post(ticket));
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()
}
struct Front {
app: axum::Router,
fake: FakeOneBox,
replay: Arc<AtomicBool>,
}
async fn front() -> Front {
let fake = FakeOneBox::spawn("holger", BEARER).await;
fake.add_account("x14", "x14@mock.invalid", &[("disk_bytes", 5 << 30)]);
let replay = Arc::new(AtomicBool::new(false));
let door = primary(fake.clone(), replay.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");
let app = holger_estate_front::buy::relay_router(&shop, &door, Some(CONSOLE.into()))
.expect("the relay");
Front { app, fake, replay }
}
impl Front {
async fn ask(&self, method: &str, uri: &str, body: Option<Value>) -> (StatusCode, Value) {
let req = axum::http::Request::builder()
.method(method)
.uri(uri)
.header("x-forwarded-for", "203.0.113.9")
.header("authorization", PERSON);
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 = self.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),
)
}
}
/// ★ RED before the owner's decision: on a ONE box that does not check Holger's tickets, every
/// grow door ran (the window answered 200 and the grow minted a checkout). Now each answers 501
/// with the buyer's words, and no `Purchase` call reaches the shop.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_holger_grow_door_refuses_where_the_one_box_checks_no_ticket() {
let f = front().await;
f.fake.checks_no_tickets();
for (method, uri, body) in [
("GET", "/api/account/subscription/bundles", None),
(
"POST",
"/api/account/subscription/buy-bundle",
Some(json!({"bundle": BUNDLE})),
),
(
"GET",
"/api/account/subscription/status?reference=p-1",
None,
),
(
"POST",
"/api/account/subscription/cancel",
Some(json!({"reference": "p-1"})),
),
] {
let (st, body) = f.ask(method, uri, body).await;
assert_eq!(st, StatusCode::NOT_IMPLEMENTED, "{method} {uri}: {body}");
assert_eq!(
body["error"], RENEWAL_NOT_VERIFIED,
"{method} {uri}: the buyer's words: {body}"
);
}
let b = f.fake.book();
assert!(
b.calls.iter().all(|c| !c.starts_with("Purchase.")),
"no order reached a shop that checks no ticket: {:?}",
b.calls
);
assert!(b.checkouts.is_empty(), "no Stripe page");
}
/// ★ On a ONE box that checks them, a fresh ticket grows the account; the same ticket presented
/// again is spent, and buys nothing more.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_checked_unspent_ticket_grows_holgers_account_and_is_then_spent() {
let f = front().await;
f.fake.verify_tickets();
let (st, window) = f
.ask("GET", "/api/account/subscription/bundles", None)
.await;
assert_eq!(st, StatusCode::OK, "{window}");
let (st, buy) = f
.ask(
"POST",
"/api/account/subscription/buy-bundle",
Some(json!({"bundle": BUNDLE})),
)
.await;
assert_eq!(st, StatusCode::OK, "a fresh ticket is honoured: {buy}");
assert_eq!(
f.fake.book().checkouts_of("bundle").len(),
1,
"one Stripe page"
);
assert!(f.fake.pay(buy["url"].as_str().unwrap()));
let (_, done) = f
.ask(
"GET",
&format!(
"/api/account/subscription/status?reference={}",
buy["reference"].as_str().unwrap()
),
None,
)
.await;
assert_eq!(
(done["state"].as_str(), done["phase"].as_str()),
(Some("paid"), Some("done")),
"{done}"
);
f.replay.store(true, Ordering::SeqCst);
let (st, again) = f
.ask(
"POST",
"/api/account/subscription/buy-bundle",
Some(json!({"bundle": BUNDLE})),
)
.await;
assert!(
!st.is_success(),
"a spent ticket buys nothing: {st} {again}"
);
assert_eq!(
f.fake.book().checkouts_of("bundle").len(),
1,
"still one Stripe page"
);
let spent = f
.fake
.book()
.tickets
.iter()
.filter(|(c, _)| c == "Purchase.Start")
.count();
assert_eq!(
spent, 2,
"the shop saw both: it honoured the first and refused the second"
);
}