crates/monetize-site-buy/tests/buyflow4.rs
//! ★ **Lane BUYFLOW-4** (monetize v2.0, owner GO 2026-10-25): what the guide's trace of the buy
//! flows found (docs/INTEGRATION-v2.md §8.15), for both sites. Addresses are `*.mock.invalid`.
//!
//! 1. A renewal's way back from Stripe carries its company, as the first buy's does (B7a.2).
//! 2. The bundle and renew doors answer a buyer in buyer words: the customer's sentence the box
//! sends beside a refusal, never the operator's; and no buyer sentence of the relay or of the
//! buy pages names the operator's machinery (lay, estate, guest, operator, cap, …).
//! 3. A PermissionDenied from the ONE box is 403 with a plain buyer sentence, never 502.
mod sites;
use std::sync::Arc;
use axum::http::StatusCode;
use monetize_site_buy::testkit::{TestSite, BUNDLE};
use monetize_site_buy::Relay;
use serde_json::json;
use sites::{Kit, Shape};
use tonic::Code;
/// **The operator's vocabulary**: words a buyer page never says (owner: customer-never-sees-caps,
/// one-monetize-only).
const OPERATOR_WORDS: &[&str] = &[
"lay",
"lays",
"laid",
"laying",
"estate",
"estates",
"guest",
"guests",
"operator",
"operators",
"cap",
"caps",
"monetize",
"tenant",
"tenants",
"appliance",
"vendor",
"vendors",
"ignalina",
];
/// The operator words `text` says, as whole words (case-insensitive), and "ONE box".
fn operator_words(text: &str) -> Vec<String> {
let lower = text.to_lowercase();
let mut said: Vec<String> = lower
.split(|c: char| !c.is_alphanumeric())
.filter(|w| OPERATOR_WORDS.contains(w))
.map(str::to_owned)
.collect();
if lower.contains("one box") {
said.push("one box".into());
}
said
}
fn assert_buyer_words(text: &str, at: &str) {
assert!(!text.trim().is_empty(), "{at}: the buyer is told in words");
let said = operator_words(text);
assert!(said.is_empty(), "{at}: a buyer reads {said:?}: {text}");
}
for_both_sites!(
a_renewal_comes_back_with_its_company,
the_bundle_and_renew_doors_answer_in_buyer_words,
a_refused_ticket_is_403_in_plain_words
);
/// 1. `/paid/estate/<co>/renewal-<n>` lands on the order's watch with `company=`, as
/// `/paid/estate/<co>` does; a change keeps its `change=1`.
async fn a_renewal_comes_back_with_its_company(shape: Shape) {
let k = Kit::new(shape).await;
let private = shape.pages().private;
for (path, to) in [
("/paid/estate/acme", format!("{private}&company=acme")),
(
"/paid/estate/acme/renewal-1",
format!("{private}&company=acme"),
),
(
"/paid/estate/acme/renewal-12",
format!("{private}&company=acme"),
),
(
"/paid/estate/acme/change-2",
format!("{private}&company=acme&change=1"),
),
("/paid/estate/acme/renewal-x", private.clone()),
] {
let r = k.ask("GET", path, None, None).await;
assert_eq!(
(r.status, r.location.as_deref()),
(StatusCode::SEE_OTHER, Some(to.as_str())),
"{shape:?} {path}"
);
}
}
/// A kit whose site reads the held room from its own word (so a grow reaches the shop's
/// `Purchase.Bundles` even while the box refuses every call).
async fn kit_holding_room(shape: Shape) -> Kit {
let fake = monetize_site_buy::testkit::FakeOneBox::spawn(shape.product(), sites::BEARER).await;
let site = TestSite::over(shape, &fake).with_held(Arc::new(|_: &str| {
std::collections::BTreeMap::from([("disk_bytes".to_owned(), 1u64 << 30)])
}));
let relay = Arc::new(Relay::new(site.clone(), fake.link()));
Kit {
shape,
fake,
site,
relay,
emitted: Default::default(),
}
}
/// 2. A refusal on the bundle and renew doors: the customer's sentence the box sent beside the
/// operator's; without one, an operator's sentence is never passed on.
async fn the_bundle_and_renew_doors_answer_in_buyer_words(shape: Shape) {
let k = kit_holding_room(shape).await;
k.fake
.add_account("x14", "x14@mock.invalid", &[("disk_bytes", 1 << 30)]);
k.fake.add_estate("acme", "x14", "Mini");
let operators = "the product cannot deliver this to x14-repo: the cap of 2 TiB is past the operator's lay room on estate gunnar-rs";
let customers =
"This size cannot be added to your account right now. Nothing has been charged.";
let doors: Vec<(&str, &str, Option<serde_json::Value>)> = vec![
("GET", "/api/account/subscription/bundles", None),
(
"POST",
"/api/account/subscription/buy-bundle",
Some(json!({"bundle": BUNDLE})),
),
(
"POST",
"/auth/private/renew",
Some(json!({"company": "acme"})),
),
];
for (method, path, body) in &doors {
k.fake
.fail_saying(Code::FailedPrecondition, operators, customers);
let r = k.ask(method, path, Some("x14"), body.clone()).await;
assert_eq!(
(r.status, r.error()),
(StatusCode::CONFLICT, customers),
"{shape:?} {path}: the customer's sentence"
);
k.fake.fail(Some((Code::FailedPrecondition, operators)));
let r = k.ask(method, path, Some("x14"), body.clone()).await;
assert_eq!(
r.status,
StatusCode::CONFLICT,
"{shape:?} {path}: {:?}",
r.body
);
assert_buyer_words(
r.error(),
&format!("{shape:?} {path}, no customer sentence"),
);
k.fake.fail(Some((
Code::FailedPrecondition,
"there is no estate acme.estates.mock.invalid",
)));
let r = k.ask(method, path, Some("x14"), body.clone()).await;
assert_buyer_words(r.error(), &format!("{shape:?} {path}, a renewal refused"));
}
// The account's own renewal on a shop that does not verify its tickets: buyer words too.
k.fake.fail(None);
// (A32-9: the fake says it checks tickets as spawned; this one is a shop that does not.)
k.fake.checks_no_tickets();
for (method, path) in [
("GET", "/api/account/subscription"),
("POST", "/api/account/subscription/renew"),
] {
let r = k.ask(method, path, Some("x14"), None).await;
assert_eq!(
r.status,
StatusCode::NOT_IMPLEMENTED,
"{shape:?} {path}: {:?}",
r.body
);
assert_buyer_words(r.error(), &format!("{shape:?} {path}"));
}
// The watch of an order named badly, and a market the shop does not sell in.
let r = k
.ask("GET", "/auth/private/status?company=..", Some("x14"), None)
.await;
assert_eq!(r.status, StatusCode::BAD_REQUEST);
assert_buyer_words(r.error(), &format!("{shape:?} status, a bad name"));
assert_buyer_words(
&monetize_site_buy::rules::market(Some("US")).unwrap_err(),
"a market not sold in",
);
}
/// 3. The ONE box refusing the site's word for who asks (PermissionDenied): 403, and the
/// buyer's plain sentence, on the private-instance doors and the account's grow doors.
async fn a_refused_ticket_is_403_in_plain_words(shape: Shape) {
let k = kit_holding_room(shape).await;
k.fake
.add_account("x14", "x14@mock.invalid", &[("disk_bytes", 1 << 30)]);
k.fake.add_estate("acme", "x14", "Mini");
let ok = k.ask("GET", "/auth/private/ladder", None, None).await;
assert_eq!(
ok.status,
StatusCode::OK,
"the shop's word on selling instances is kept"
);
k.fake.fail(Some((
Code::PermissionDenied,
"REFUSED [estate-unnamed] the ticket names no estate this monetize holds a key for",
)));
let doors: Vec<(&str, &str, Option<serde_json::Value>)> = vec![
(
"POST",
"/auth/private/start",
Some(
json!({"company": "beta", "front_plan": "f-mini", "main_plan": "m-mini", "main_data_gib": 40}),
),
),
(
"POST",
"/auth/private/change",
Some(
json!({"company": "acme", "front_plan": "f-mini", "main_plan": "m-small", "main_data_gib": 120}),
),
),
("GET", "/auth/private/status?company=acme", None),
(
"POST",
"/auth/private/renew",
Some(json!({"company": "acme"})),
),
("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,
),
];
for (method, path, body) in &doors {
let r = k.ask(method, path, Some("x14"), body.clone()).await;
assert_eq!(
(r.status, r.error()),
(
StatusCode::FORBIDDEN,
monetize_site_buy::rules::WHO_NOT_CONFIRMED
),
"{shape:?} {method} {path}"
);
}
let r = k
.ask(
"POST",
"/auth/signup/start",
None,
Some(json!({"username": "newbie", "email": "newbie@mock.invalid"})),
)
.await;
assert_eq!(
(r.status, r.error()),
(
StatusCode::FORBIDDEN,
monetize_site_buy::rules::WHO_NOT_CONFIRMED
),
"{shape:?} sign-up"
);
assert!(
k.fake.book().checkouts.is_empty(),
"nothing was sold while the box refused"
);
}
// ── 2. the buy pages' own words ─────────────────────────────────────────────────────────
/// The string literals of a script or Rust source that read as a sentence (a space in them),
/// comment lines left out.
fn sentences(source: &str) -> Vec<String> {
let mut out = Vec::new();
for line in source.lines() {
let t = line.trim_start();
if t.starts_with("//") || t.starts_with("/*") || t.starts_with('*') {
continue;
}
let mut chars = line.chars().peekable();
let mut open: Option<char> = None;
let mut cur = String::new();
let mut prev = ' ';
while let Some(c) = chars.next() {
match open {
None if (c == '\'' || c == '"' || c == '`')
&& !(c == '\'' && prev.is_alphanumeric()) =>
{
open = Some(c);
cur.clear();
}
None if c == '/' && chars.peek() == Some(&'/') => break,
Some(q) if c == '\\' => {
let _ = q;
chars.next();
}
Some(q) if c == q => {
if cur.contains(' ') {
out.push(cur.clone());
}
open = None;
}
Some(_) => cur.push(c),
None => {}
}
prev = c;
}
}
out
}
/// Only the part of a Rust source before its tests.
fn before_tests(src: &str) -> &str {
src.split("#[cfg(test)]").next().unwrap_or(src)
}
#[test]
fn the_buy_pages_and_the_relays_buyer_sentences_name_no_operator_machinery() {
use monetize_site_buy::{relay, rules};
let mut bad = Vec::new();
for (name, js) in [
("buy.js", monetize_site_buy::assets::BUY_JS),
("welcome.js", monetize_site_buy::assets::WELCOME_JS),
("paid.js", monetize_site_buy::assets::PAID_JS),
] {
for s in sentences(js) {
if !operator_words(&s).is_empty() {
bad.push(format!("{name}: {s}"));
}
}
}
// Every sentence a buy door refuses with (`Answer::refuse(…, "…")`).
for line in before_tests(include_str!("../src/relay.rs"))
.lines()
.filter(|l| l.contains("Answer::refuse("))
{
for s in sentences(line) {
if !operator_words(&s).is_empty() {
bad.push(format!("relay.rs: {s}"));
}
}
}
for (name, words) in [
("SHOP_DID_NOT_ANSWER", rules::SHOP_DID_NOT_ANSWER),
("WHO_NOT_CONFIRMED", rules::WHO_NOT_CONFIRMED),
("NOT_DONE_HERE", rules::NOT_DONE_HERE),
("PAY_URL_REFUSED", rules::PAY_URL_REFUSED),
("SHOP_UNAVAILABLE", relay::SHOP_UNAVAILABLE),
("ACCOUNT_NOT_SOLD_HERE", relay::ACCOUNT_NOT_SOLD_HERE),
("START_TOO_SLOW", relay::START_TOO_SLOW),
("NOT_A_SESSION", relay::NOT_A_SESSION),
("RENEWAL_NOT_VERIFIED", relay::RENEWAL_NOT_VERIFIED),
(
"AWAITING_PROVIDER",
monetize_site_buy::purchase::AWAITING_PROVIDER,
),
("PROVIDER_LATE", monetize_site_buy::purchase::PROVIDER_LATE),
("NOTHING_FITS", monetize_site_buy::purchase::NOTHING_FITS),
] {
if !operator_words(words).is_empty() {
bad.push(format!("{name}: {words}"));
}
}
assert!(
bad.is_empty(),
"buyer sentences that name the operator's machinery:\n{}",
bad.join("\n")
);
}
/// ★ **PAID-2 (owner 2026-10-26): the welcome page's register button says what it does.** "Register
/// it here now and continue" was unclear. The shared page says "Register your security key now",
/// with ONE line under it: "Or open the link we sent to your e-mail." No site keeps a copy.
#[test]
fn the_welcome_pages_register_button_says_register_your_security_key_now() {
let js = monetize_site_buy::assets::WELCOME_JS;
for words in [
"Register your security key now",
"Or open the link we sent to your e-mail.",
] {
assert!(
js.contains(&format!("'{words}'")),
"the welcome page says {words:?}"
);
}
assert!(
!js.contains("Register it here now"),
"the old words are gone"
);
}
/// ★ **The account line's words** (owner 2026-10-27): "Your <site> account shows everything you
/// have bought and lets you add more. You pay only for the storage you choose." The site is the
/// page's own host (`{host}`, filled from `location.hostname`), never a name written in.
#[test]
fn the_sign_up_pages_account_line_is_the_owners_words_with_the_sites_own_name() {
let js = monetize_site_buy::assets::BUY_JS;
assert!(js.contains("'account': 'Your {host} account shows everything you have bought and lets you add more. You pay only for the storage you choose.',"), "the owner's words");
assert!(
!js.contains(
"is where you see and grow what you bought. Then you pay for the room you chose."
),
"the old words are gone"
);
assert!(
sentences(js)
.iter()
.all(|s| !s.contains("gunnar.rs") && !s.contains("holger.rs")),
"no site's name is written into what the page says"
);
}
/// ★ BUY-ONE-2: the owner's sentences and the two tab names live in the ONE buy page; the free
/// row's note, the card check and the beta line are the SITE's words (RA5-41: a Holger site, or
/// one on another vendor or currency, says its own), filled from its settings.
#[test]
fn the_one_buy_page_carries_the_owners_sentences_and_tab_names() {
let said = sentences(monetize_site_buy::assets::BUY_JS);
for words in [
"Your {host} account shows everything you have bought and lets you add more. You pay only for the storage you choose.",
"Grow your plan",
"New private instance",
] {
assert!(said.iter().any(|s| s == words), "the page says {words:?}");
}
let js = monetize_site_buy::assets::BUY_JS;
assert!(
js.contains("'account-free': '@@CARD_CHECK@@',"),
"the free row's line is its card check, the site's words"
);
assert!(
js.contains("note: '@@FREE_NOTE@@' };")
&& js.contains("const BETA_LINE = '@@BETA_LINE@@';")
);
for gunnars in [
"Free: clone only. Pushing needs a paid plan.",
"A card check at Stripe (0 kr) proves you are a person.",
"Beta: we replicate your data, but cannot guarantee it.",
] {
assert!(
!said.iter().any(|s| s == gunnars),
"Gunnar's words are its settings', not the shared page's: {gunnars:?}"
);
}
for gone in ["New instance", "Private @@PRODUCT@@ instance"] {
assert!(
!said.iter().any(|s| s == gone),
"the old tab name {gone:?} is still said"
);
}
}
#[test]
fn the_sentence_scanner_reads_what_a_page_says() {
assert_eq!(
sentences("show('Your estate is laid', 'err'); // a lay"),
vec!["Your estate is laid".to_owned()]
);
assert!(sentences("// 'the operator says'").is_empty());
assert_eq!(
operator_words("The operator's cap"),
vec!["operator".to_owned(), "cap".to_owned()]
);
assert!(operator_words("Your capital is capped? no: a capacity").is_empty());
}