Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-site-buy/tests/checked_tickets_on_the_shop.rs

//! ★ **Both sites hold their grow doors to a checked, unspent ticket** (owner decision
//! 2026-10-26, item 1; A32-9: every site, there is no knob any more).
//!
//! A Gunnar-shaped and a Holger-shaped [`TestSite`] over the crate's fake ONE box: where the box
//! checks no tenant ticket ([`FakeOneBox::checks_no_tickets`]), the window, the grow, the status poll and the cancel
//! answer 501 with the buyer's words and no `Purchase` call is made; where it checks them
//! ([`FakeOneBox::verify_tickets`]), a fresh ticket grows the account and the same ticket again
//! is spent. The fake's own `Products.List` and spent rule are tested here too. Addresses are
//! `*.mock.invalid`.

mod sites;

use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

use axum::http::StatusCode;
use monetize_proto as pb;
use monetize_site_buy::relay::RENEWAL_NOT_VERIFIED;
use monetize_site_buy::testkit::{FakeOneBox, BUNDLE, SPENT};
use monetize_site_buy::Relay;
use serde_json::json;
use sites::{Kit, Shape, TestSite, BEARER};

for_both_sites!(
    a_grow_door_refuses_where_the_shop_checks_no_ticket,
    a_checked_unspent_ticket_grows_the_account_and_is_then_spent
);

async fn kit(shape: Shape, verifies: bool) -> Kit {
    let fake = FakeOneBox::spawn(shape.product(), BEARER).await;
    if verifies {
        fake.verify_tickets();
    } else {
        fake.checks_no_tickets();
    }
    fake.add_account("x14", "x14@mock.invalid", &[("disk_bytes", 5 << 30)]);
    let link = fake.link();
    let mut k = Kit::over(shape, fake, link, None);
    // Each ticket with a fresh nonce, as the real sites mint them: the box spends each once.
    let nonce = Arc::new(AtomicU64::new(0));
    let mint = move |b: &monetize_site_buy::Buyer,
                     purpose: &str,
                     _: &std::collections::BTreeMap<String, u64>| {
        format!(
            "{}:{purpose}:{}",
            b.account,
            nonce.fetch_add(1, Ordering::SeqCst)
        )
        .into_bytes()
    };
    k.site = TestSite::over(shape, &k.fake).with_minter(Arc::new(mint));
    k.relay = Arc::new(Relay::new(k.site.clone(), k.fake.link()));
    k
}

async fn a_grow_door_refuses_where_the_shop_checks_no_ticket(shape: Shape) {
    let k = kit(shape, false).await;
    for (method, uri, body) in [
        ("GET", "/api/account/subscription/bundles", None),
        (
            "POST",
            "/api/account/subscription/buy-bundle",
            Some(json!({"bundle": BUNDLE})),
        ),
        (
            "GET",
            "/api/account/subscription/status?reference=p-1",
            None,
        ),
        (
            "POST",
            "/api/account/subscription/cancel",
            Some(json!({"reference": "p-1"})),
        ),
    ] {
        let r = k.ask(method, uri, Some("x14"), body).await;
        assert_eq!(
            (r.status, r.error()),
            (StatusCode::NOT_IMPLEMENTED, RENEWAL_NOT_VERIFIED),
            "{shape:?} {method} {uri}: {:?}",
            r.body
        );
    }
    let b = k.fake.book();
    assert!(
        b.calls.iter().all(|c| !c.starts_with("Purchase.")),
        "{shape:?}: no order reached the shop: {:?}",
        b.calls
    );
    assert!(b.checkouts.is_empty());
}

async fn a_checked_unspent_ticket_grows_the_account_and_is_then_spent(shape: Shape) {
    let k = kit(shape, true).await;
    let window = k
        .ask(
            "GET",
            "/api/account/subscription/bundles",
            Some("x14"),
            None,
        )
        .await;
    assert_eq!(
        window.status,
        StatusCode::OK,
        "{shape:?}: {:?}",
        window.body
    );
    let buy = k
        .ask(
            "POST",
            "/api/account/subscription/buy-bundle",
            Some("x14"),
            Some(json!({"bundle": BUNDLE})),
        )
        .await;
    assert_eq!(
        buy.status,
        StatusCode::OK,
        "{shape:?}: a fresh ticket is honoured: {:?}",
        buy.body
    );
    assert_eq!(k.fake.book().checkouts_of("bundle").len(), 1);
    let reference = buy.body["reference"].as_str().unwrap().to_owned();
    let done = k
        .ask(
            "GET",
            &format!("/api/account/subscription/status?reference={reference}"),
            Some("x14"),
            None,
        )
        .await;
    assert_eq!(
        done.status,
        StatusCode::OK,
        "{shape:?}: each poll carries a fresh ticket: {:?}",
        done.body
    );
    // The order's ticket presented again is spent: the box buys nothing more for it.
    let order = k
        .fake
        .book()
        .tickets
        .iter()
        .find(|(c, _)| c == "Purchase.Start")
        .unwrap()
        .1
        .clone();
    let again = k
        .fake
        .link()
        .purchase()
        .start(pb::StartPurchaseRequest {
            product: shape.product().into(),
            tenant: "x14".into(),
            bundle: BUNDLE.into(),
            actor_ticket: order,
            ..Default::default()
        })
        .await;
    let refused = again.expect_err("a spent ticket");
    assert_eq!(
        (refused.code(), refused.message()),
        (tonic::Code::PermissionDenied, SPENT),
        "{shape:?}"
    );
    assert_eq!(
        k.fake.book().checkouts_of("bundle").len(),
        1,
        "{shape:?}: still one Stripe page"
    );
}

/// The fake's `Products.List` says whether it checks tickets (as spawned it says so, as a
/// configured ONE box does), and once it verifies them its `Purchase` doors honour a ticket once.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_fake_one_box_says_whether_it_checks_tickets_and_spends_each_once() {
    let fake = FakeOneBox::spawn("holger", BEARER).await;
    let products = |fake: &FakeOneBox| {
        let link = fake.link();
        async move {
            link.products()
                .list(pb::ListProductsRequest {})
                .await
                .unwrap()
                .into_inner()
                .products
        }
    };
    let p = products(&fake).await;
    assert_eq!(
        (p.len(), p[0].id.as_str(), p[0].tenant_renewal),
        (1, "holger", true),
        "as spawned it says it checks them"
    );
    let start = |ticket: &'static [u8]| {
        let link = fake.link();
        async move {
            link.purchase()
                .start(pb::StartPurchaseRequest {
                    product: "holger".into(),
                    tenant: "x14".into(),
                    bundle: BUNDLE.into(),
                    actor_ticket: ticket.to_vec(),
                    ..Default::default()
                })
                .await
        }
    };
    assert!(
        start(b"t1").await.is_ok() && start(b"t1").await.is_ok(),
        "as spawned it spends none"
    );
    fake.checks_no_tickets();
    assert!(
        !products(&fake).await[0].tenant_renewal,
        "a shop older than the ticket"
    );
    fake.verify_tickets();
    assert!(start(b"t2").await.is_ok(), "a fresh ticket is honoured");
    let spent = start(b"t2").await.expect_err("the same ticket again");
    assert_eq!(
        (spent.code(), spent.message()),
        (tonic::Code::PermissionDenied, SPENT)
    );
}