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

nordisk/monetize

crates/monetize-server/src/estate_order/tests/cap_rule.rs

//! **The cap rule** (owner 2026-10-14, after the first LIVE site lay of test2.gunnar.rs):
//!
//! * the OPERATOR's own orders — a site order, and a change of a site the operator owns — meet
//!   the cloud's spend cap BEFORE the payment, refused plainly with the numbers in EUR,
//!   nothing charged;
//! * a CUSTOMER's order is still taken over the cap: it is paid and waits, the operator told;
//! * in both cases the cap is checked for the whole change before ANY machine is changed (live:
//!   a paid grow resized the front, and only then the cap queued the rest — a half-done grow).

use super::*;
use monetize_cloud::cap::{Ceiling, SpendCap};

const EUR: monetize_pay::Currency = monetize_pay::Currency::Eur;

/// The registry's cloud: the mock inside a real spend cap of `monthly` minor a month.
fn capped(cloud: &Arc<MockCloud>, dir: &std::path::Path, monthly: u64) -> SpendCap<Arc<MockCloud>> {
    SpendCap::new(
        cloud.clone(),
        Ceiling::monthly(monthly, EUR),
        dir.join("spend-cap.json"),
    )
    .unwrap()
}

/// [`site_rig`]'s operator site `gunnar-rs` (owned by `ignalina`), the cloud capped at `monthly`.
fn capped_site_rig(
    name: &str,
    monthly: u64,
) -> (Arc<AppState>, HoldPay, Arc<MockCloud>, Arc<AtomicU64>) {
    let dir = tmp(name);
    let pay = HoldPay::default();
    let cloud = Arc::new(MockCloud::new());
    let kit = FakeKit::new("PASS");
    let site =
        |vendor: &str, site: &str, owner: &str, operator_site: bool| crate::config::EstateConfig {
            product: "gunnar".into(),
            vendor: vendor.into(),
            site: site.into(),
            owner: owner.into(),
            operator_site,
            ..Default::default()
        };
    let reg = Registry::new()
        .sells("gunnar", pay.clone())
        .cloud_capped(capped(&cloud, &dir, monthly))
        .shared_product(shop_product())
        .home("gunnar", "gunnar-rs")
        .estate("gunnar-rs", site("holdpay", "gunnar.rs", "ignalina", true));
    let (state, clock, ..) = build_in(
        reg,
        &dir,
        EstateOrderConfig {
            vendor: String::new(),
            apex: String::new(),
            ..cfg()
        },
        kit,
        None,
        None,
    );
    (state, pay, cloud, clock)
}

/// [`rig`], the cloud capped at `monthly`.
fn capped_rig(name: &str, monthly: u64) -> Rig {
    let dir = tmp(name);
    let pay = HoldPay::default();
    let cloud = Arc::new(MockCloud::new());
    let kit = FakeKit::new("PASS");
    let reg = Registry::new()
        .sells("gunnar", pay.clone())
        .cloud_capped(capped(&cloud, &dir, monthly))
        .shared_product(shop_product());
    let (state, clock, mails, health) = build_in(reg, &dir, cfg(), kit.clone(), None, None);
    Rig {
        state,
        pay,
        cloud,
        kit,
        dir,
        clock,
        mails,
        health,
    }
}

/// ★ (a) **A site order over the cloud's cap is refused BEFORE it is paid**, in EUR: the
/// default estate is 3 servers + 8 disks at 10.00 EUR = 110.00 EUR a month; the cap 50.00.
#[test]
fn a_site_order_over_the_clouds_cap_is_refused_before_anything_is_held() {
    let (state, pay, cloud, _) = capped_site_rig("cap-site-order", 5_000);
    let r = site::start_site(
        &state,
        "gunnar-rs",
        "ops@example.invalid",
        &EstateSizes::default(),
    );
    let Err(StartRefusal::NotNow(w)) = &r else {
        panic!("refused plainly before the payment: {r:?}")
    };
    assert_eq!(w, "raise the cap first: this needs 110.00 EUR a month, the cap is 50.00 EUR a month, 0.00 EUR is committed; nothing was charged");
    assert!(pay.0.requests.lock().unwrap().is_empty(), "nothing is held");
    assert!(state.estates.get("gunnarrs").is_none(), "no order is kept");
    assert!(cloud.lays().is_empty());
    // Under a cap it fits, the same order is taken.
    let (state, ..) = capped_site_rig("cap-site-order-fits", 11_000);
    site::start_site(
        &state,
        "gunnar-rs",
        "ops@example.invalid",
        &EstateSizes::default(),
    )
    .expect("fits under the cap: taken");
}

/// ★ (b) **A customer's order over the same cap is taken**, paid, and waits in the queue —
/// the buyer never reads a cap.
#[test]
fn a_customers_order_over_the_clouds_cap_is_still_taken_and_waits() {
    let r = capped_rig("cap-customer", 5_000);
    start(
        &r.state,
        "acme",
        "buyer@example.com",
        "",
        &EstateSizes::default(),
    )
    .expect("a customer is never refused for a cap");
    r.pay.authorize(&handle_of(&r, "acme"));
    drive(&r.state, "acme");
    let run = r.state.estates.get("acme").unwrap();
    assert_eq!(run.phase, phase::HELD, "{:#?}", run.history);
    assert!(run.why.starts_with(ORDERS_WAIT), "{}", run.why);
    assert!(r.cloud.lays().is_empty());
}

/// ★ (a) **A change of the operator's own site over the cap is refused before it is held**:
/// the site laid (110.00 EUR committed under a 120.00 cap), a bigger main adds 40.00.
#[test]
fn a_change_of_the_operators_site_over_the_cap_is_refused_before_anything_is_held() {
    let (state, pay, cloud, _) = capped_site_rig("cap-site-change", 12_000);
    let run = site::start_site(
        &state,
        "gunnar-rs",
        "ops@example.invalid",
        &EstateSizes::default(),
    )
    .unwrap();
    pay.authorize(&run.handle.clone().unwrap().id);
    drive(&state, "gunnarrs");
    assert_eq!(
        state.estates.get("gunnarrs").unwrap().phase,
        phase::CAPTURED
    );
    cloud
        .plan_prices
        .lock()
        .unwrap()
        .insert("STARTER-16xCPU-64GB".into(), 5_000);
    let held = pay.0.requests.lock().unwrap().len();
    let r = change::change(&state, "gunnarrs", &bigger_main());
    let Err(StartRefusal::NotNow(w)) = &r else {
        panic!("refused plainly before the payment: {r:?}")
    };
    assert!(w.starts_with("raise the cap first: this needs 40.00 EUR a month, the cap is 120.00 EUR a month, 110.00 EUR is committed"), "{w}");
    assert_eq!(
        pay.0.requests.lock().unwrap().len(),
        held,
        "nothing more is held"
    );
    assert!(cloud.plan_changes().is_empty(), "no machine is touched");
    assert!(
        state.estates.get("gunnarrs").unwrap().change.is_none(),
        "no change is opened"
    );
}

/// ★ (c) **A paid change is checked against the cap as a WHOLE before any machine changes**:
/// a bigger front (+20.00) fits under the cap, front and main together (+60.00) do not — the
/// change waits with NOTHING resized (live 2026-10-14: the front was resized, then the cap
/// held the rest). A customer's change is taken and waits; the operator is told in EUR.
#[test]
fn a_paid_change_over_the_cap_waits_with_no_machine_changed() {
    // The estate is laid: 110.00 EUR committed; the cap 140.00.
    let r = capped_rig("cap-half-grow", 14_000);
    start_for(
        &r.state,
        "acme",
        "buyer@example.com",
        "",
        "alice",
        &EstateSizes::default(),
    )
    .unwrap();
    r.pay.authorize(&handle_of(&r, "acme"));
    drive(&r.state, "acme");
    assert_eq!(r.state.estates.get("acme").unwrap().phase, phase::CAPTURED);
    {
        let mut p = r.cloud.plan_prices.lock().unwrap();
        p.insert("STARTER-8xCPU-32GB".into(), 3_000);
        p.insert("STARTER-16xCPU-64GB".into(), 5_000);
    }
    let now = r.state.estates.get("acme").unwrap().paid_until_ms - 90 * DAY;
    r.clock.store(now, Ordering::SeqCst);
    let c = change::change(
        &r.state,
        "acme",
        &EstateSizes {
            front_plan: "STARTER-8xCPU-32GB".into(),
            main_plan: "STARTER-16xCPU-64GB".into(),
            ..EstateSizes::default()
        },
    )
    .expect("a customer's change is taken over the cap");
    r.pay.authorize(&c.handle.as_ref().unwrap().id);
    let run = at(&r, now + 60_000);
    assert!(
        r.cloud.plan_changes().is_empty(),
        "no machine is changed while the whole change does not fit: {:?}",
        r.cloud.plan_changes()
    );
    let open = run.change.as_ref().expect("the change waits");
    assert_eq!(open.status, change::status::QUEUED, "{open:#?}");
    assert!(
        open.why.starts_with(ORDERS_WAIT)
            && open.why.contains("60.00 EUR a month")
            && open.why.contains("140.00 EUR a month")
            && open.why.contains("110.00 EUR is committed"),
        "the operator's words, in EUR: {}",
        open.why
    );
    assert!(open.steps.iter().all(|s| !s.done), "{:?}", open.steps);
    assert_eq!(
        run.quote.sizes.front_plan,
        run.laid.as_ref().unwrap().machine("front").unwrap().plan,
        "the front keeps its plan"
    );
}

/// ★ **A waiting order's ALERT is written once**, again when its words change, and as an
/// hourly reminder — never every tick (live 2026-10-14: 242 lines in an hour).
#[test]
fn a_waiting_orders_alert_is_said_once_then_on_a_change_or_hourly() {
    let reference = "estate/alert-once/2026-10-14";
    let t0 = 1_900_000_000_000;
    assert!(
        wait_alert_due(reference, "orders wait: raise the cap: A", t0),
        "the first time"
    );
    for tick in 1..1_800u64 {
        assert!(
            !wait_alert_due(
                reference,
                "orders wait: raise the cap: A",
                t0 + tick * 2_000
            ),
            "tick {tick}: the same words are not said again"
        );
    }
    assert!(
        wait_alert_due(
            reference,
            "orders wait: raise the cap: B",
            t0 + 3_600_000 - 1
        ),
        "new words are said at once"
    );
    assert!(!wait_alert_due(
        reference,
        "orders wait: raise the cap: B",
        t0 + 3_600_000
    ));
    assert!(
        wait_alert_due(
            reference,
            "orders wait: raise the cap: B",
            t0 + 2 * 3_600_000
        ),
        "the hourly reminder"
    );
    forget_wait_alert(reference);
    assert!(
        wait_alert_due(
            reference,
            "orders wait: raise the cap: B",
            t0 + 2 * 3_600_000 + 1
        ),
        "a new wait after it went on is said at once"
    );
}