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

//! **The operator's TEST BUTTON over its real wire** (owner 2026-10-11): a
//! product declares its test actions, the operator presses one, and monetize
//! makes a REAL account through the very path the sign-up page takes — the
//! name, the address, the plan, the box's room, the sold capacity, the vendor —
//! with the vendor's test payment method instead of a person at a card form.
//!
//! The private-instance half (account, order, lay, install, capture, the setup's
//! lines) is proven in `estate_order::tests`, where the estate rig lives.

use std::sync::Arc;

use monetize_pay::Currency;
use monetize_proto::test_purchase_client::TestPurchaseClient;
use monetize_proto::{self as pb};
use monetize_server::config::SignupPlan;
use monetize_server::registry::Registry;
use monetize_server::state::AppState;
use monetize_server::testing::{CallbackPay, TestServer};
use tonic::metadata::MetadataValue;
use tonic::transport::Channel;
use tonic::{Code, Request};

const BEARER: &str = "test-bearer-token-of-sufficient-length";
const OPERATOR: &str = "operator-bearer-token-of-sufficient-length";
const SECRET: &str = "mockpay-shared-secret";
const PLAN: &str = "gunnar/10gb-1m";
const GIB: u64 = 1 << 30;

/// A sink that makes the account it is asked for, unless the name is held.
#[derive(Default)]
struct Makes(std::sync::Mutex<Vec<String>>);
impl monetize_pay::SignupSink for Makes {
    fn paid(&self, s: &monetize_pay::PaidSignup) -> Result<monetize_pay::SignupOutcome, monetize_pay::SignupError> {
        self.0.lock().unwrap().push(s.username.clone());
        Ok(monetize_pay::SignupOutcome::Created { namespace: s.username.clone(), username: s.username.clone() })
    }
    fn name_verdict(&self, _: &str) -> Result<monetize_pay::NameVerdict, monetize_pay::SignupError> {
        Ok(monetize_pay::NameVerdict::Free)
    }
}

struct Rig {
    pay: CallbackPay,
    product: Arc<monetize::testing::MockProduct>,
    state: Arc<AppState>,
    server: TestServer,
    channel: Channel,
    sink: Arc<Makes>,
}

impl Rig {
    async fn up(livemode: Option<bool>) -> Rig {
        let pay = CallbackPay::with_checkout(SECRET, "https://checkout.example/");
        pay.set_livemode(livemode);
        let product = Arc::new(monetize::testing::MockProduct::gunnar());
        *product.test_actions.lock().unwrap() = vec![monetize_product::TestAction {
            id: "hosted-10gb".into(),
            label: "Test: 10 GB hosted".into(),
            kind: monetize_product::TestActionKind::HostedPlan { cap_gib: 10 },
        }];
        let mut state = AppState::in_memory(Registry::new().sells("gunnar", pay.clone()).shared_product(product.clone()));
        state.signup_plans = [(PLAN.to_string(), SignupPlan { amount_minor: 10_000, currency: Currency::Sek, cap_bytes: 10 * GIB, period_days: 30 })].into();
        let sink = Arc::new(Makes::default());
        state.signup_sink = sink.clone();
        let state = Arc::new(state);
        let server = TestServer::spawn_with_operator(state.clone(), BEARER, OPERATOR).await;
        let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
        Rig { pay, product, state, server, channel, sink }
    }

    fn client(&self, bearer: &str) -> TestPurchaseClient<tonic::service::interceptor::InterceptedService<Channel, impl tonic::service::Interceptor>> {
        let v: MetadataValue<_> = format!("Bearer {bearer}").parse().unwrap();
        TestPurchaseClient::with_interceptor(self.channel.clone(), move |mut r: Request<()>| {
            r.metadata_mut().insert("authorization", v.clone());
            Ok(r)
        })
    }
}

fn press() -> pb::TestPurchaseStartRequest {
    pb::TestPurchaseStartRequest { product: "gunnar".into(), action: "hosted-10gb".into(), ..Default::default() }
}

/// ★ **The operator's, and nobody else's.** The service bearer a product's
/// console holds is refused on every verb with PERMISSION_DENIED; no bearer
/// at all is UNAUTHENTICATED; nothing was bought either way.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn the_test_button_refuses_everyone_but_the_operator() {
    let rig = Rig::up(Some(false)).await;
    let mut service = rig.client(BEARER);
    for refused in [
        service.actions(pb::TestActionsRequest::default()).await.map(|_| ()).expect_err("actions"),
        service.start(press()).await.map(|_| ()).expect_err("start"),
        service.list(pb::TestPurchaseListRequest::default()).await.map(|_| ()).expect_err("list"),
        service.status(pb::TestPurchaseStatusRequest { id: "tp-1".into() }).await.map(|_| ()).expect_err("status"),
    ] {
        assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
    }
    let naked = TestPurchaseClient::new(rig.channel.clone()).start(press()).await.expect_err("no bearer");
    assert_eq!(naked.code(), Code::Unauthenticated, "{naked:?}");
    assert!(rig.pay.test_paid().is_empty() && rig.state.signups.list(0).unwrap().is_empty(), "nothing was bought");
    rig.server.stop().await;
}

/// ★ **A key that cannot say whether it is live is refused** BEFORE anything is
/// made: no account, no checkout, no payment, no run — and the console is told
/// why on the button. (A LIVE key runs the real flow: see below.)
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_silent_key_is_refused_before_anything_is_made() {
    for mode in [None] {
        let rig = Rig::up(mode).await;
        let mut op = rig.client(OPERATOR);
        let offers = op.actions(pb::TestActionsRequest::default()).await.unwrap().into_inner().actions;
        assert_eq!(offers.len(), 1);
        assert!(!offers[0].runnable && offers[0].why_not.contains("not a TEST key"), "{mode:?}: {offers:?}");
        let refused = op.start(press()).await.expect_err("refused");
        assert_eq!(refused.code(), Code::FailedPrecondition, "{mode:?}: {refused:?}");
        assert!(refused.message().contains("never spends live money"), "{}", refused.message());
        assert!(rig.pay.test_paid().is_empty() && rig.pay.signup_orders().is_empty(), "{mode:?}: the vendor was asked");
        assert!(rig.state.signups.list(0).unwrap().is_empty() && rig.sink.0.lock().unwrap().is_empty(), "{mode:?}: an account was made");
        assert!(rig.product.absorb_asked().is_empty(), "{mode:?}: the box was asked before the key was");
        assert!(op.list(pb::TestPurchaseListRequest::default()).await.unwrap().into_inner().runs.is_empty());
        rig.server.stop().await;
    }
}

/// ★ **Same path as the sign-up page.** On a test key the press makes a real
/// account: the box is asked for the plan's room exactly as a signup asks
/// (the brand-new-tenant question), the plan is the one the page offers for
/// 10 GiB, the vendor is handed the very order a checkout would carry — a
/// `test-<stamp>` name, an address nobody owns — and pays it with its test
/// payment method; the paid signup is settled through the one settle path,
/// so the store reads it CREATED. The run is kept and read back with its lines.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_press_on_a_test_key_makes_a_real_account_through_the_signup_path() {
    let rig = Rig::up(Some(false)).await;
    let mut op = rig.client(OPERATOR);
    let offers = op.actions(pb::TestActionsRequest { product: "gunnar".into() }).await.unwrap().into_inner().actions;
    assert_eq!(offers.iter().map(|o| (o.id.as_str(), o.label.as_str(), o.kind.as_str(), o.cap_gib, o.runnable)).collect::<Vec<_>>(), [("hosted-10gb", "Test: 10 GB hosted", "hosted_plan", 10, true)]);

    let run = op.start(press()).await.expect("pressed").into_inner();
    assert_eq!((run.state.as_str(), run.signup_state.as_str()), ("done", "CREATED"), "{run:?}");
    assert!(run.username.starts_with("test-"), "{}", run.username);
    assert_eq!(run.vendor, "mockpay");
    assert!(run.mailbox.is_empty(), "no mailbox configured: nobody is mailed");

    // The sign-up path's own gates ran: the box was asked the new-tenant question for the plan's room.
    assert_eq!(rig.product.absorb_asked().len(), 1, "the capacity gate is the signup's");
    assert_eq!(rig.product.absorb_asked()[0].get("pack_bytes"), Some(&(10 * GIB)));
    // The vendor was handed the order a checkout carries, and paid it with test money.
    let paid = rig.pay.test_paid();
    assert_eq!(paid.len(), 1);
    assert_eq!((paid[0].username.as_str(), paid[0].plan.as_str(), paid[0].product.as_str()), (run.username.as_str(), PLAN, "gunnar"));
    assert!(paid[0].email.ends_with("@test-purchase.invalid"), "never a real address: {}", paid[0].email);
    assert_eq!(paid[0].amount.minor, 10_000, "the plan's NET price, as a checkout asks");
    assert!(rig.pay.signup_orders().is_empty(), "no hosted page was minted for nobody to open");
    // ★ A33-2: the test card is the only difference — the paid session is recorded as minted
    // HERE (ST-7, as a buyer's checkout is), its welcome kept, and it settles on the one path.
    assert!(rig.state.signups.minted("mockpay", &run.session).unwrap().is_some(), "the press's session is recorded as this box's own, as a buyer's is");
    // The account exists, made by the product's sink, and the store says so.
    assert_eq!(*rig.sink.0.lock().unwrap(), std::slice::from_ref(&run.username));
    let rows = rig.state.signups.list(0).unwrap();
    assert_eq!(rows.len(), 1);
    assert!(matches!(rows[0].state, monetize_server::signup::SignupState::Created { .. }), "{:?}", rows[0].state);

    // Kept, listed newest first, read back with its lines.
    let listed = op.list(pb::TestPurchaseListRequest::default()).await.unwrap().into_inner().runs;
    assert_eq!(listed.iter().map(|r| r.id.as_str()).collect::<Vec<_>>(), [run.id.as_str()]);
    let again = op.status(pb::TestPurchaseStatusRequest { id: run.id.clone() }).await.unwrap().into_inner();
    assert_eq!(again.state, "done");
    assert!(again.lines.iter().any(|l| l.source == "vendor" && l.line.contains("test payment")), "{:?}", again.lines);
    rig.server.stop().await;
}

/// ★ **The sign-up path's refusals are the button's**: a full box refuses the
/// press exactly as it refuses a stranger's signup — before the vendor.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_full_box_refuses_the_press_as_it_refuses_a_signup() {
    let rig = Rig::up(Some(false)).await;
    *rig.product.refuse_absorb.lock().unwrap() = Some("gunnar: the data set is full".into());
    let run = rig.client(OPERATOR).start(press()).await.expect("a refused run is still a run").into_inner();
    assert_eq!(run.state, "failed", "{run:?}");
    assert!(run.words.to_lowercase().contains("nothing has been charged"), "the signup's own words: {}", run.words);
    assert!(rig.pay.test_paid().is_empty(), "the vendor was asked for room that does not exist");
    rig.server.stop().await;
}

/// ★ ST-8 (owner 2026-10-11: "test2 money = real purchase, the owner pays at the card
/// step"): **on a LIVE key the button runs the SAME real flow** — a real checkout through
/// the sign-up path, no test card, no fake settle — and hands the operator the vendor's
/// own checkout page to pay himself. The offer says so; a press without the operator's
/// "LIVE — you will be charged" is refused before anything is made.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn on_a_live_key_the_button_runs_the_real_flow_and_hands_the_operator_the_checkout() {
    let rig = Rig::up(Some(true)).await;
    let mut op = rig.client(OPERATOR);
    let offers = op.actions(pb::TestActionsRequest::default()).await.unwrap().into_inner().actions;
    assert_eq!(offers.len(), 1);
    assert!(offers[0].runnable && offers[0].live && offers[0].why_not.is_empty(), "{offers:?}");

    let unconfirmed = op.start(press()).await.expect_err("not confirmed");
    assert_eq!(unconfirmed.code(), Code::FailedPrecondition, "{unconfirmed:?}");
    assert!(unconfirmed.message().contains("LIVE — you will be charged"), "{}", unconfirmed.message());
    assert!(rig.pay.signup_orders().is_empty() && rig.state.signups.list(0).unwrap().is_empty(), "nothing was started");

    let run = op.start(pb::TestPurchaseStartRequest { live: true, ..press() }).await.expect("pressed, confirmed").into_inner();
    assert!(run.live, "{run:?}");
    assert!(run.pay_url.starts_with("https://checkout.example/"), "the vendor's own checkout page: {run:?}");
    assert_eq!(run.state, "running", "waiting for the operator's payment: {run:?}");
    assert!(run.words.contains("LIVE — you will be charged"), "{}", run.words);
    assert!(rig.pay.test_paid().is_empty(), "no test card on a live key");
    let orders = rig.pay.signup_orders();
    assert_eq!(orders.len(), 1, "one real checkout");
    assert_eq!((orders[0].username.as_str(), orders[0].plan.as_str()), (run.username.as_str(), PLAN));
    assert!(rig.sink.0.lock().unwrap().is_empty(), "no fake settle: the account is made when the vendor says it is paid");
    rig.server.stop().await;
}

/// ★ ST-8: **each offer is for ONE site, and a press sells as that site only.** A product
/// sold for two sites offers each action once per site (its estate and host); a press names
/// the site, its return page is on that site's host, its account is made on that site's
/// estate; a press naming no site (or one not declared) is refused before anything is made.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn each_site_has_its_own_button_and_never_sells_as_the_other() {
    let pay = CallbackPay::with_checkout(SECRET, "https://checkout.example/");
    pay.set_livemode(Some(false));
    let product = Arc::new(monetize::testing::MockProduct::gunnar());
    *product.test_actions.lock().unwrap() = vec![monetize_product::TestAction { id: "hosted-10gb".into(), label: "Test: 10 GB hosted".into(), kind: monetize_product::TestActionKind::HostedPlan { cap_gib: 10 } }];
    let site = |s: &str| monetize_server::config::EstateConfig { product: "gunnar".into(), vendor: "mockpay".into(), owner: "ignalina".into(), site: s.into(), appliance_key: String::new(), ..Default::default() };
    let reg = Registry::new().sells("gunnar", pay.clone()).shared_product(product.clone()).home("gunnar", "gunnar-rs").estate("gunnar-rs", site("gunnar.rs")).estate("test2-gunnar-rs", site("test2.gunnar.rs"));
    let mut state = AppState::in_memory(reg);
    state.signup_plans = [(PLAN.to_string(), SignupPlan { amount_minor: 10_000, currency: Currency::Sek, cap_bytes: 10 * GIB, period_days: 30 })].into();
    let sink = Arc::new(Makes::default());
    state.signup_sink = sink.clone();
    let state = Arc::new(state);
    let server = TestServer::spawn_with_operator(state.clone(), BEARER, OPERATOR).await;
    let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
    let v: MetadataValue<_> = format!("Bearer {OPERATOR}").parse().unwrap();
    let mut op = TestPurchaseClient::with_interceptor(channel, move |mut r: Request<()>| {
        r.metadata_mut().insert("authorization", v.clone());
        Ok(r)
    });

    let offers = op.actions(pb::TestActionsRequest::default()).await.unwrap().into_inner().actions;
    let mut sites: Vec<(String, String)> = offers.iter().map(|o| (o.site.clone(), o.site_host.clone())).collect();
    sites.sort();
    assert_eq!(sites, [("gunnar-rs".to_string(), "gunnar.rs".to_string()), ("test2-gunnar-rs".to_string(), "test2.gunnar.rs".to_string())], "{offers:?}");
    assert!(offers.iter().all(|o| o.runnable && !o.live), "{offers:?}");

    let unnamed = op.start(press()).await.expect_err("which site?");
    assert_eq!(unnamed.code(), Code::FailedPrecondition, "{unnamed:?}");
    assert!(unnamed.message().contains("[test-site-unnamed]"), "{}", unnamed.message());
    let undeclared = op.start(pb::TestPurchaseStartRequest { site: "elsewhere".into(), ..press() }).await.expect_err("not a site of gunnar");
    assert_eq!(undeclared.code(), Code::FailedPrecondition, "{undeclared:?}");
    assert!(pay.test_paid().is_empty() && state.signups.list(0).unwrap().is_empty(), "nothing was bought for no site");

    let run = op.start(pb::TestPurchaseStartRequest { site: "test2-gunnar-rs".into(), ..press() }).await.expect("test2's button").into_inner();
    assert_eq!((run.state.as_str(), run.site.as_str()), ("done", "test2-gunnar-rs"), "{run:?}");
    let paid = pay.test_paid();
    assert_eq!(paid.len(), 1);
    assert!(paid[0].success_url.starts_with("https://test2.gunnar.rs/welcome?session="), "test2's own return page: {}", paid[0].success_url);
    let rows = state.signups.list(0).unwrap();
    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].estate.as_deref(), Some("test2-gunnar-rs"), "made on test2, never as gunnar.rs");
    server.stop().await;
}