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

nordisk/monetize

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"
    );
}