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/account_page.rs

//! ★ **"Your purchases" on Gunnar's Account page** (lane ACCT-PAGE, owner 2026-10-29: "fix
//! all"). gunnar.rs/account read `test2.gunnar.rs Mini · 7 cores · 16 GB RAM · 40 GB data
//! 0.50 EUR a month` and an everyday "Restore front" button beside Grow and Open.
//!
//! * ACCT-4: the price on a row is what is CHARGED a term. The operator's own buy holds EUR
//!   0.50 a term (A33-5), and test2's term is one day: "0.50 EUR a month" was a month that was
//!   never charged. A customer's month price stays "… a month".
//! * ACCT-6: the row says when the estate's FRONT needs repair (it stopped asking for its
//!   update), and only then: "Restore front" is a repair, never an everyday button. The words
//!   are monetize's; the console draws its Repair section from them alone.

use super::*;

fn held_run(name: &str, cfg: EstateOrderConfig) -> EstateRun {
    let (state, pay, _cloud) = operator_rig(name, cfg);
    let run = start_on(
        &state,
        Some("gunnar-rs"),
        "opa",
        "ops@example.invalid",
        "",
        "ignalina",
        &EstateSizes::default(),
    )
    .expect("the operator's own buy is taken");
    pay.authorize(&run.handle.clone().unwrap().id);
    drive(&state, "opa");
    let run = state.estates.get("opa").unwrap();
    assert!(run.operator_test && run.amount == FIFTY_CENTS, "{run:?}");
    run
}

/// ★ ACCT-4: test2's own buy — EUR 0.50 held for ONE DAY — reads "0.50 EUR a day", never "a
/// month"; on six-month terms the held 0.50 reads "every 6 months".
#[test]
fn the_operator_buys_row_says_the_price_of_its_term_never_a_month() {
    let day = held_run(
        "acct-held-day",
        EstateOrderConfig {
            term_days: 1,
            ..cfg()
        },
    );
    assert_eq!(
        charge_words(&day.quote, day.operator_test),
        "0.50 EUR a day",
        "{:?}",
        day.quote
    );
    let half_year = held_run("acct-held-6m", cfg());
    assert_eq!(half_year.quote.term_months, 6);
    assert_eq!(
        charge_words(&half_year.quote, half_year.operator_test),
        "0.50 EUR every 6 months"
    );
}

/// ★ ACCT-4: a customer's row keeps its month price, as the ladder says it; a customer on a
/// term of days reads what that term charges.
#[test]
fn a_customers_row_keeps_its_month_price() {
    let r = owned("acct-customer", "acme", "alice");
    let run = r.state.estates.get("acme").unwrap();
    assert!(!run.operator_test);
    assert_eq!(charge_words(&run.quote, false), monthly_words(&run.quote));
    assert!(charge_words(&run.quote, false).ends_with(" a month"));
    let days = Quote {
        term_days: 3,
        term_months: 0,
        ..run.quote.clone()
    };
    assert_eq!(
        charge_words(&days, false),
        format!("{} every 3 days", days.total.in_words())
    );
}

/// ★ ACCT-6: a running estate whose front asked for its update lately needs no repair; one
/// whose front has been silent past [`sold::FRONT_SILENT_MS`] does, in words that name the
/// host and the backup; a front that never asked (a release with no taker), an estate not
/// running, and an adopted one are never offered a repair on a guess.
#[test]
fn only_a_silent_front_is_offered_a_repair() {
    let r = owned("acct-repair", "acme", "alice");
    let mut run = r.state.estates.get("acme").unwrap();
    let now = 100 * 3_600_000;
    run.update_taken_at_ms = now - 10 * 60_000;
    assert_eq!(
        sold::repair_words(&run, "acme.gunnar.rs", now),
        "",
        "a front that asked ten minutes ago stands"
    );
    run.update_taken_at_ms = now - sold::FRONT_SILENT_MS - 1;
    let words = sold::repair_words(&run, "acme.gunnar.rs", now);
    assert!(
        words.contains("acme.gunnar.rs") && words.contains("front") && words.contains("backup"),
        "{words}"
    );
    run.phase = phase::GRACE.into();
    assert!(
        !sold::repair_words(&run, "acme.gunnar.rs", now).is_empty(),
        "an estate in grace still runs"
    );
    for gone in [phase::SUSPENDED, phase::DELETED, phase::INSTALLING] {
        run.phase = gone.into();
        assert_eq!(
            sold::repair_words(&run, "acme.gunnar.rs", now),
            "",
            "{gone}: not running, no repair"
        );
    }
    run.phase = phase::CAPTURED.into();
    run.update_taken_at_ms = 0;
    assert_eq!(
        sold::repair_words(&run, "acme.gunnar.rs", now),
        "",
        "a front that never asked: unknown, not lost"
    );
}

/// ★ ACCT-6 over the wire: `Purchases` carries the repair words (empty for a front that
/// stands) and the charged price.
#[test]
fn purchases_carry_the_repair_words_and_the_charged_price() {
    use crate::services::estate_order::EstateOrderService;
    use monetize_proto as pb;
    use monetize_proto::estate_order_server::EstateOrder as _;
    let r = owned("acct-wire", "acme", "alice");
    let svc = EstateOrderService(r.state.clone());
    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    let p = rt
        .block_on(
            svc.purchases(tonic::Request::new(pb::EstateOrderPurchasesRequest {
                account: "alice".into(),
                actor_ticket: account_ticket("alice", "n-acct-1"),
            })),
        )
        .expect("alice's purchases")
        .into_inner();
    let acme = &p.estates[0];
    assert_eq!(
        acme.repair_words, "",
        "a front just laid needs no repair: {acme:?}"
    );
    let run = r.state.estates.get("acme").unwrap();
    assert_eq!(
        acme.monthly_words,
        charge_words(&run.quote, run.operator_test)
    );
}