crates/monetize-site-buy/tests/ladder_cache.rs
//! ★ **The plans page answers from the front's cache, never from a slow ONE box** (LADDER-1/2,
//! owner 2026-10-27): on gunnar.rs the "New instance" tab said "The shop did not answer" and
//! "Buy a plan" took about 5 s, because each page view asked the ONE box, whose ladder prices
//! every step at the cloud behind the same one-at-a-time lane as its whole-account listings.
//! Both ladders (the account's plans and the private instance's) are kept per site, refreshed
//! by a jera job, and the page answers at once with the cache's age; with nothing kept yet and
//! a ONE box that does not answer, the page gets a named error, never a spinner.
#![cfg(feature = "testkit")]
use std::time::{Duration, Instant};
use axum::http::StatusCode;
use monetize_site_buy::relay::{private_ladder, signup_plans, Ask, Relay};
use monetize_site_buy::testkit::{FakeOneBox, Shape, TestSite};
fn ask() -> Ask {
let mut h = axum::http::HeaderMap::new();
h.insert("accept-language", "sv-SE".parse().unwrap());
Ask::new(h, "198.51.100.7")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_stuck_one_box_does_not_slow_the_plans_page() {
let fake = FakeOneBox::spawn("gunnar", "b").await;
let r = Relay::new(TestSite::over(Shape::Gunnar, &fake), fake.link());
let (plans, ladder) = (
signup_plans(&r, &ask(), None).await,
private_ladder(&r, &ask(), None).await,
);
assert_eq!(
(plans.status, ladder.status),
(StatusCode::OK, StatusCode::OK),
"the first views fill the cache: {} {}",
plans.body,
ladder.body
);
fake.stall(Some(Duration::from_secs(30)));
for _ in 0..5 {
let t = Instant::now();
let plans = signup_plans(&r, &ask(), None).await;
let ladder = private_ladder(&r, &ask(), None).await;
let took = t.elapsed();
assert!(
took < Duration::from_millis(200),
"both pages answered in {took:?} while the ONE box is stuck"
);
assert_eq!(
(plans.status, ladder.status),
(StatusCode::OK, StatusCode::OK)
);
assert!(
plans.body["age_s"].is_u64() && ladder.body["age_s"].is_u64(),
"the cache's age is said: {} {}",
plans.body,
ladder.body
);
assert!(
!plans.body["plans"].as_array().unwrap().is_empty()
&& !ladder.body["steps"].as_array().unwrap().is_empty()
);
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn an_empty_cache_and_a_stuck_one_box_give_the_named_error() {
let fake = FakeOneBox::spawn("gunnar", "b").await;
fake.stall(Some(Duration::from_secs(30)));
let r = Relay::new(TestSite::over(Shape::Gunnar, &fake), fake.link());
for page in ["plans", "ladder"] {
let t = Instant::now();
let a = if page == "plans" {
signup_plans(&r, &ask(), None).await
} else {
private_ladder(&r, &ask(), None).await
};
assert!(
t.elapsed() < Duration::from_millis(1_500),
"{page}: answered in {:?}, not left spinning",
t.elapsed()
);
assert_eq!(
a.status,
StatusCode::SERVICE_UNAVAILABLE,
"{page}: {}",
a.body
);
assert_eq!(
a.body["error"],
monetize_site_buy::ladders::NOT_READ,
"{page}: the named error"
);
}
}
/// ★ **Building a site's doors warms both ladders** (LADDER-2): the first page view after a front
/// starts finds them kept, rather than asking a ONE box that prices them for seconds.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn building_the_doors_warms_both_ladders() {
let fake = FakeOneBox::spawn("gunnar", "b").await;
let r = std::sync::Arc::new(Relay::new(
TestSite::over(Shape::Gunnar, &fake),
fake.link(),
));
let _doors = monetize_site_buy::router(std::sync::Arc::clone(&r));
let ladders = r.shop.caches().ladders();
let t = Instant::now();
while (ladders.kept(monetize_site_buy::ladders::PRIVATE).is_none()
|| ladders
.kept(&monetize_site_buy::ladders::signup_key(0))
.is_none())
&& t.elapsed() < Duration::from_secs(5)
{
tokio::time::sleep(Duration::from_millis(20)).await;
}
assert!(
ladders.kept(monetize_site_buy::ladders::PRIVATE).is_some(),
"the private ladder is kept before any page asked"
);
assert!(
ladders
.kept(&monetize_site_buy::ladders::signup_key(0))
.is_some(),
"the account plans (the default market) are kept before any page asked"
);
}