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

//! **The operator terminates a SOLD private estate** (owner 2026-10-22): `monetize site
//! terminate polka --confirm polka` over the operator door was refused "a private Gunnar is
//! bought, read and changed by its gunnar.rs account" (Unauthenticated). The operator of the
//! ONE box may end a customer's private estate too, by the SAME path as the buyer's own:
//!
//! * the operator bearer (no site's own bearer beside it) needs no account ticket;
//! * the name is still typed back, and a protected site is still refused;
//! * no backup is offered to the operator (the wire carries no backup word, A32-27);
//! * the run's history says "terminated by the operator";
//! * the money rules are unchanged: nothing is captured and nothing is refunded.

use super::*;
use crate::services::estate_order::EstateOrderService;
use monetize_proto as pb;
use monetize_proto::estate_order_server::EstateOrder as _;

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(confirm: &str) -> pb::EstateOrderTerminateRequest {
    pb::EstateOrderTerminateRequest {
        company: "acme".into(),
        actor_ticket: Vec::new(),
        confirm: confirm.into(),
    }
}

/// ★ **The operator ends a sold estate** with no account ticket: every machine and disk given
/// back, the run terminated, its history saying the operator did it, and no money moved.
#[test]
fn the_operator_terminates_a_sold_estate_by_the_buyers_path() {
    let r = delivered_acme("op-term-sold");
    assert!(
        !r.state.estates.get("acme").unwrap().site_order,
        "a customer's sold estate"
    );
    assert!(r.cloud.live_under("estate/acme") > 0);
    let (captures, refunds) = (r.pay.captures(), r.pay.refunds());
    let svc = EstateOrderService(r.state.clone());
    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    // No backup word: the operator is not offered one, the estate is ended without it.
    let t = rt
        .block_on(svc.terminate(as_operator(ask("acme"))))
        .expect("the operator terminates a sold estate")
        .into_inner();
    assert!(!t.done && t.words.contains("being ended now"), "{t:?}");
    crate::estate_order::drive(&r.state, "acme");
    assert_eq!(
        r.cloud.live_under("estate/acme"),
        0,
        "every machine and disk is given back"
    );
    let run = r.state.estates.get("acme").unwrap();
    assert_eq!(run.phase, phase::TERMINATED, "{:#?}", run.history);
    assert!(
        run.history
            .iter()
            .any(|t| t.phase == phase::TERMINATING && t.note.contains("by the operator")),
        "{:#?}",
        run.history
    );
    assert!(
        run.history
            .iter()
            .any(|t| t.phase == phase::TERMINATED && t.note == "terminated by the operator"),
        "{:#?}",
        run.history
    );
    assert!(
        !run.history
            .iter()
            .any(|t| t.note.contains("by its account") || t.note.contains("the account chose")),
        "{:#?}",
        run.history
    );
    assert_eq!(
        (r.pay.captures(), r.pay.refunds()),
        (captures, refunds),
        "no money moved: nothing captured, nothing refunded"
    );
}

/// ★ **Without the name typed back nothing ends**, for the operator too; and nobody without
/// the operator marker (or a site's own bearer beside it) is heard without the account.
#[test]
fn the_operator_is_refused_without_the_name_typed_back_and_others_without_the_account() {
    let r = delivered_acme("op-term-refused");
    let before = r.cloud.live_under("estate/acme");
    let svc = EstateOrderService(r.state.clone());
    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    for typed in ["", "acne", "polka"] {
        let e = rt
            .block_on(svc.terminate(as_operator(ask(typed))))
            .unwrap_err();
        assert_eq!(e.code(), tonic::Code::InvalidArgument, "{typed:?}: {e:?}");
    }
    let plain = rt
        .block_on(svc.terminate(tonic::Request::new(ask("acme"))))
        .unwrap_err();
    assert_eq!(plain.code(), tonic::Code::Unauthenticated, "{plain:?}");
    let mut a_site = as_operator(ask("acme"));
    a_site
        .extensions_mut()
        .insert(crate::auth::CallerEstate("gunnar-rs".into()));
    assert_eq!(
        rt.block_on(svc.terminate(a_site)).unwrap_err().code(),
        tonic::Code::Unauthenticated
    );
    assert_eq!(
        r.cloud.live_under("estate/acme"),
        before,
        "every machine and disk stands"
    );
    assert_eq!(r.state.estates.get("acme").unwrap().phase, phase::CAPTURED);
}

/// ★ **A protected site stays refused** for the operator (gunnar.rs).
#[test]
fn a_protected_site_stays_refused_for_the_operator() {
    let (state, ..) = operator_rig("op-term-protected", cfg());
    let svc = EstateOrderService(state.clone());
    let rt = tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();
    for name in ["gunnar.rs", "gunnar-rs"] {
        let req = pb::EstateOrderTerminateRequest {
            company: name.into(),
            actor_ticket: Vec::new(),
            confirm: name.into(),
        };
        let e = rt.block_on(svc.terminate(as_operator(req))).unwrap_err();
        assert_eq!(e.code(), tonic::Code::FailedPrecondition, "{name}: {e:?}");
        assert!(e.message().contains("protected"), "{e:?}");
    }
}