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

//! **The sentence a customer reads when the answer is YES.**
//!
//! `Refusal::customer_words` has given every REFUSAL a customer sentence since
//! 2026-09-14, and `QuoteResponse.words` carries it. There was no equivalent for
//! a grant, and there has to be, because of a policy the console cannot express
//! for itself:
//!
//! > **A purchase never buys cloud disk.** It raises the tenant's cap at once
//! > and is served from the free room; it only QUEUES an evaluation. Growth
//! > fires at 80 % used and sizes back to 65 %.
//!
//! So the customer must be told the raise has ALREADY happened, and must not be
//! led to expect a provisioning step that will never be reported to them.
//! gunnar-ui may not write that sentence — its standing rule is no price, no
//! currency, no size, no menu, and monetize's strings rendered verbatim — so it
//! lives in monetize's voice (`monetize::transaction::words`) and travels on
//! `StartPurchaseResponse.words`, the same way the refusal's does on the quote.
//!
//! What this file proves is the part no unit test on the string can: that the
//! sentence actually reaches the WIRE, on every path that ends in a grant and
//! on none that does not.

use std::sync::Arc;

use monetize::testing::{purchase, Answer, MockCloud, MockPay, NOW};
use monetize::transaction::{words, Outcome};
use monetize_proto::purchase_client::PurchaseClient;
use monetize_proto::{self as pb, PurchaseState};
use monetize_server::registry::Registry;
use monetize_server::state::AppState;
use monetize_server::testing::estate::FakeEstate;
use monetize_server::testing::products::TestProduct;
use monetize_server::testing::TestServer;
use tonic::metadata::MetadataValue;
use tonic::transport::Channel;
use tonic::Request;

const BEARER: &str = "test-bearer-token-of-sufficient-length";

struct Rig {
    server: TestServer,
    channel: Channel,
}

impl Rig {
    /// alice bought a period at [`NOW`] and holds 10 GiB — so an order for 100
    /// is a raise and a renewal has a period to extend.
    async fn up() -> Rig {
        Rig::up_with(|_| {}).await
    }

    /// The same, with the vendor configured by `tune` before it is registered —
    /// which is how a PENDING attempt is rehearsed (a vendor that mints a link
    /// and holds nothing until the customer uses it).
    async fn up_with(tune: impl FnOnce(&MockPay)) -> Rig {
        let cloud = MockCloud::new();
        cloud.answer_all(Answer::Provisioned);
        let vendor = MockPay::new();
        tune(&vendor);
        let mut state = AppState::in_memory(Registry::new().pulled_product(TestProduct::gunnar(), "gunnar.rs").sells("gunnar", vendor).cloud(cloud));
        FakeEstate::declare(&state, "gunnar", "gunnar.rs").buys_iron(&["pack_bytes"]);
        *state.terms.write().unwrap() = monetize_server::state::TermsStore::fixture();
        state.clock = Box::new(|| NOW);
        let state = Arc::new(state);
        let out = state
            .transaction(state.registry.products().next().unwrap(), monetize_pay::Vendor::of(&MockPay::new()))
            .run(&purchase("alice"))
            .expect("the first period must sell");
        assert!(matches!(out, Outcome::Paid { .. }), "{out:?}");
        let server = TestServer::spawn(state.clone(), BEARER).await;
        let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
        Rig { server, channel }
    }

    fn authed<T>(&self, req: T) -> Request<T> {
        let mut r = Request::new(req);
        r.metadata_mut().insert("authorization", MetadataValue::try_from(format!("Bearer {BEARER}")).unwrap());
        r
    }

    async fn start(&self, req: pb::StartPurchaseRequest) -> Result<pb::StartPurchaseResponse, tonic::Status> {
        PurchaseClient::new(self.channel.clone()).start(self.authed(req)).await.map(|r| r.into_inner())
    }

    fn order(gib: u64) -> pb::StartPurchaseRequest {
        pb::StartPurchaseRequest {
            product: "gunnar".into(),
            tenant: "alice".into(),
            caps: [("pack_bytes".to_string(), gib << 30)].into(),
            vendor: "mock".into(),
            payer: String::new(),
            starts_unix_ms: NOW,
            market: 0,
            renew: false,
            actor_ticket: Vec::new(),
            bundle: String::new(),
            users: None,
        }
    }

    fn renewal() -> pb::StartPurchaseRequest {
        pb::StartPurchaseRequest {
            product: "gunnar".into(),
            tenant: "alice".into(),
            caps: Default::default(),
            vendor: "mock".into(),
            payer: String::new(),
            starts_unix_ms: 0,
            market: 0,
            renew: true,
            actor_ticket: Vec::new(),
            bundle: String::new(),
            users: None,
        }
    }
}

/// ★ **A GRANTED ORDER SAYS THE ROOM IS THERE NOW.**
///
/// The sentence is monetize's, byte for byte — a console that composed its own
/// would be inventing the one thing it cannot know: that the cap moved before
/// the response was written and that nothing is being provisioned on the
/// customer's behalf.
#[tokio::test]
async fn a_granted_order_carries_monetizes_own_sentence() {
    let rig = Rig::up().await;

    let out = rig.start(Rig::order(100)).await.expect("the order must sell");

    assert_eq!(out.state, PurchaseState::Paid as i32, "{}", out.reason);
    assert_eq!(out.words, words::ORDER_GRANTED, "the granted sentence must reach the wire verbatim");
    rig.server.stop().await;
}

/// **A granted RENEWAL says the subscription is extended and claims no new
/// room.** A renewal sells an empty delta; a sentence that told the customer
/// their limit had gone up would be a plain untruth about their money.
#[tokio::test]
async fn a_granted_renewal_claims_no_new_room() {
    let rig = Rig::up().await;

    let out = rig.start(Rig::renewal()).await.expect("the renewal must sell");

    assert_eq!(out.state, PurchaseState::Paid as i32, "{}", out.reason);
    assert_eq!(out.words, words::RENEWAL_GRANTED);
    assert_ne!(out.words, words::ORDER_GRANTED);
    rig.server.stop().await;
}

/// ★ **THE RETRY PATH CARRIES IT TOO** — and it is the one an implementation
/// forgets.
///
/// An order already filled answers PAID from `already_filled` without running
/// a transaction at all, so it is a second place the response is built. A
/// customer whose first request timed out after the money moved is exactly the
/// customer who most needs to be told the room is already theirs.
#[tokio::test]
async fn an_order_already_filled_still_tells_the_customer_the_room_is_theirs() {
    let rig = Rig::up().await;
    let first = rig.start(Rig::order(100)).await.expect("the order must sell");
    assert_eq!(first.state, PurchaseState::Paid as i32, "{}", first.reason);

    let again = rig.start(Rig::order(100)).await.expect("a retry answers the attempt that filled it");

    assert_eq!(again.reference, first.reference, "the same attempt, not a second sale");
    assert_eq!(again.words, words::ORDER_GRANTED, "the retry is a grant and must say so");
    rig.server.stop().await;
}

/// **An attempt that is not a grant carries no granted sentence.**
///
/// The vendor mints a link and holds nothing until the customer uses it, so the
/// answer is PENDING: nothing has been raised, nothing is theirs yet, and a
/// sentence saying otherwise would be a lie told at the worst moment. Empty,
/// and the console falls back to `reason` exactly as before.
#[tokio::test]
async fn a_pending_attempt_carries_no_granted_sentence() {
    let rig = Rig::up_with(|pay| pay.awaits_the_customer()).await;

    let out = rig.start(Rig::order(100)).await.expect("the order reserves");

    assert_eq!(out.state, PurchaseState::Pending as i32, "{}", out.reason);
    assert!(out.words.is_empty(), "a pending attempt granted nothing: {:?}", out.words);
    rig.server.stop().await;
}