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

//! ★ **OVW-CLEAN (owner 2026-10-29): the Overview lists only estates that exist, and one slot
//! makes a new one.** Prod's Overview listed every declared site and every record its store
//! keeps: test2-gunnar-rs and test2-holger-rs (declared, not ordered) and acme2, ignalina, polka,
//! rickardtest and test3-holger-rs (terminated orders and signups, still in the fleet's store),
//! each as "not installed".
//!
//! * `Estates.Overview`: the rows are what is installed or being laid; what ended or was never
//!   installed is the quiet line; a declared site with no row is the slot's to start by name.
//! * `Estates.NewEstate`: a declared operator site is ordered as it is; a new name is copied from a
//!   declared site of its product into the SITE BOOK, then ordered — the one site order, the
//!   same payment page. Refused by name, before anything is kept or held.

use std::sync::OnceLock;

use super::*;
use crate::services::estates::EstatesService;
use crate::site_book::{SiteBook, SiteBookLink, SiteBookRefusal, SitePut, SitePutDone, SiteShown};
use monetize_proto as pb;
use monetize_proto::estates_server::Estates as _;

/// A site book that keeps each put and declares it at once, as the ONE box's config watcher
/// does a look later: the new site copies the orders (the media) of the site it names.
#[derive(Clone, Default)]
struct Book {
    state: Arc<OnceLock<Arc<AppState>>>,
    puts: Arc<Mutex<Vec<SitePut>>>,
    /// A box that takes the put late: it is kept, and declared only by [`Book::take`].
    slow: Arc<std::sync::atomic::AtomicBool>,
}

impl Book {
    /// Declare `p` as the ONE box's config watcher does once it looks.
    fn take(&self, p: &SitePut) {
        let state = self.state.get().expect("the rig's state");
        let orders = state
            .registry
            .declared_estates()
            .into_iter()
            .find(|(l, _)| *l == p.media_from)
            .map(|(_, e)| e.orders)
            .unwrap_or_default();
        let e = crate::config::EstateConfig {
            product: p.product.clone(),
            vendor: p.vendor.clone(),
            owner: p.first_admin.clone(),
            site: p.domain.clone(),
            operator_site: p.operator_site,
            terminable: p.terminable,
            orders,
            ..Default::default()
        };
        state.registry.declare_estate(&p.estate, e);
    }
}

impl SiteBook for Book {
    fn show(&self, estate: &str) -> Result<SiteShown, SiteBookRefusal> {
        Err(SiteBookRefusal::NotFound(format!(
            "{estate} is not in this fake"
        )))
    }
    fn put(&self, p: &SitePut) -> Result<SitePutDone, SiteBookRefusal> {
        self.puts.lock().unwrap().push(p.clone());
        if !self.slow.load(std::sync::atomic::Ordering::SeqCst) {
            self.take(p);
        }
        Ok(SitePutDone {
            words: format!("{} is added", p.estate),
            changed: Vec::new(),
        })
    }
}

/// The ONE box of the rig: `gunnar-rs` (its home, protected), `test2-gunnar-rs` (an operator
/// test site with media of its own, declared and never ordered) and `kunde-se` (a customer's
/// site, under the zone), with a site book when `book` is given.
fn rig(name: &str, book: Option<Book>) -> (Arc<AppState>, HoldPay) {
    let dir = tmp(name);
    let pay = HoldPay::default();
    let site = |vendor: &str, site: &str, operator_site: bool| crate::config::EstateConfig {
        product: "gunnar".into(),
        vendor: vendor.into(),
        site: site.into(),
        owner: "ignalina".into(),
        operator_site,
        ..Default::default()
    };
    let mut test2 = crate::config::EstateConfig {
        terminable: true,
        ..site("holdpay", "test2.gunnar.rs", true)
    };
    test2.orders.media.insert("dev".into(), "dev-media".into());
    test2.orders.term_days = Some(1);
    let mut reg = Registry::new()
        .sells("gunnar", pay.clone())
        .vendor(SitePay("sitepay-kunde"))
        .cloud(Arc::new(MockCloud::new()))
        .shared_product(shop_product())
        .home("gunnar", "gunnar-rs")
        .estate("gunnar-rs", site("holdpay", "gunnar.rs", true))
        .estate("test2-gunnar-rs", test2)
        .estate("kunde-se", site("sitepay-kunde", "kunde.gunnar.rs", false));
    if let Some(b) = &book {
        reg = reg.with_linked(crate::registry::Linked {
            site_book: Some(SiteBookLink(Arc::new(b.clone()))),
            ..Default::default()
        });
    }
    let (state, ..) = build_in(
        reg,
        &dir,
        EstateOrderConfig {
            vendor: String::new(),
            apex: String::new(),
            ..cfg()
        },
        FakeKit::new("PASS"),
        None,
        None,
    );
    if let Some(b) = book {
        let _ = b.state.set(state.clone());
    }
    (state, pay)
}

fn names(lines: &[standing::EstateLine]) -> Vec<(&str, &str)> {
    lines
        .iter()
        .map(|l| (l.name.as_str(), l.standing))
        .collect()
}

/// ★ RED: **the rows are the estates that exist**; the leftovers are the quiet line; a declared
/// site that was never ordered is no row and is the slot's to start.
#[test]
fn the_overview_lists_what_exists_and_hides_the_leftovers() {
    let (state, _pay) = rig("ovw-standing", None);
    let g = state.fleet.guardian().unwrap();
    // gunnar.rs runs (kit-installed: a fleet record whose main reports, no run here).
    g.store.declare("gunnar-rs", "gunnar").unwrap();
    g.store
        .record_report("gunnar-rs", "main", state.now(), &running("main"))
        .unwrap();
    // A signup's leftover: declared in the fleet's store, never installed (polka, ignalina …).
    g.store.declare("polka", "gunnar").unwrap();
    let o = standing::overview(&state);
    assert_eq!(
        names(&o.estates),
        [("gunnar-rs", "installed")],
        "only what exists is a row: {o:#?}"
    );
    assert!(
        o.estates[0].in_fleet && o.estates[0].domain == "gunnar.rs",
        "{o:#?}"
    );
    assert_eq!(names(&o.ended), [("polka", "never_installed")]);
    assert_eq!(
        o.ended[0].words,
        "never installed: no machine of it ever reported in"
    );
    assert_eq!(
        o.declared,
        ["test2-gunnar-rs"],
        "declared, never ordered: no row, startable by name (a customer's site never)"
    );
    assert_eq!(
        o.products,
        ["gunnar"],
        "the slot makes what a declared site with media of its own can be copied from"
    );

    // Ordered: a row being laid, before the fleet knows it.
    site::start_site(
        &state,
        "test2-gunnar-rs",
        "ops@example.invalid",
        &EstateSizes::default(),
    )
    .expect("ordered");
    let o = standing::overview(&state);
    assert_eq!(
        names(&o.estates),
        [("gunnar-rs", "installed"), ("test2-gunnar-rs", "laying")]
    );
    let t2 = &o.estates[1];
    assert_eq!(
        (
            t2.words.as_str(),
            t2.in_fleet,
            t2.domain.as_str(),
            t2.product.as_str()
        ),
        (
            "ordered: waiting for its payment",
            false,
            "test2.gunnar.rs",
            "gunnar"
        )
    );
    assert!(
        o.declared.is_empty(),
        "a site with a row is not offered to the slot: {o:#?}"
    );

    // Terminated (acme2, rickardtest, test3-holger-rs …): its fleet record stays, its boxes go.
    g.store.declare("test2-gunnar-rs", "gunnar").unwrap();
    {
        let _pen = state.estates.pen("test2gunnarrs");
        let mut run = state.estates.get("test2gunnarrs").unwrap();
        run.phase = phase::TERMINATED.into();
        run.history.push(Transition {
            phase: phase::TERMINATED.into(),
            at_ms: 1_793_059_200_000,
            note: "terminated by the operator".into(),
        });
        save(&state, &run).unwrap();
    }
    let o = standing::overview(&state);
    assert_eq!(names(&o.estates), [("gunnar-rs", "installed")]);
    assert_eq!(
        names(&o.ended),
        [("polka", "never_installed"), ("test2-gunnar-rs", "ended")]
    );
    assert_eq!(o.ended[1].words, "terminated on 2026-10-27");
    assert_eq!(
        o.declared,
        ["test2-gunnar-rs"],
        "an ended site is the slot's to start again"
    );
}

/// The operator's marker on a request, as the door's operator bearer carries it.
fn as_operator<T>(r: T) -> tonic::Request<T> {
    let mut req = tonic::Request::new(r);
    req.extensions_mut().insert(crate::auth::Operator);
    req
}

fn ask(name: &str, product: &str) -> pb::NewEstateRequest {
    pb::NewEstateRequest {
        name: name.into(),
        product: product.into(),
        email: "ops@example.invalid".into(),
        actor: "console:op-1".into(),
        ..Default::default()
    }
}

/// ★ RED: **the slot makes a new estate and hands it to the ONE site order** — copied from the
/// declared test site of its product into the site book, its domain from its name, then the
/// same order and payment page as the Sites page's; and the Overview shows it being laid.
#[test]
fn the_slot_makes_a_new_estate_and_hands_it_to_the_one_site_order() {
    let book = Book::default();
    let (state, pay) = rig("ovw-new", Some(book.clone()));
    let svc = EstatesService(state.clone());
    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    let made = rt
        .block_on(svc.new_estate(as_operator(ask("test4-gunnar-rs", "gunnar"))))
        .expect("made and ordered")
        .into_inner();
    assert!(made.created, "{made:?}");
    assert_eq!(
        (
            made.estate.as_str(),
            made.domain.as_str(),
            made.reference.as_str(),
            made.price_words.as_str()
        ),
        (
            "test4-gunnar-rs",
            "test4.gunnar.rs",
            "estate/test4gunnarrs",
            "0.50 EUR"
        )
    );
    assert!(
        made.checkout_url.starts_with("https://pay.example/"),
        "the one payment page: {made:?}"
    );
    assert!(
        made.words.contains("as test2-gunnar-rs's") && made.words.contains("pay 0.50 EUR"),
        "{}",
        made.words
    );
    let puts = book.puts.lock().unwrap().clone();
    assert_eq!(puts.len(), 1);
    let p = &puts[0];
    assert_eq!(
        (
            p.domain.as_str(),
            p.vendor.as_str(),
            p.first_admin.as_str(),
            p.media_from.as_str(),
            p.actor.as_str()
        ),
        (
            "test4.gunnar.rs",
            "holdpay",
            "ignalina",
            "test2-gunnar-rs",
            "console:op-1"
        )
    );
    assert!(
        p.create && p.operator_site && p.terminable && p.terms.term_days == 1,
        "{p:?}"
    );
    assert_eq!(
        pay.0.requests.lock().unwrap().len(),
        1,
        "one hold, through the site's own vendor"
    );
    let o = standing::overview(&state);
    assert!(
        o.estates.iter().any(|l| l.name == "test4-gunnar-rs"
            && l.standing == "laying"
            && l.domain == "test4.gunnar.rs"),
        "{o:#?}"
    );
    // The same name again: it exists, nothing more is made or held.
    let again = rt
        .block_on(svc.new_estate(as_operator(ask("test4-gunnar-rs", "gunnar"))))
        .unwrap_err();
    assert_eq!(again.code(), tonic::Code::AlreadyExists, "{again:?}");
    assert_eq!(
        (
            book.puts.lock().unwrap().len(),
            pay.0.requests.lock().unwrap().len()
        ),
        (1, 1)
    );
    // A declared site is ordered as it is: nothing is made.
    let declared = rt
        .block_on(svc.new_estate(as_operator(ask("test2-gunnar-rs", "gunnar"))))
        .expect("ordered")
        .into_inner();
    assert!(
        !declared.created
            && declared.domain == "test2.gunnar.rs"
            && declared.checkout_url.starts_with("https://"),
        "{declared:?}"
    );
    assert_eq!(
        book.puts.lock().unwrap().len(),
        1,
        "a declared site is not put again"
    );
}

/// ★ **OVW-5: the slot never holds the call for a box that takes the site late** (owner rule:
/// a non-trivial ask is a queued jera job, never one long blocking call). The call waited up to
/// 20 s for the box to take a site it added, then said `[new-estate-not-taken-yet]` and the
/// operator pressed again. Now, past a short look, a queued job owns the order: the call answers
/// at once that the estate is made, nothing is held yet, and once the box takes it the job
/// places the ONE site order (its payment page is on the Sites page).
#[test]
fn a_site_the_box_takes_late_is_ordered_by_a_queued_job_never_by_a_held_call() {
    let book = Book::default();
    book.slow.store(true, std::sync::atomic::Ordering::SeqCst);
    let (state, pay) = rig("ovw-late", Some(book.clone()));
    let svc = EstatesService(state.clone());
    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    let t0 = std::time::Instant::now();
    let made = rt
        .block_on(svc.new_estate(as_operator(ask("test4-gunnar-rs", "gunnar"))))
        .expect("made; the order is the queued job's")
        .into_inner();
    assert!(
        t0.elapsed() < std::time::Duration::from_secs(10),
        "the call is not held for the box's take: {:?}",
        t0.elapsed()
    );
    assert!(
        made.created
            && made.domain == "test4.gunnar.rs"
            && made.checkout_url.is_empty()
            && made.reference.is_empty(),
        "{made:?}"
    );
    assert!(
        made.words.contains("Sites page"),
        "where its payment page will be: {}",
        made.words
    );
    assert!(
        pay.0.requests.lock().unwrap().is_empty(),
        "nothing is held before the box took it"
    );
    // The box takes it: the queued job places the one site order.
    let put = book.puts.lock().unwrap()[0].clone();
    book.take(&put);
    let ordered = rt.block_on(async {
        for _ in 0..100 {
            if state.estates.get("test4gunnarrs").is_some() {
                return true;
            }
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
        }
        false
    });
    assert!(ordered, "the job ordered it once the box took it");
    assert_eq!(
        pay.0.requests.lock().unwrap().len(),
        1,
        "one hold, through the site's own vendor"
    );
    let o = standing::overview(&state);
    assert!(
        o.estates
            .iter()
            .any(|l| l.name == "test4-gunnar-rs" && l.standing == "laying"),
        "{o:#?}"
    );
}

/// ★ RED: **refused by name, before anything is kept or held**.
#[test]
fn the_slot_refuses_by_name_before_anything_is_kept_or_held() {
    let book = Book::default();
    let (state, pay) = rig("ovw-refused", Some(book.clone()));
    let svc = EstatesService(state.clone());
    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    let refused =
        |r: tonic::Request<pb::NewEstateRequest>| rt.block_on(svc.new_estate(r)).unwrap_err();
    // Not the operator: the service bearer, or a site's own bearer beside the marker.
    assert_eq!(
        refused(tonic::Request::new(ask("test4-gunnar-rs", "gunnar"))).code(),
        tonic::Code::PermissionDenied
    );
    let mut a_site = as_operator(ask("test4-gunnar-rs", "gunnar"));
    a_site
        .extensions_mut()
        .insert(crate::auth::CallerEstate("gunnar-rs".into()));
    assert_eq!(refused(a_site).code(), tonic::Code::PermissionDenied);
    for (r, code, named) in [
        (
            ask("Test4", "gunnar"),
            tonic::Code::InvalidArgument,
            "[new-estate-name]",
        ),
        (
            pb::NewEstateRequest {
                email: "nobody".into(),
                ..ask("test4-gunnar-rs", "gunnar")
            },
            tonic::Code::InvalidArgument,
            "[new-estate-email]",
        ),
        (
            ask("test4-holger-rs", "holger"),
            tonic::Code::InvalidArgument,
            "[new-estate-product]",
        ),
        (
            ask("gunnar-rs", "holger"),
            tonic::Code::InvalidArgument,
            "[new-estate-other-product]",
        ),
        (
            ask("kunde-se", "gunnar"),
            tonic::Code::FailedPrecondition,
            "[new-estate-customer-site]",
        ),
        (
            ask("kunde", "gunnar"),
            tonic::Code::AlreadyExists,
            "[new-estate-domain-taken]",
        ),
    ] {
        let s = refused(as_operator(r));
        assert_eq!(s.code(), code, "{named}: {s:?}");
        assert!(s.message().contains(named), "{named}: {}", s.message());
    }
    // An estate that exists is never laid over: one whose boxes call home (as its site stands).
    let g = state.fleet.guardian().unwrap();
    g.store.declare("old-one", "gunnar").unwrap();
    g.store
        .record_report("old-one", "main", state.now(), &running("main"))
        .unwrap();
    let s = refused(as_operator(ask("old-one", "gunnar")));
    assert!(
        s.code() == tonic::Code::AlreadyExists && s.message().contains("[new-estate-taken]"),
        "{s:?}"
    );
    assert!(
        book.puts.lock().unwrap().is_empty() && pay.0.requests.lock().unwrap().is_empty(),
        "nothing kept, nothing held"
    );
    // A box with no site book makes nothing new (a declared site is still ordered).
    let (bare, _) = rig("ovw-no-book", None);
    let s = rt
        .block_on(EstatesService(bare).new_estate(as_operator(ask("test4-gunnar-rs", "gunnar"))))
        .unwrap_err();
    assert!(
        s.code() == tonic::Code::FailedPrecondition && s.message().contains("[new-estate-no-book]"),
        "{s:?}"
    );
}

/// ★ The Overview over the wire: rows, the quiet line and the slot, behind the console's bearer.
#[test]
fn the_overview_answers_on_the_wire_behind_the_consoles_bearer() {
    let (state, _) = rig("ovw-wire", None);
    let g = state.fleet.guardian().unwrap();
    g.store.declare("gunnar-rs", "gunnar").unwrap();
    g.store
        .record_report("gunnar-rs", "main", state.now(), &running("main"))
        .unwrap();
    g.store.declare("acme2", "gunnar").unwrap();
    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    let o = rt
        .block_on(
            EstatesService(state).overview(tonic::Request::new(pb::EstatesOverviewRequest {})),
        )
        .expect("the console reads it")
        .into_inner();
    assert_eq!(
        o.estates
            .iter()
            .map(|e| (e.name.as_str(), e.standing.as_str()))
            .collect::<Vec<_>>(),
        [("gunnar-rs", "installed")]
    );
    assert_eq!(
        o.ended.iter().map(|e| e.name.as_str()).collect::<Vec<_>>(),
        ["acme2"]
    );
    let slot = o.slot.expect("the slot");
    assert_eq!(
        (slot.products, slot.declared),
        (
            vec!["gunnar".to_string()],
            vec!["test2-gunnar-rs".to_string()]
        )
    );
}