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/plans/tests.rs

//! The server half of the price rule: the basis read through the cloud seam
//! and cached, and N counted from what was paid last month.

use std::sync::atomic::Ordering;
use std::sync::Arc;

use monetize::testing::{MockCloud, MockPay, MockProduct};
use monetize_embed::civil::unix_ms_from_civil;
use monetize_pay::Money;

use super::*;
use crate::config::{Config, MachineConfig};
use crate::registry::Registry;

const GIB: u64 = 1 << 30;

/// The owner's estate on the mock: three servers at 10.00 EUR each through
/// the cloud and their public IPv4 addresses at 3.51 EUR each from the same
/// list (+ 96.80 EUR stated, what the seam cannot price) = 137.33 EUR a month; a GiB of main's data tier at 0.10 EUR through the cloud
/// + 0.1599 EUR for the twin's replica tier = 0.2599 EUR.
pub(crate) fn owners_pricing() -> PricingConfig {
    PricingConfig {
        currency: Currency::Eur,
        base_extra_month_minor: 9_680,
        replica_gib_month_milli: 15_990,
        fx: Fx(
            [("SEK".to_string(), 110_000u64), ("DKK".to_string(), 74_600)]
                .into_iter()
                .collect(),
        ),
        plans: [
            ("10gb-1m", 10, Period::Month),
            ("10gb-12m", 10, Period::Year),
            ("100gb-12m", 100, Period::Year),
            ("500gb-12m", 500, Period::Year),
        ]
        .into_iter()
        .map(|(id, gib, period)| {
            (
                id.to_string(),
                PlanSpec {
                    size_bytes: gib * GIB,
                    period,
                    target: None,
                },
            )
        })
        .collect(),
        ..PricingConfig::default()
    }
}

/// `[machines]` for the owner's estate, every box on the mock cloud.
pub(crate) fn owners_machines() -> Config {
    let m = |role: &str, plan: &str| MachineConfig {
        role: role.into(),
        hostname: format!("gunnar-{role}"),
        cloud: "mockcloud".into(),
        server: format!("srv-{role}"),
        plan: plan.into(),
        ..Default::default()
    };
    Config {
        machines: [
            ("main".to_string(), m("appliance", "4xCPU-8GB")),
            ("twin".to_string(), m("twin", "2xCPU-4GB")),
            ("front".to_string(), m("front", "1xCPU-2GB")),
        ]
        .into(),
        ..Config::default()
    }
}

fn state(cloud: Arc<MockCloud>) -> AppState {
    let mut s = AppState::in_memory(
        Registry::new()
            .shared_product(Arc::new(MockProduct::gunnar()))
            .cloud(cloud),
    );
    *s.config.write().unwrap() = owners_machines();
    s.plans = PlanBook::new([("gunnar".to_string(), owners_pricing())].into());
    s
}

/// ★ **The basis is the PROVIDER's price**, read through the cloud seam: the
/// servers of `[machines]` and a GiB of main's data tier, plus what the seam
/// cannot price, stated — the owner's 137.33 EUR and 0.26 EUR per GiB.
#[test]
fn the_basis_is_read_from_the_providers_price_and_is_the_owners_estate() {
    let cloud = Arc::new(MockCloud::new());
    let s = state(cloud.clone());
    let b = read_basis(&s, "gunnar").expect("priced");
    assert_eq!(
        b,
        CostBasis {
            currency: Currency::Eur,
            base_month_minor: 13_733,
            storage_gib_month_milli: 25_990
        }
    );
    assert_eq!(cloud.prices_asked.load(Ordering::SeqCst), 4 + 9, "three server plans and one data disk, and the nine plans of the zone's catalog, read once for its address price");
    assert_eq!(
        cloud.ipv4_asked.load(Ordering::SeqCst),
        1,
        "and the public IPv4 address, once for the zone every machine is in"
    );
    let r = PriceRule::default();
    assert_eq!(
        Money {
            minor: r.monthly_minor(&b, 10 * GIB, 20),
            currency: Currency::Eur
        }
        .in_words(),
        "13.71 EUR"
    );

    // A provider that prices in another currency than the estate is paid in
    // is not converted at a guess.
    let mut wrong = owners_pricing();
    wrong.currency = Currency::Sek;
    s.plans
        .products
        .write()
        .unwrap()
        .insert("gunnar".into(), wrong);
    let e = read_basis(&s, "gunnar").unwrap_err();
    assert!(e.contains("currency"), "{e}");
}

/// ★ **THE PUBLIC IPv4 ADDRESSES ARE THE PROVIDER'S PRICE** (owner,
/// 2026-10-05: the base counted 3 × a typed 3.00 EUR; UpCloud's list says
/// `ipv4_address` = 3.51): each machine's address from its cloud's price
/// list, the same book that prices the servers and the disk — a list at 4.00
/// is 3 × 4.00 in the base. The stated fallback (3.51 unless said) only when
/// the provider cannot price an address.
#[test]
fn the_base_counts_each_machines_public_ipv4_at_the_providers_price_and_the_fallback_only_without_one(
) {
    let base_without_ipv4 = 3 * 1_000 + 9_680;
    let cloud = Arc::new(MockCloud::new());
    *cloud.ipv4_minor.lock().unwrap() = Some(400);
    let s = state(cloud.clone());
    assert_eq!(
        read_basis(&s, "gunnar").unwrap().base_month_minor,
        base_without_ipv4 + 3 * 400,
        "three addresses at the list's 4.00"
    );

    // A provider that cannot price an address: the stated fallback, 3.51 by default.
    let cloud = Arc::new(MockCloud::new());
    *cloud.fail_ipv4.lock().unwrap() = "no ipv4_address in this list".into();
    let s = state(cloud.clone());
    assert_eq!(owners_pricing().ipv4_fallback_month_minor, 351);
    assert_eq!(
        read_basis(&s, "gunnar").unwrap().base_month_minor,
        base_without_ipv4 + 3 * 351,
        "the fallback, 3.51 a month each"
    );
    let mut said = owners_pricing();
    said.ipv4_fallback_month_minor = 300;
    s.plans
        .products
        .write()
        .unwrap()
        .insert("gunnar".into(), said);
    assert_eq!(
        read_basis(&s, "gunnar").unwrap().base_month_minor,
        base_without_ipv4 + 3 * 300,
        "a stated fallback"
    );
}

/// ★ **CACHED at least 10 minutes**: a second reading inside the life asks
/// the provider nothing; past it, one reading.
#[test]
fn the_basis_is_cached_for_ten_minutes_and_read_again_after() {
    let cloud = Arc::new(MockCloud::new());
    let s = state(cloud.clone());
    assert!(s.plans.cached("gunnar").is_none(), "nothing read yet");
    read_basis(&s, "gunnar").unwrap();
    let asked = cloud.prices_asked.load(Ordering::SeqCst);
    s.plans.age_for_test(BASIS_FRESH - Duration::from_secs(5));
    assert!(
        s.plans.cached("gunnar").is_some(),
        "still fresh at 9 min 55 s"
    );
    assert_eq!(
        cloud.prices_asked.load(Ordering::SeqCst),
        asked,
        "a cached basis asks the provider nothing"
    );
    s.plans.age_for_test(Duration::from_secs(10));
    assert!(
        s.plans.cached("gunnar").is_none(),
        "stale past 10 minutes: the next asker reads again"
    );
}

/// A cloud that cannot price says so, by name; a stated basis needs no cloud.
#[test]
fn a_cloud_that_cannot_price_is_named_and_a_stated_basis_needs_none() {
    let cloud = Arc::new(MockCloud::new());
    cloud.fail_price("the price book is down");
    let s = state(cloud);
    let e = read_basis(&s, "gunnar").unwrap_err();
    assert!(
        e.contains("could not price") && e.contains("price book is down"),
        "{e}"
    );
    let mut stated = owners_pricing();
    stated.basis = Some(StatedBasis {
        base_month_minor: 13_580,
        storage_gib_month_milli: 25_990,
    });
    s.plans
        .products
        .write()
        .unwrap()
        .insert("gunnar".into(), stated);
    assert_eq!(read_basis(&s, "gunnar").unwrap().base_month_minor, 13_580);
}

/// ★ **N = last month's paying accounts, never under 20** — distinct
/// tenants who paid in the PREVIOUS calendar month (a granted sale at a
/// price, or a paid signup), counted once a month.
#[test]
fn n_is_last_months_distinct_paying_accounts_and_never_under_twenty() {
    let cloud = Arc::new(MockCloud::new());
    let mut s = state(cloud);
    let september = unix_ms_from_civil(2026, 9, 10);
    let october = unix_ms_from_civil(2026, 10, 5);
    let p = MockProduct::gunnar();
    // 25 tenants paid in September, one of them twice; three paid in October.
    for i in 0..26u32 {
        let t = if i == 25 {
            "t0".to_string()
        } else {
            format!("t{i}")
        };
        let mut buy = monetize::testing::purchase(&t);
        buy.starts_unix_ms = september + u64::from(i) * 60_000;
        s.clock = Box::new(move || september);
        s.transaction(p.served(), monetize_pay::Vendor::of(&MockPay::new()))
            .run(&buy)
            .expect("paid");
    }
    s.clock = Box::new(move || october);
    for i in 0..3u32 {
        let mut buy = monetize::testing::purchase(&format!("new{i}"));
        buy.starts_unix_ms = october;
        s.transaction(p.served(), monetize_pay::Vendor::of(&MockPay::new()))
            .run(&buy)
            .expect("paid");
    }
    assert_eq!(
        paying_last_month(&s, "gunnar", october),
        25,
        "distinct tenants, September only"
    );
    let rule = PriceRule::default();
    s.clock = Box::new(move || october);
    assert_eq!(accounts(&s, "gunnar", &rule), 25);
    assert_eq!(
        rule.accounts(paying_last_month(
            &s,
            "gunnar",
            unix_ms_from_civil(2026, 9, 20)
        )),
        20,
        "August had none: N is 20"
    );
}

/// **test2's estate** (2026-10-05): the basis that read 100.54 SEK for
/// 10 GiB · 1 month and 966.24 SEK for 10 GiB · 12 months by the rule (base
/// 73.00 EUR a month with its three public IPv4 addresses at a typed 3.00,
/// storage 0.2599 EUR per GiB-month), the addresses now at the provider's
/// 3.51 — 74.53 EUR — the SEK and DKK rates of the kit, and the owner's two
/// targets: 10 GiB · 1 month 110 SEK, 10 GiB · 12 months 890 SEK.
fn test2_pricing() -> (PricingConfig, CostBasis) {
    let mut cfg = owners_pricing();
    cfg.plans.get_mut("10gb-1m").unwrap().target = Some([("SEK".to_string(), 11_000u64)].into());
    cfg.plans.get_mut("10gb-12m").unwrap().target = Some([("SEK".to_string(), 89_000u64)].into());
    (
        cfg,
        CostBasis {
            currency: Currency::Eur,
            base_month_minor: 7_300 + 3 * (351 - 300),
            storage_gib_month_milli: 25_990,
        },
    )
}

fn priced(
    cfg: &PricingConfig,
    basis: &CostBasis,
    n: u32,
    to: Currency,
    id: &str,
    current: Option<&Current>,
    now: u64,
) -> Priced {
    let row = plan::rows(&cfg.rule, current, &cfg.plans, now)
        .into_iter()
        .find(|r| r.id == id)
        .unwrap_or_else(|| panic!("no row {id}"));
    let p = price_row(cfg, basis, n, to, &row, current, now).expect("priced");
    assert!(
        clears_floor(cfg, &p),
        "{id} at N={n} in {to:?}: {} is under the floor",
        p.price.in_words()
    );
    p
}

/// ★ **The owner's two prices are TARGETS on two plans** (2026-10-05:
/// "10 GiB monthly 110+", "12 months 890"; "do the professional choice"),
/// on test2. 10 GiB · 1 month is max(110, rule, floor): 110.00 SEK at
/// N = 20 and N = 30 (the rule says 101.75 and 82.28). 10 GiB · 12 months is
/// max(floor, 890) (owner 2026-10-06: FIXED at the target): the floor at
/// N = 20 (935.33) and 21 (909.15), the target from 22 on (890.00), also at
/// N = 30 where the rule says 790.68 — never under the floor.
/// Every plan without a target — 100 and 500 GiB — is the rule's, which did
/// not move.
#[test]
fn the_owners_prices_are_targets_raising_the_month_and_lowering_the_year_and_nothing_else_moves() {
    let (cfg, b) = test2_pricing();
    let now = unix_ms_from_civil(2026, 10, 5);
    let at =
        |n: u32, to: Currency, id: &str| priced(&cfg, &b, n, to, id, None, now).price.in_words();
    assert_eq!(
        (
            at(20, Currency::Sek, "10gb-1m"),
            at(30, Currency::Sek, "10gb-1m")
        ),
        ("110.00 SEK".into(), "110.00 SEK".into()),
        "the month target raises it"
    );
    assert_eq!(
        at(20, Currency::Sek, "10gb-12m"),
        "935.33 SEK",
        "the floor wins at N = 20 (the rule says 976.80)"
    );
    assert_eq!(
        at(21, Currency::Sek, "10gb-12m"),
        "909.15 SEK",
        "still the floor (the rule says 950.40)"
    );
    assert_eq!(
        at(22, Currency::Sek, "10gb-12m"),
        "890.00 SEK",
        "the target (the rule says 926.64)"
    );
    assert_eq!(
        at(30, Currency::Sek, "10gb-12m"),
        "890.00 SEK",
        "FIXED at the target (owner 2026-10-06): the rule's 790.68 does not lower it"
    );
    // The month a target year locks: its twelfth, up; the rule's own month when the rule stands.
    assert_eq!(
        priced(&cfg, &b, 22, Currency::Sek, "10gb-12m", None, now)
            .month_price
            .in_words(),
        "74.17 SEK"
    );
    assert_eq!(
        priced(&cfg, &b, 30, Currency::Sek, "10gb-12m", None, now)
            .month_price
            .in_words(),
        "74.17 SEK",
        "the year stays at the target, so its month too"
    );
    // Another market: the SEK target back in EUR (rounded down) and on.
    assert_eq!(at(22, Currency::Eur, "10gb-12m"), "80.90 EUR");
    assert_eq!(at(22, Currency::Dkk, "10gb-12m"), "603.52 DKK");
    assert_eq!(at(20, Currency::Eur, "10gb-1m"), "10.00 EUR");
    // No target: the rule's price, the rule being today's (× 1.40, × 0.80).
    assert_eq!((cfg.rule.markup_bp, cfg.rule.yearly_bp), (14_000, 8_000));
    for (id, gib, n, want) in [
        ("100gb-12m", 100, 20, "4488.00 SEK"),
        ("100gb-12m", 100, 30, "4301.88 SEK"),
        ("500gb-12m", 500, 20, "20091.72 SEK"),
        ("500gb-12m", 500, 30, "19905.60 SEK"),
    ] {
        let rule = cfg
            .fx
            .convert(
                Money {
                    minor: cfg.rule.yearly_total_minor(&b, gib * GIB, n),
                    currency: Currency::Eur,
                },
                Currency::Sek,
            )
            .unwrap();
        assert_eq!(
            (at(n, Currency::Sek, id), rule.in_words()),
            (want.to_string(), want.to_string()),
            "{id} at N = {n}"
        );
    }
    // An UPGRADE from a month paid at the target to 10 GiB · 12 months is
    // the target year less the unused month.
    let month = Current {
        period: Period::Month,
        size_bytes: 10 * GIB,
        paid_until_unix_ms: now + 15 * monetize_embed::civil::DAY_MS,
        state: monetize_product::State::Paid,
        month_price: Some(Money {
            minor: 11_000,
            currency: Currency::Sek,
        }),
    };
    let up = priced(&cfg, &b, 22, Currency::Sek, "10gb-12m", Some(&month), now);
    let credit = up.credit.expect("the unused month is credited");
    assert_eq!(
        up.price.minor + credit.minor,
        89_000,
        "the target year, less the credit: {} + {}",
        up.price.in_words(),
        credit.in_words()
    );
    // A PROLONG of that month keeps its locked 110.00 SEK.
    let prolong = priced(
        &cfg,
        &b,
        30,
        Currency::Sek,
        plan::PROLONG_ID,
        Some(&month),
        now,
    );
    assert_eq!(prolong.price.in_words(), "110.00 SEK");
}

/// ★ **OWNER DECISION 2026-10-06: the 10 GiB · 12 months price is FIXED at
/// 890 SEK — it does not fall with scale** (it was max(floor, min(rule, 890)):
/// 872.52 SEK at N = 25 and 803.88 at N = 30 on the live list). Now max(floor,
/// 890): never under cost + 10 % after the fee (the owner's standing rule),
/// never under the target. Basis: UpCloud's LIVE se-sto1 list, read 2026-10-06
/// (GET /1.3/price): STARTER-4xCPU-8GB 2.9761 c/h → 21.73 EUR, -4xCPU-16GB
/// 4.1666 → 30.42, -1xCPU-2GB 0.8928 → 6.52, ipv4_address 0.4812 → 3.51 each,
/// storage_maxiops 0.031 → 0.2263 EUR/GiB-month; the kit's twin replica 0.0599.
/// Base 69.20 EUR, storage 0.2862 EUR/GiB-month, fx 11.00.
#[test]
fn the_yearly_ten_gib_price_is_fixed_at_its_target_and_never_falls_with_scale() {
    let (mut cfg, _) = test2_pricing();
    cfg.replica_gib_month_milli = 5_990;
    let live = CostBasis {
        currency: Currency::Eur,
        base_month_minor: 2_173 + 3_042 + 652 + 3 * 351,
        storage_gib_month_milli: 22_630 + 5_990,
    };
    let now = unix_ms_from_civil(2026, 10, 6);
    let at = |n: u32, id: &str| {
        priced(&cfg, &live, n, Currency::Sek, id, None, now)
            .price
            .in_words()
    };
    assert_eq!(
        at(20, "10gb-12m"),
        "934.89 SEK",
        "the cost floor still wins at N = 20 (the target is under cost + 10 %)"
    );
    assert_eq!(
        at(25, "10gb-12m"),
        "890.00 SEK",
        "FIXED at the target — the rule's 872.52 does not lower it"
    );
    assert_eq!(
        at(30, "10gb-12m"),
        "890.00 SEK",
        "FIXED at the target — the rule's 803.88 does not lower it"
    );
    assert_eq!(at(200, "10gb-12m"), "890.00 SEK", "at any scale");
    assert_eq!(
        (at(20, "10gb-1m"), at(30, "10gb-1m")),
        ("110.00 SEK".into(), "110.00 SEK".into()),
        "the month is untouched"
    );
}

/// ★ **OWNER 2026-10-06 "make it simple": 890 SEK from day one.** The kit
/// states `[pricing.gunnar.rule] min_accounts = 22`, so N = max(paying, 22):
/// with ONE paying account (a new estate, test2) the live list prices
/// 10 GiB · 12 months at 890.00 SEK and 10 GiB · 1 month at 110.00 SEK.
#[test]
fn with_twenty_two_accounts_as_the_least_the_yearly_ten_gib_is_890_from_day_one() {
    let (mut cfg, _) = test2_pricing();
    cfg.replica_gib_month_milli = 5_990;
    cfg.rule.min_accounts = 22;
    let live = CostBasis {
        currency: Currency::Eur,
        base_month_minor: 2_173 + 3_042 + 652 + 3 * 351,
        storage_gib_month_milli: 22_630 + 5_990,
    };
    let now = unix_ms_from_civil(2026, 10, 6);
    let n = cfg.rule.accounts(1);
    assert_eq!(n, 22, "one paying account: N is the least the kit states");
    assert_eq!(
        priced(&cfg, &live, n, Currency::Sek, "10gb-12m", None, now)
            .price
            .in_words(),
        "890.00 SEK"
    );
    assert_eq!(
        priced(&cfg, &live, n, Currency::Sek, "10gb-1m", None, now)
            .price
            .in_words(),
        "110.00 SEK"
    );
}

/// ★ **Sign-up by the room rule** (round 5.3.1, owner 2026-10-08: ONE bar at
/// sign-up too): a priced id names its plan and the price the checkout charged,
/// and reads back to the declared size and period at that price; a plan that is
/// not declared, a 1-month plan past 10 GiB, or any other id is no priced plan.
#[test]
fn a_priced_sign_up_id_reads_back_to_its_declared_plan_at_its_price() {
    let price = Money {
        minor: 89_000,
        currency: Currency::Sek,
    };
    let id = priced_id("gunnar", "10gb-12m", &price);
    assert_eq!(id, "gunnar/10gb-12m@89000SEK");
    assert_eq!(
        parse_priced_id(&id),
        Some(("gunnar".to_owned(), "10gb-12m".to_owned(), price))
    );
    assert_eq!(
        parse_priced_id("gunnar/10gb-1m"),
        None,
        "a table plan is no priced plan"
    );
    assert_eq!(
        parse_priced_id("gunnar/10gb-1m@12XYZ"),
        None,
        "an unknown currency"
    );
    assert_eq!(parse_priced_id("@100SEK"), None);

    let s = state(Arc::new(MockCloud::new()));
    let year = priced_signup_plan(&s, &id).expect("a declared 12-month plan");
    assert_eq!(
        (
            year.cap_bytes,
            year.period_days,
            year.amount_minor,
            year.currency
        ),
        (10 * GIB, 365, 89_000, Currency::Sek)
    );
    let big = priced_signup_plan(&s, "gunnar/500gb-12m@512345EUR").expect("500 GiB for 12 months");
    assert_eq!((big.cap_bytes, big.period_days), (500 * GIB, 365));
    let month = priced_signup_plan(&s, "gunnar/10gb-1m@11000SEK").expect("the 1-month plan");
    assert_eq!((month.cap_bytes, month.period_days), (10 * GIB, 30));
    assert!(
        priced_signup_plan(&s, "gunnar/1000gb-12m@1SEK").is_none(),
        "a plan this deployment does not declare is never sold"
    );
    assert!(
        priced_signup_plan(&s, "holger/10gb-12m@1SEK").is_none(),
        "a product with no [pricing]"
    );
    let mut wide = owners_pricing();
    wide.plans.insert(
        "100gb-1m".into(),
        PlanSpec {
            size_bytes: 100 * GIB,
            period: Period::Month,
            target: None,
        },
    );
    s.plans
        .products
        .write()
        .unwrap()
        .insert("gunnar".into(), wide);
    assert!(
        priced_signup_plan(&s, "gunnar/100gb-1m@1SEK").is_none(),
        "a 1-month plan is ONLY 10 GiB (owner 2026-10-05)"
    );
}

/// ★ **A new account's plans are the rule's, priced now in its market** — the
/// 1-month 10 GiB first, then the 12-month ones by size — and the START checks
/// a priced id against today's price: the shown price is current, a stale or a
/// forged one is not.
#[test]
fn a_new_accounts_plans_are_the_rules_and_a_start_checks_the_price_shown() {
    let cloud = Arc::new(MockCloud::new());
    let s = Arc::new(state(cloud));
    let (cfg, basis) = test2_pricing();
    s.plans
        .products
        .write()
        .unwrap()
        .insert("gunnar".into(), cfg);
    s.plans.store("gunnar", basis);
    let rooms = signup_rooms_cached(&s, "gunnar", Market::Se).expect("priced");
    let names: Vec<&str> = rooms.iter().map(|r| r.name.as_str()).collect();
    assert_eq!(rooms.len(), 4, "{names:?}");
    assert_eq!(rooms[0].plan.period_days, 30, "the month first: {names:?}");
    assert!(
        rooms[1..].iter().all(|r| r.plan.period_days == 365),
        "then the years: {names:?}"
    );
    assert!(
        rooms
            .windows(2)
            .skip(1)
            .all(|w| w[0].plan.cap_bytes < w[1].plan.cap_bytes),
        "by size: {names:?}"
    );
    assert!(
        rooms
            .iter()
            .all(|r| r.plan.currency == Currency::Sek && r.id.ends_with("SEK")),
        "one market's currency"
    );
    assert_eq!(
        rooms[0].plan.amount_minor, 11_000,
        "the owner's 110 SEK month"
    );
    for r in &rooms {
        assert_eq!(
            priced_id_is_current(&s, &r.id, Market::Se),
            Ok(true),
            "{} is today's price",
            r.id
        );
        assert_eq!(
            priced_signup_plan(&s, &r.id),
            Some(r.plan),
            "the paid check reads the same terms"
        );
    }
    let forged = priced_id(
        "gunnar",
        "500gb-12m",
        &Money {
            minor: 100,
            currency: Currency::Sek,
        },
    );
    assert_eq!(
        priced_id_is_current(&s, &forged, Market::Se),
        Ok(false),
        "a price that is not today's is never started"
    );
    assert!(
        priced_id_is_current(&s, "gunnar/1000gb-12m@1SEK", Market::Se).is_err(),
        "a plan not sold here"
    );
}

/// The kit's `[pricing.gunnar.plans]` (private-gunnar-ops `MONETIZE_COMMERCIAL`,
/// owner test2 2026-10-08: the room bar 10 GB → 1 TB): the month at 10 GiB, then
/// 12 months at 10 / 50 / 100 / 250 / 500 / 1000 GiB, the owner's two targets.
fn the_kits_pricing() -> PricingConfig {
    let mut cfg = owners_pricing();
    cfg.rule.min_accounts = 22;
    cfg.plans = [
        ("10gb-1m", 10, Period::Month),
        ("10gb-12m", 10, Period::Year),
        ("50gb-12m", 50, Period::Year),
        ("100gb-12m", 100, Period::Year),
        ("250gb-12m", 250, Period::Year),
        ("500gb-12m", 500, Period::Year),
        ("1000gb-12m", 1000, Period::Year),
    ]
    .into_iter()
    .map(|(id, gib, period)| {
        (
            id.to_string(),
            PlanSpec {
                size_bytes: gib * GIB,
                period,
                target: None,
            },
        )
    })
    .collect();
    cfg.plans.get_mut("10gb-1m").unwrap().target = Some([("SEK".to_string(), 11_000u64)].into());
    cfg.plans.get_mut("10gb-12m").unwrap().target = Some([("SEK".to_string(), 89_000u64)].into());
    cfg
}

/// ★ **A NEW visitor's bar is the same priced stops an account grows on**
/// (owner on test2 2026-10-09: "+ / − don't work" — the sign-up offered one
/// step). Over the wire, `Signup.Plans` answers the kit's seven stops: 10 GB ·
/// 1 month first, then the 12-month stops up to 1 TB, in the visitor's market
/// currency, each the rule's price and never under the floor; `Signup.Start`
/// on the stop chosen mints the checkout at exactly that price, and the plan
/// it settles to is exactly that room for that period.
#[tokio::test]
async fn a_new_visitor_is_offered_every_stop_and_the_checkout_charges_the_one_chosen() {
    use crate::services::signup::SignupService;
    use monetize_proto::signup_server::Signup as _;
    use monetize_proto::{self as pb};

    let pay = crate::testing::CallbackPay::with_checkout(
        "stripe-shared-secret",
        "https://checkout.example/pay/",
    );
    let mut st = AppState::in_memory(
        Registry::new()
            .sells("gunnar", pay.clone())
            .cloud(MockCloud::new()),
    );
    *st.config.write().unwrap() = owners_machines();
    let cfg = the_kits_pricing();
    st.plans = PlanBook::new([("gunnar".to_string(), cfg.clone())].into());
    st.signup_sink = crate::signup::tests::CountingSink::creating();
    let s = Arc::new(st);
    let basis = read_basis(&s, "gunnar").expect("the provider's price");
    s.plans.store("gunnar", basis);
    let now = s.now();
    let n = accounts(&s, "gunnar", &cfg.rule);

    for (market, currency) in [
        (pb::Market::Se, Currency::Sek),
        (pb::Market::Dk, Currency::Dkk),
        (pb::Market::Fi, Currency::Eur),
    ] {
        let out = SignupService(s.clone())
            .plans(tonic::Request::new(pb::SignupPlansRequest {
                product: "gunnar".into(),
                market: market as i32,
            }))
            .await
            .expect("plans")
            .into_inner();
        let names: Vec<&str> = out.plans.iter().map(|p| p.name.as_str()).collect();
        assert_eq!(
            out.plans.len(),
            7,
            "{market:?}: the month and six 12-month stops: {names:?}"
        );
        assert_eq!(out.market, market as i32);
        assert_eq!(
            (out.plans[0].cap_bytes, out.plans[0].period_days),
            (10 * GIB, 30),
            "10 GB · 1 month first: {names:?}"
        );
        let years: Vec<u64> = out.plans[1..].iter().map(|p| p.cap_bytes / GIB).collect();
        assert_eq!(years, [10, 50, 100, 250, 500, 1000], "{names:?}");
        assert!(
            out.plans[1..].iter().all(|p| p.period_days == 365),
            "{names:?}"
        );
        for p in &out.plans {
            let (_, row_id, price) =
                parse_priced_id(&p.id).unwrap_or_else(|| panic!("{} is a priced id", p.id));
            assert_eq!(
                price.currency, currency,
                "{market:?}: one market's currency: {}",
                p.id
            );
            // The rule's price, the floor held (`priced` asserts `clears_floor`).
            let rule = priced(&cfg, &basis, n, currency, &row_id, None, now);
            assert_eq!(price, rule.price, "{}", p.id);
            let words =
                format!("{} {} {}", p.name, p.resources_words, p.price_words).to_ascii_lowercase();
            assert!(
                !words.contains("left") && !words.contains("cap"),
                "a visitor never reads a cap: {words}"
            );
        }
        let prices: std::collections::BTreeSet<u64> = out
            .plans
            .iter()
            .map(|p| parse_priced_id(&p.id).unwrap().2.minor)
            .collect();
        assert_eq!(prices.len(), 7, "the price moves with every stop");
    }

    // The visitor chose the top stop (and then one between): the checkout is
    // minted at exactly that price, and the plan is exactly that room and period.
    let se = SignupService(s.clone())
        .plans(tonic::Request::new(pb::SignupPlansRequest {
            product: "gunnar".into(),
            market: pb::Market::Se as i32,
        }))
        .await
        .unwrap()
        .into_inner();
    for (i, chosen) in [se.plans[6].clone(), se.plans[2].clone()]
        .into_iter()
        .enumerate()
    {
        let user = format!("olle{i}");
        let started = SignupService(s.clone())
            .start(tonic::Request::new(pb::StartSignupRequest {
                product: "gunnar".into(),
                username: user.clone(),
                email: format!("{user}@example.com"),
                plan: chosen.id.clone(),
                market: pb::Market::Se as i32,
                success_url: "https://test2.gunnar.rs/welcome?session={session}".into(),
                cancel_url: "https://test2.gunnar.rs/signup".into(),
                ..Default::default()
            }))
            .await
            .unwrap_or_else(|e| panic!("{}: {e}", chosen.id))
            .into_inner();
        assert!(
            started.url.starts_with("https://checkout.example/pay/"),
            "{started:?}"
        );
        let order = pay
            .signup_orders()
            .last()
            .cloned()
            .expect("an order reached the vendor");
        let (_, _, price) = parse_priced_id(&chosen.id).unwrap();
        assert_eq!(
            (order.plan.as_str(), order.amount),
            (chosen.id.as_str(), price),
            "the checkout charges the stop shown"
        );
        let plan = priced_signup_plan(&s, &chosen.id).expect("the plan it settles to");
        assert_eq!(
            (
                plan.cap_bytes,
                plan.period_days,
                plan.amount_minor,
                plan.currency
            ),
            (chosen.cap_bytes, 365, price.minor, Currency::Sek),
            "{}",
            chosen.id
        );
    }
}