crates/monetize-server/tests/paid_signup.rs
//! **The paid-signup seam over its real wires**: the vendor's HTTP webhook into
//! `POST /callback/mockpay`, and the return page's `Signup.Status` over gRPC
//! with a bearer, against the same in-memory store.
//!
//! The unit half — the store's idempotency, each named refusal, the retry and
//! the 24 h ceiling — is in `monetize_server::signup::tests`. This file proves
//! the two ends meet: that a verified signup never touches the ledger, that the
//! vendor is answered **200 in every verified case** (a 5xx buys days of Stripe
//! retries and then a disabled endpoint), and that a page polling before the
//! webhook lands gets `UNKNOWN` rather than an error it would have to
//! special-case.
use std::sync::Arc;
use monetize_pay::{Currency, Money, PaidSignup};
use monetize_proto::signup_admin_client::SignupAdminClient;
use monetize_proto::signup_client::SignupClient;
use monetize_proto::signup_status_response::State;
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::{http_post, CallbackPay, TestServer};
use tonic::metadata::MetadataValue;
use tonic::transport::Channel;
use tonic::{Code, Request};
const BEARER: &str = "test-bearer-token-of-sufficient-length";
const SECRET: &str = "mockpay-shared-secret";
const PLAN: &str = "gunnar/10gb-1m";
struct Rig {
pay: CallbackPay,
state: Arc<AppState>,
server: TestServer,
channel: Channel,
/// The selling rigs' sink, for the mail it was handed.
sink: Option<Arc<HoldingSink>>,
}
impl Rig {
async fn up() -> Rig {
let pay = CallbackPay::new(SECRET);
let mut state = AppState::in_memory(Registry::new().sells("gunnar", pay.clone()));
// The plan table an operator wrote in `monetize.toml`. The open build's
// `LoudSink` is the default sink and stays: this file is about the
// wires, and the sink's own answers are the unit tests' business.
state.signup_plans = [(
PLAN.to_string(),
SignupPlan { amount_minor: 10_000, currency: Currency::Sek, cap_bytes: 10_737_418_240, period_days: 30 },
)]
.into();
let state = Arc::new(state);
let server = TestServer::spawn(state.clone(), BEARER).await;
let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
Rig { pay, state, server, channel, sink: None }
}
fn signup_client(&self) -> SignupClient<tonic::service::interceptor::InterceptedService<Channel, impl tonic::service::Interceptor>> {
let bearer: MetadataValue<_> = format!("Bearer {BEARER}").parse().unwrap();
SignupClient::with_interceptor(self.channel.clone(), move |mut r: Request<()>| {
r.metadata_mut().insert("authorization", bearer.clone());
Ok(r)
})
}
fn admin_client(&self) -> SignupAdminClient<tonic::service::interceptor::InterceptedService<Channel, impl tonic::service::Interceptor>> {
let bearer: MetadataValue<_> = format!("Bearer {BEARER}").parse().unwrap();
SignupAdminClient::with_interceptor(self.channel.clone(), move |mut r: Request<()>| {
r.metadata_mut().insert("authorization", bearer.clone());
Ok(r)
})
}
/// The vendor POSTs a verified signup for a checkout THIS box minted (`Signup.Start`
/// keeps it, ST-7). Returns `(status, body)`.
fn deliver(&self, s: &PaidSignup) -> (u16, String) {
self.state.signups.record_minted(&s.vendor, &s.session, None).expect("minted");
self.deliver_unminted(s)
}
/// The vendor POSTs a verified signup this box never minted.
fn deliver_unminted(&self, s: &PaidSignup) -> (u16, String) {
http_post(self.server.addr, "/callback/mockpay", &self.pay.signed_headers(), &CallbackPay::signup_body(s)).expect("the webhook answers")
}
}
fn a_signup(session: &str, username: &str) -> PaidSignup {
PaidSignup {
vendor: "mockpay".into(),
session: session.into(),
reference: "pi_1".into(),
email: "bob@example.com".into(),
username: username.into(),
product: "gunnar".into(),
plan: PLAN.into(),
amount: Money { minor: 10_000, currency: Currency::Sek },
// No tax registration when this fixture was written: the vendor took the
// plan price and nothing on top, so gross and net are the same number.
tax: Money { minor: 0, currency: Currency::Sek },
paid_at_unix_ms: 1_788_436_800_000,
customer_country: None,
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_verified_signup_webhook_creates_the_customer_and_the_return_page_reads_it() {
let rig = Rig::up().await;
let s = a_signup("cs_live", "bob");
// The page polls FIRST — the redirect regularly beats the webhook.
let early = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_live".into() }).await.expect("UNKNOWN is not an error");
assert_eq!(early.into_inner().state(), State::Unknown);
let (code, body) = rig.deliver(&s);
assert_eq!(code, 200, "{body}");
assert!(body.contains("\"state\":\"CREATED\""), "{body}");
let out = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_live".into() }).await.unwrap().into_inner();
assert_eq!(out.state(), State::Created);
assert_eq!(out.username, "bob");
assert_eq!(out.namespace, "bob");
assert_eq!(out.plan, PLAN);
assert_eq!(out.product, "gunnar");
assert_eq!(out.paid_at_unix_ms, 1_788_436_800_000);
assert!(out.reason.is_empty(), "a created signup carries no reason");
// **The ledger was never touched.** A signup holds no reference and opens
// no attempt; if it ever did, this is where it would show.
assert!(rig.state.ledger.unfinished().unwrap().is_empty() && rig.state.ledger.recent(10).unwrap().is_empty());
rig.server.stop().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn the_same_session_delivered_twice_is_one_customer_and_two_200s() {
let rig = Rig::up().await;
let s = a_signup("cs_twice", "bob");
let (first, _) = rig.deliver(&s);
let (second, body) = rig.deliver(&s);
assert_eq!((first, second), (200, 200), "a retry must never be a 5xx: {body}");
assert!(body.contains("\"state\":\"CREATED\""), "{body}");
assert_eq!(rig.state.signups.list(0).unwrap().len(), 1, "one payment, one row");
rig.server.stop().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_refused_signup_is_answered_200_and_the_reason_reaches_the_page() {
let rig = Rig::up().await;
let (code, body) = rig.deliver(&a_signup("cs_bad", "Bob!"));
// 200, deliberately: the delivery succeeded and the answer is "no". A 5xx
// here is how a Stripe endpoint gets retried for days and then disabled.
assert_eq!(code, 200, "{body}");
assert!(body.contains("\"state\":\"REFUSED\""), "{body}");
let out = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_bad".into() }).await.unwrap().into_inner();
assert_eq!(out.state(), State::Refused);
assert!(out.reason.contains("Bob!"), "{}", out.reason);
assert!(out.username.is_empty(), "a refused signup names no user on the return page");
rig.server.stop().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_plan_the_config_never_named_is_refused_over_the_wire() {
let rig = Rig::up().await;
let mut s = a_signup("cs_plan", "bob");
s.plan = "gunnar/1tb-forever".into();
let (code, _) = rig.deliver(&s);
assert_eq!(code, 200);
let out = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_plan".into() }).await.unwrap().into_inner();
assert_eq!(out.state(), State::Refused);
assert!(out.reason.contains("gunnar/1tb-forever") && out.reason.contains(PLAN), "{}", out.reason);
rig.server.stop().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn the_operator_sees_every_signup_and_every_refusal_and_whom_to_write_to() {
let rig = Rig::up().await;
rig.deliver(&a_signup("cs_ok", "bob"));
rig.deliver(&a_signup("cs_no", "Bob!"));
let rows = rig.admin_client().list(pb::ListSignupsRequest { limit: 0 }).await.unwrap().into_inner().signups;
assert_eq!(rows.len(), 2);
let refused = rows.iter().find(|r| r.session == "cs_no").expect("the refusal is listed");
assert_eq!(refused.state(), State::Refused);
// The console keeps the name the customer ASKED for: "the name was
// rejected" is unreadable without the name.
assert_eq!(refused.username, "Bob!");
assert!(refused.namespace.is_empty());
let created = rows.iter().find(|r| r.session == "cs_ok").expect("the sale is listed");
assert_eq!(created.state(), State::Created);
assert_eq!(created.amount, Some(pb::Money { minor: 10_000, currency: pb::Currency::Sek as i32 }));
assert!(created.decided_at_unix_ms > 0);
// The contact address rides with the account on the OPERATOR's wire…
assert_eq!(created.email, "bob@example.com", "an account nobody can write to cannot be run");
// …and never on the customer's unauthenticated poll.
let polled = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_ok".into() }).await.unwrap().into_inner();
assert!(!format!("{polled:?}").contains("bob@example.com"), "Signup.Status carried the e-mail");
rig.server.stop().await;
}
/// **The resend door over the wire**: a session the store never saw is
/// NOT_FOUND; a created one on a sink that sends no mail (the open LoudSink) is
/// FAILED_PRECONDITION with the sink's own words — never a quiet "sent".
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn resend_welcome_names_why_it_sent_nothing() {
let rig = Rig::up().await;
rig.deliver(&a_signup("cs_ok", "bob"));
let mut admin = rig.admin_client();
let unknown = admin.resend_welcome(pb::ResendWelcomeRequest { vendor: "mockpay".into(), session: "cs_nobody".into(), force: false }).await.unwrap_err();
assert_eq!(unknown.code(), Code::NotFound, "{unknown:?}");
let no_mailer = admin.resend_welcome(pb::ResendWelcomeRequest { vendor: "mockpay".into(), session: "cs_ok".into(), force: false }).await.unwrap_err();
assert_eq!(no_mailer.code(), Code::FailedPrecondition, "{no_mailer:?}");
assert!(no_mailer.message().contains("sends no welcome mail"), "{}", no_mailer.message());
rig.server.stop().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn signup_status_is_behind_the_same_bearer_as_every_other_service() {
let rig = Rig::up().await;
let mut naked = SignupClient::new(rig.channel.clone());
let refused = naked.status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_live".into() }).await.expect_err("no bearer, no answer");
assert_eq!(refused.code(), Code::Unauthenticated);
rig.server.stop().await;
}
// ---------------------------------------------------------------------------
// Picking the name BEFORE the card, and renaming after it when the race bites.
// ---------------------------------------------------------------------------
/// **A sink that already has some customers.** The open `LoudSink` holds
/// nothing and can therefore never answer "taken", which is exactly the answer
/// these three verbs exist for.
struct HoldingSink {
taken: std::sync::Mutex<Vec<String>>,
/// Every notice it was handed to mail, in order.
mailed: std::sync::Mutex<Vec<monetize_pay::CustomerNotice>>,
/// Set: the relay does not take any mail.
relay_down: std::sync::atomic::AtomicBool,
}
impl HoldingSink {
fn with(names: &[&str]) -> std::sync::Arc<HoldingSink> {
std::sync::Arc::new(HoldingSink { taken: std::sync::Mutex::new(names.iter().map(|n| (*n).to_owned()).collect()), mailed: Default::default(), relay_down: Default::default() })
}
fn holds(&self, name: &str) -> bool {
self.taken.lock().unwrap().iter().any(|n| n == name)
}
}
impl monetize_pay::SignupSink for HoldingSink {
fn paid(&self, s: &PaidSignup) -> Result<monetize_pay::SignupOutcome, monetize_pay::SignupError> {
if self.holds(&s.username) {
return Err(monetize_pay::SignupError::NameTaken(format!(
"username {:?} is taken on this appliance; the money is at the vendor and needs a refund BY HAND",
s.username
)));
}
self.taken.lock().unwrap().push(s.username.clone());
Ok(monetize_pay::SignupOutcome::Created { namespace: s.username.clone(), username: s.username.clone() })
}
fn name_verdict(&self, username: &str) -> Result<monetize_pay::NameVerdict, monetize_pay::SignupError> {
Ok(if self.holds(username) {
monetize_pay::NameVerdict::Taken(format!("username {username:?} is taken"))
} else {
monetize_pay::NameVerdict::Free
})
}
fn notify(&self, notice: &monetize_pay::CustomerNotice) -> Result<String, monetize_pay::SignupError> {
if self.relay_down.load(std::sync::atomic::Ordering::SeqCst) {
return Err(monetize_pay::SignupError::Unavailable("the test's relay is down".into()));
}
self.mailed.lock().unwrap().push(notice.clone());
Ok(format!("{} handed to the test's relay", notice.kind))
}
}
impl Rig {
/// The same server with a sink that already has customers, and a vendor that
/// mints its own signup pages.
async fn selling(names: &[&str]) -> Rig {
let pay = CallbackPay::with_checkout(SECRET, "https://checkout.example/");
let mut state = AppState::in_memory(Registry::new().sells("gunnar", pay.clone()));
state.signup_plans = [(
PLAN.to_string(),
SignupPlan { amount_minor: 10_000, currency: Currency::Sek, cap_bytes: 10_737_418_240, period_days: 30 },
)]
.into();
// gunnar's sink, and only gunnar's (G4: one sink per product).
let sink = HoldingSink::with(names);
state.signup_sink = monetize_server::signup::HotSink::per_product(std::collections::BTreeMap::from([("gunnar".to_string(), sink.clone() as monetize_server::signup::Sink)]));
let state = Arc::new(state);
let server = TestServer::spawn(state.clone(), BEARER).await;
let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
Rig { pay, state, server, channel, sink: Some(sink) }
}
/// [`Rig::selling`] with the PRODUCT linked too — the shop deployment, where
/// there is an appliance behind the checkout that can be asked whether it
/// has room.
async fn selling_on(product: Arc<monetize::testing::MockProduct>) -> Rig {
Rig::selling_on_allowing(product, monetize_server::oversell::DEFAULT_MAX_SOLD_PERCENT).await
}
/// [`Rig::selling_on`] with `[signups.<product>] max_sold_percent_of_capacity` set —
/// the operator's choice of how much of the iron this deployment may sell.
async fn selling_on_allowing(product: Arc<monetize::testing::MockProduct>, percent: u32) -> Rig {
let pay = CallbackPay::with_checkout(SECRET, "https://checkout.example/");
let mut state = AppState::in_memory(Registry::new().sells("gunnar", pay.clone()).shared_product(product));
state.signup_max_sold_percents = std::collections::BTreeMap::from([("gunnar".to_string(), percent)]);
state.signup_plans = [(
PLAN.to_string(),
SignupPlan { amount_minor: 10_000, currency: Currency::Sek, cap_bytes: 10_737_418_240, period_days: 30 },
)]
.into();
let sink = HoldingSink::with(&[]);
state.signup_sink = sink.clone();
let state = Arc::new(state);
let server = TestServer::spawn(state.clone(), BEARER).await;
let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
Rig { pay, state, server, channel, sink: Some(sink) }
}
}
/// ★ **THE SHOP DOES NOT SELL ROOM THE APPLIANCE DOES NOT HAVE.**
///
/// A PURCHASE has asked the product since the order model was written —
/// `Transaction::quote` calls `can_absorb` before it touches a vendor, and
/// `monetize::transaction`'s own test is called *"No money moves for capacity
/// that cannot be delivered"*. A SIGNUP asked nobody. Its three checks are the
/// name, the plan and the amount (`signup::refusal`), the sink checks the name
/// and mints a principal, and `signup_sales` records the sale with the comment
/// *"Nothing was bought at a cloud for a signup: the room was on hand"* — which
/// is an ASSUMPTION and was never once verified against the box.
///
/// So on a full appliance the shop kept minting live checkouts. Each one is a
/// real charge on a real card, the customer gets an account and a cap the box
/// cannot honour, and the way out is a refund by hand. The purchase path and
/// the signup path sold the same disk under two different rules.
///
/// Refused BEFORE the card, which is the only place a capacity refusal is free:
/// after the webhook the money has already moved.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_full_appliance_mints_no_checkout_and_the_customer_never_sees_the_box() {
let product = Arc::new(monetize::testing::MockProduct::gunnar());
*product.refuse_absorb.lock().unwrap() =
Some("gunnar: the data set is full; an operator must extend the filesystem before another 10 GiB tenant".into());
let rig = Rig::selling_on(product.clone()).await;
let refused = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(),
username: "olle".into(),
email: "olle@example.com".into(),
success_url: "https://gunnar.rs/welcome?session={session}".into(),
..Default::default()
})
.await
.expect_err("a full appliance must not sell an account");
// NOTHING REACHED THE VENDOR. That is the assertion that is about money.
assert!(rig.pay.signup_orders().is_empty(), "a checkout was minted for room that does not exist");
// And the customer reads maintenance words, never the appliance's.
assert_eq!(refused.code(), Code::Unavailable, "{refused:?}");
let shown = refused.message().to_lowercase();
for word in monetize_server::signup::words::NEVER_SHOWN {
assert!(!shown.contains(word), "the customer was shown {word:?}: {}", refused.message());
}
assert!(shown.contains("nothing has been charged"), "{}", refused.message());
// Room again: the same request now mints a checkout, so the gate is a gate
// and not a wall.
*product.refuse_absorb.lock().unwrap() = None;
let out = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(),
username: "olle".into(),
email: "olle@example.com".into(),
success_url: "https://gunnar.rs/welcome?session={session}".into(),
..Default::default()
})
.await
.expect("a box with room sells")
.into_inner();
assert!(out.url.starts_with("https://checkout.example/"), "{}", out.url);
assert_eq!(rig.pay.signup_orders().len(), 1);
rig.server.stop().await;
}
/// **A deployment with NO product linked still sells.** The open build links no
/// product at all, and a capacity gate that refused where there is nobody to
/// ask would close the open shop to buy nothing.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_deployment_with_no_product_is_not_gated_by_a_box_it_does_not_have() {
let rig = Rig::selling(&[]).await;
let out = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(),
username: "olle".into(),
email: "olle@example.com".into(),
success_url: "https://gunnar.rs/welcome?session={session}".into(),
..Default::default()
})
.await
.expect("no product to ask is not a refusal")
.into_inner();
assert!(out.url.starts_with("https://checkout.example/"), "{}", out.url);
rig.server.stop().await;
}
/// ★ **"Choose another username", BEFORE the card is charged.**
///
/// The whole feature. The name used to be typed on the vendor's own hosted page,
/// where nothing can validate it, and the first time this deployment saw it was
/// in the webhook — after the money moved.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_taken_name_is_refused_before_any_money_moves() {
let rig = Rig::selling(&["bob"]).await;
let free = rig.signup_client().check_name(pb::CheckNameRequest { username: "olle".into(), product: "gunnar".into() }).await.unwrap().into_inner();
assert!(free.free, "olle is nobody's");
assert!(free.reason.is_empty(), "a free name carries no sentence");
let taken = rig.signup_client().check_name(pb::CheckNameRequest { username: "bob".into(), product: "gunnar".into() }).await.unwrap().into_inner();
assert!(!taken.free);
assert!(taken.reason.contains("bob"), "{}", taken.reason);
// A name that is not a name is refused with the RULE in it, and never
// reaches the sink: the customer is owed the rule, not the word "invalid".
let bad = rig.signup_client().check_name(pb::CheckNameRequest { username: "Bob!".into(), product: "gunnar".into() }).await.unwrap().into_inner();
assert!(!bad.free);
assert!(bad.reason.contains(monetize_pay::signup::USERNAME_RULE), "{}", bad.reason);
// ★ G4: a name is checked FOR a product. None named is the caller's
// mistake, said as one; a product this server sells no signup for is
// refused by name, never answered by another product's sink.
let unnamed = rig.signup_client().check_name(pb::CheckNameRequest { username: "olle".into(), product: String::new() }).await.unwrap_err();
assert_eq!(unnamed.code(), Code::InvalidArgument, "{unnamed:?}");
let other = rig.signup_client().check_name(pb::CheckNameRequest { username: "bob".into(), product: "holger".into() }).await.unwrap().into_inner();
assert!(!other.free && other.reason.contains("holger"), "gunnar's sink never answers for holger: {other:?}");
// And a checkout is minted FOR a product: none named mints nothing.
let unnamed = rig
.signup_client()
.start(pb::StartSignupRequest { username: "olle".into(), email: "olle@example.com".into(), success_url: "https://gunnar.rs/welcome?session={session}".into(), ..Default::default() })
.await
.unwrap_err();
assert_eq!(unnamed.code(), Code::InvalidArgument, "{unnamed:?}");
assert!(unnamed.message().contains("product"), "{unnamed:?}");
// Nothing was recorded: this verb holds nothing and writes nothing.
assert!(rig.state.signups.list(0).unwrap().is_empty());
rig.server.stop().await;
}
/// ★ **The checkout is minted for a name the deployment has already agreed to**,
/// and the vendor is handed that name — which is the half that used to be
/// missing, and the reason a paid customer could arrive with a name nobody could
/// give them.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn start_mints_a_checkout_carrying_the_name_and_the_plans_net_price() {
let rig = Rig::selling(&["bob"]).await;
let out = rig
.signup_client()
.start(pb::StartSignupRequest {
username: "olle".into(),
product: "gunnar".into(),
// Empty: a console carries no price list, and this deployment sells one.
plan: String::new(),
vendor: String::new(),
success_url: "https://gunnar.rs/welcome?session={session}".into(),
cancel_url: "https://gunnar.rs/signup".into(),
email: " olle@example.com ".into(),
market: 0,
})
.await
.expect("a free name gets a checkout")
.into_inner();
assert!(out.url.starts_with("https://checkout.example/"), "{}", out.url);
let order = rig.pay.signup_orders().pop().expect("the vendor was asked");
assert_eq!(order.username, "olle", "the vendor was not told the name");
assert_eq!(order.email, "olle@example.com", "the address the buyer typed, trimmed, reaches the vendor");
// A16 (owner 2026-10-23: a buyer never sees monetize): the product, its room and period, and
// the price in major units — never the plan id, never öre.
assert_eq!(order.description, "Gunnar — 10.0 GiB storage, 1 month — 100.00 SEK", "the buyer reads the product and major units");
assert!(!order.description.contains(PLAN), "the vendor's page names no plan id: {}", order.description);
// No address, or a mistyped one: refused by name before any card form.
for (bad, words) in [("", "required"), ("olle.example.com", "no '@'")] {
let refused = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(), username: "olle".into(), email: bad.into(), success_url: "https://gunnar.rs/welcome?session={session}".into(), ..Default::default() })
.await
.expect_err("no usable address, no checkout");
assert_eq!(refused.code(), Code::InvalidArgument, "{bad:?}: {refused:?}");
assert!(refused.message().contains(words), "{bad:?}: {}", refused.message());
}
assert_eq!(rig.pay.signup_orders().len(), 1, "a checkout was minted without a usable address");
assert_eq!(order.plan, PLAN, "the only plan this deployment sells");
assert_eq!(order.product, "gunnar", "read off the plan id where the caller named none");
assert_eq!(order.amount, Money { minor: 10_000, currency: Currency::Sek }, "the NET price: tax is the vendor's to add");
assert_eq!(order.success_url, "https://gunnar.rs/welcome?session={session}");
// And a taken name never reaches the vendor at all.
let refused = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(), username: "bob".into(), success_url: "https://gunnar.rs/welcome?session={session}".into(), ..Default::default() })
.await
.expect_err("a taken name gets no card form");
assert_eq!(refused.code(), Code::FailedPrecondition);
assert!(refused.message().contains("bob"), "{}", refused.message());
assert_eq!(rig.pay.signup_orders().len(), 1, "a refused name still reached the vendor");
rig.server.stop().await;
}
/// **A vendor that mints no signup page answers UNIMPLEMENTED**, which is what a
/// console turns into its old fixed buy link. It must be distinguishable from a
/// refusal, or a deployment that sells through a Payment Link loses its door.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_vendor_with_no_signup_page_is_unimplemented_and_not_a_refusal() {
// `Rig::up`'s vendor is built without a checkout page.
let rig = Rig::up().await;
let out = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(), username: "olle".into(), email: "olle@example.com".into(), success_url: "https://gunnar.rs/welcome?session={session}".into(), ..Default::default() })
.await
.expect_err("this vendor sells no signups");
assert_eq!(out.code(), Code::Unimplemented, "{}", out.message());
assert!(out.message().contains("mockpay"), "it names the vendor: {}", out.message());
rig.server.stop().await;
}
/// ★ **No vendor here can sell the signup: UNIMPLEMENTED, a typed "not sold
/// here"** (stinky gunnar-ui #7, DECISIONS 5.4): a configuration gap is told
/// apart by its CODE, never by prose a console has to match — the same code as
/// no sink, and never FAILED_PRECONDITION, which a name refusal wears.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn no_vendor_that_can_sell_the_signup_is_unimplemented_not_a_refusal() {
let mut rig = Rig::up().await;
let mut state = AppState::in_memory(Registry::new());
state.signup_plans = rig.state.signup_plans.clone();
let state = Arc::new(state);
let server = TestServer::spawn(state.clone(), BEARER).await;
rig.channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
let out = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(), username: "olle".into(), email: "olle@example.com".into(), success_url: "https://gunnar.rs/welcome?session={session}".into(), ..Default::default() })
.await
.expect_err("no vendor: no sale");
assert_eq!(out.code(), Code::Unimplemented, "{}", out.message());
// The plan's price in major units with its currency, never the öre count.
assert!(out.message().contains("at 100.00 SEK") && !out.message().contains("10000"), "{}", out.message());
assert!(rig.pay.signup_orders().is_empty(), "nothing was minted");
server.stop().await;
rig.server.stop().await;
}
/// ★ **The race: a name free at the check, taken by the time the card cleared.**
///
/// The customer has paid. Before `Rename` the answer was a refund by hand and an
/// account that never appeared.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_customer_whose_name_was_taken_while_they_paid_renames_instead_of_being_refunded() {
let rig = Rig::selling(&[]).await;
// Somebody else's webhook lands first and takes the name.
rig.deliver(&a_signup("cs_first", "bob"));
// Ours lands second, for the same name, and is refused over the NAME.
let (code, _) = rig.deliver(&a_signup("cs_second", "bob"));
assert_eq!(code, 200);
let refused = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_second".into() }).await.unwrap().into_inner();
assert_eq!(refused.state(), State::Refused);
// **The machine-readable half.** The page decides to offer one text field on
// THIS, never on the prose.
assert_eq!(refused.refusal(), pb::signup_status_response::Refusal::Name);
let out = rig
.signup_client()
.rename(pb::RenameSignupRequest { vendor: "mockpay".into(), session: "cs_second".into(), username: "bob2".into() })
.await
.expect("a free name finishes it")
.into_inner()
.status
.expect("the record as it stands");
assert_eq!(out.state(), State::Created);
assert_eq!(out.username, "bob2");
assert_eq!(out.namespace, "bob2");
// The money never moved twice: one payment, one row, and the poll agrees.
assert_eq!(rig.state.signups.list(0).unwrap().len(), 2, "one row per payment");
let polled = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_second".into() }).await.unwrap().into_inner();
assert_eq!(polled.state(), State::Created);
assert_eq!(polled.username, "bob2");
rig.server.stop().await;
}
/// **A refusal about the ORDER is final on the wire too**, and the code says so
/// without anybody reading the sentence.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_refusal_about_the_order_is_final_and_rename_refuses_to_touch_it() {
let rig = Rig::selling(&[]).await;
let mut s = a_signup("cs_plan2", "bob");
s.plan = "gunnar/1tb-forever".into();
rig.deliver(&s);
let out = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_plan2".into() }).await.unwrap().into_inner();
assert_eq!(out.refusal(), pb::signup_status_response::Refusal::Final);
let err = rig
.signup_client()
.rename(pb::RenameSignupRequest { vendor: "mockpay".into(), session: "cs_plan2".into(), username: "olle".into() })
.await
.expect_err("a plan nobody sells is not a name problem");
assert_eq!(err.code(), Code::FailedPrecondition);
assert!(err.message().contains("gunnar/1tb-forever"), "the page shows the ORIGINAL refusal: {}", err.message());
rig.server.stop().await;
}
/// The four refusals a rename can give are four codes, because a page answers
/// each differently: a session nobody knows, a new name also taken, a record
/// that cannot be renamed, and an outage.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn the_rename_refusals_are_told_apart_by_code() {
let rig = Rig::selling(&["bob", "olle"]).await;
let unknown = rig
.signup_client()
.rename(pb::RenameSignupRequest { vendor: "mockpay".into(), session: "cs_nobody".into(), username: "x1".into() })
.await
.expect_err("nobody has heard of it");
assert_eq!(unknown.code(), Code::NotFound);
rig.deliver(&a_signup("cs_race", "bob"));
let also_taken = rig
.signup_client()
.rename(pb::RenameSignupRequest { vendor: "mockpay".into(), session: "cs_race".into(), username: "olle".into() })
.await
.expect_err("olle is taken too");
assert_eq!(also_taken.code(), Code::InvalidArgument, "{}", also_taken.message());
// And the record is still renameable, so the page simply asks again.
let still = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_race".into() }).await.unwrap().into_inner();
assert_eq!(still.refusal(), pb::signup_status_response::Refusal::Name);
rig.server.stop().await;
}
/// Every new verb is behind the same bearer as `Status`. `CheckName` is an
/// oracle by nature and the per-address budget lives in the console in front of
/// it, but nothing here is answered to a caller with no credential at all.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn the_new_signup_verbs_are_behind_the_same_bearer() {
let rig = Rig::selling(&[]).await;
let mut naked = SignupClient::new(rig.channel.clone());
assert_eq!(naked.check_name(pb::CheckNameRequest { username: "olle".into(), product: "gunnar".into() }).await.unwrap_err().code(), Code::Unauthenticated);
assert_eq!(naked.start(pb::StartSignupRequest { product: "gunnar".into(), username: "olle".into(), ..Default::default() }).await.unwrap_err().code(), Code::Unauthenticated);
assert_eq!(
naked.rename(pb::RenameSignupRequest { vendor: "mockpay".into(), session: "cs_x".into(), username: "olle".into() }).await.unwrap_err().code(),
Code::Unauthenticated
);
rig.server.stop().await;
}
/// ★ **A FLEET SOLD PAST ITS ALLOWANCE MINTS NO CHECKOUT, AND THE BUYER READS
/// NONE OF IT.**
///
/// `can_absorb` — the gate above — asks about FREE SPACE, and free space is
/// what nobody has written yet. Eleven tenants holding a fraction of their
/// 10 GiB caps leave a box that says yes to the twelfth, and the twelfth is
/// sold a promise the disk cannot keep. `monetize::fleet::Oversell` has
/// measured exactly that since it was written and gated nothing.
///
/// The fixture is the front on 2026-09-18: eleven tenants at 10 GiB caps on a
/// 38 GiB set. The refusal is UNAVAILABLE with the maintenance sentence — the
/// customer must never read "oversold", "capacity" or "disk", and must read
/// that nothing has been charged, because nothing has.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_fleet_sold_past_its_allowance_mints_no_checkout_and_the_customer_never_sees_the_arithmetic() {
const GIB: u64 = 1 << 30;
let product = Arc::new(monetize::testing::MockProduct::gunnar());
// The box answers every capacity question with a yes: 35 GiB really is free.
*product.servable.lock().unwrap() = Some(monetize_product::Servable::Measured {
servable_bytes: 35 * GIB + GIB / 4,
total_bytes: 38 * GIB,
growth: monetize_product::Growth::Sealed("not grown here".into()),
ceilings: Default::default(),
});
let rig = Rig::selling_on(product.clone()).await;
seed_eleven(&rig);
let refused = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(),
username: "olle".into(),
email: "olle@example.com".into(),
success_url: "https://gunnar.rs/welcome?session={session}".into(),
..Default::default()
})
.await
.expect_err("a fleet already sold past its allowance must not sell a twelfth");
// NOTHING REACHED THE VENDOR: the assertion that is about money.
assert!(rig.pay.signup_orders().is_empty(), "a checkout was minted for capacity that is already promised");
// The BOX still said yes — which is the whole point: this refusal is not
// the appliance's, it is the fleet arithmetic's.
assert_eq!(refused.code(), Code::Unavailable, "{refused:?}");
let shown = refused.message().to_lowercase();
for word in monetize_server::signup::words::NEVER_SHOWN {
assert!(!shown.contains(word), "the customer was shown {word:?}: {}", refused.message());
}
assert!(shown.contains("nothing has been charged"), "{}", refused.message());
// **THE SAME FLEET, with the allowance raised to 400 %, sells it** — so the
// gate is the operator's number and not a wall. The state is rebuilt with
// the same eleven tenants, because the allowance is read at start exactly
// as it is on a real deployment.
let roomy = Rig::selling_on_allowing(product.clone(), 400).await;
seed_eleven(&roomy);
let out = roomy
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(),
username: "olle".into(),
email: "olle@example.com".into(),
success_url: "https://gunnar.rs/welcome?session={session}".into(),
..Default::default()
})
.await
.expect("a fleet inside its allowance sells")
.into_inner();
assert!(out.url.starts_with("https://checkout.example/"), "{}", out.url);
rig.server.stop().await;
roomy.server.stop().await;
}
/// Eleven paid tenants at the plan's 10 GiB cap, each holding a quarter of a
/// gigabyte of it — the live front's fleet, in the ledger and the usage store.
fn seed_eleven(rig: &Rig) {
const GIB: u64 = 1 << 30;
for i in 0..11 {
let tenant = format!("t{i}");
rig.state.usage.put("gunnar", &tenant, &monetize_product::Usage { meters: [("pack_bytes".to_string(), GIB / 4)].into(), measured_at_unix_ms: 1 }).unwrap();
rig.state
.ledger
.set_entitlement(
"gunnar",
&rig.state.signer_for("gunnar").sign_fact(monetize_product::EntitlementFact {
tenant: monetize_product::TenantId(tenant),
plan: PLAN.into(),
state: monetize_product::State::Paid,
paid_until_unix_ms: Some(2),
caps: [("pack_bytes".to_string(), 10_737_418_240u64)].into(),
source: "payment:mockpay:x".into(),
signature: Vec::new(),
issued_unix_ms: None,
issued_signature: Vec::new(),
}),
)
.unwrap();
}
}
/// ★ **NO SINK, NO SALE** (owner 2026-09-26). The open server's sink when
/// none is loaded is `NoSink`: `Signup.Start` is refused UNIMPLEMENTED before
/// any checkout (the console reads that as "cannot sell an account"), and a
/// paid webhook that reached it anyway is refused by name — never `Created`.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn with_no_sink_loaded_no_checkout_is_minted_and_nothing_is_ever_created() {
let mut rig = Rig::selling(&[]).await;
// the server a deployment gets when nothing creates accounts
let mut state = AppState::in_memory(Registry::new().sells("gunnar", rig.pay.clone()));
state.signup_plans = rig.state.signup_plans.clone();
state.signup_sink = monetize_server::registry::Linked::default().build_signup_sink(std::path::Path::new("/nonexistent"), &serde_json::json!({})).unwrap();
assert!(!state.signup_sink.creates_accounts(), "the open default creates nothing and says so");
let state = Arc::new(state);
let server = TestServer::spawn(state.clone(), BEARER).await;
rig.channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
let refused = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(), username: "olle".into(), email: "olle@example.com".into(), success_url: "https://gunnar.rs/welcome?session={session}".into(), ..Default::default() })
.await
.expect_err("no sink: no sale");
assert_eq!(refused.code(), tonic::Code::Unimplemented, "{refused:?}");
assert!(refused.message().contains("no signup sink"), "{}", refused.message());
assert!(rig.pay.signup_orders().is_empty(), "a checkout was minted for an account nobody can create");
// and the sink itself never says Created
let s = monetize_pay::signup::PaidSignup {
vendor: "stripe".into(), session: "cs_x".into(), reference: String::new(), email: String::new(), username: "olle".into(),
product: "gunnar".into(), plan: PLAN.into(), amount: monetize_pay::Money { minor: 10_000, currency: Currency::Sek },
tax: monetize_pay::Money { minor: 0, currency: Currency::Sek }, paid_at_unix_ms: 0, customer_country: None,
};
assert!(matches!(state.signup_sink.paid(&s), Err(monetize_pay::signup::SignupError::Refused(m)) if m.contains("no account was created")));
drop(server);
}
// ---------------------------------------------------------------------------
// One shop (owner 2026-10-07): an account at the shop, without room here.
// ---------------------------------------------------------------------------
/// The shop's account plan, as the kit writes it: no price, no room.
const ACCOUNT_PLAN: &str = "gunnar/account";
impl Rig {
/// [`Rig::selling`] with the shop's account plan beside the paid one.
async fn selling_accounts(names: &[&str]) -> Rig {
let rig = Rig::selling(names).await;
rig.state.signup_plans.set(
[
(PLAN.to_string(), SignupPlan { amount_minor: 10_000, currency: Currency::Sek, cap_bytes: 10_737_418_240, period_days: 30 }),
(ACCOUNT_PLAN.to_string(), SignupPlan { amount_minor: 0, currency: Currency::Sek, cap_bytes: 0, period_days: 0 }),
]
.into(),
Default::default(),
);
rig
}
async fn start_account(&self, username: &str) -> Result<pb::StartSignupResponse, tonic::Status> {
self.signup_client()
.start(pb::StartSignupRequest {
username: username.into(),
email: format!("{username}@example.com"),
plan: ACCOUNT_PLAN.into(),
product: "gunnar".into(),
vendor: "stripe".into(),
success_url: "https://gunnar.rs/welcome?session={session}".into(),
..Default::default()
})
.await
.map(tonic::Response::into_inner)
}
/// ★ **A free account, the whole way** (owner 2026-10-15): `Start` sends the buyer to the
/// vendor's card check, the buyer checks a card of their own (`fp_<name>`), and the welcome
/// page's `Status` poll makes the account. Answers the welcome page the buyer lands on.
async fn make_account(&self, username: &str) -> Result<pb::StartSignupResponse, tonic::Status> {
let page = self.start_account(username).await?;
assert!(page.url.starts_with("https://checkout.example/cs_card"), "the vendor's card check: {}", page.url);
self.pay.check_card(&page.session, &format!("fp_{username}"));
let st = self.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: page.session.clone() }).await?.into_inner();
assert_eq!(st.state(), State::Created, "made once the card is checked: {st:?}");
Ok(pb::StartSignupResponse { url: format!("https://gunnar.rs/welcome?session={}", page.session), session: page.session })
}
}
/// ★ **An account at the shop is made once its card is checked** (one shop, owner
/// 2026-10-07; the card check owner 2026-10-15): a visitor who buys only a private Gunnar
/// first makes the account they will see and grow it under. Its plan has no price, so no
/// money moves — but `Start` sends the buyer to the vendor's CARD CHECK (nothing charged),
/// never a paid checkout, and the account is made when the return page's poll finds the
/// card checked; from then on it reads CREATED. A name already held is refused before any
/// card check.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn an_account_at_the_shop_is_made_once_its_card_is_checked() {
let rig = Rig::selling_accounts(&["bob"]).await;
let page = rig.start_account("alice").await.expect("a card check");
assert!(page.url.starts_with("https://checkout.example/cs_card"), "the vendor's card check: {}", page.url);
assert!(rig.pay.signup_orders().is_empty(), "no paid checkout: nothing is paid for");
let st = rig.signup_client().status(pb::SignupStatusRequest { vendor: "stripe".into(), session: page.session.clone() }).await.unwrap().into_inner();
assert_ne!(st.state(), State::Created, "not made before the card is checked");
let out = rig.make_account("carol").await.expect("made");
let st = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: out.session.clone() }).await.unwrap().into_inner();
assert_eq!(st.state(), State::Created, "the return page reads it made");
assert_eq!((st.username.as_str(), st.namespace.as_str()), ("carol", "carol"));
let taken = rig.start_account("bob").await.expect_err("a name already held");
assert_eq!(taken.code(), Code::FailedPrecondition, "{taken:?}");
rig.server.stop().await;
}
/// ★ **An account at the shop is mailed its sign-in link** (§3c, OM-20):
/// the moment `Start` makes the account, the buyer's address gets ONE mail
/// carrying the link the answer leads to — this console's welcome page with
/// the account's session, which registers the passkey while the account holds
/// none. A buyer who closes the tab before the passkey still has a way in that
/// is not an operator's. The secret is in the body and nowhere else (not the
/// subject); a refused start mails nothing; a relay that does not take the
/// mail never un-makes the account.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn an_account_at_the_shop_is_mailed_its_sign_in_link() {
let rig = Rig::selling_accounts(&["bob"]).await;
let sink = rig.sink.clone().expect("the selling rig's sink");
let out = rig.make_account("alice").await.expect("made");
let mailed = sink.mailed.lock().unwrap().clone();
assert_eq!(mailed.len(), 1, "{mailed:?}");
let m = &mailed[0];
assert_eq!((m.product.as_str(), m.tenant.as_str(), m.to.as_str(), m.kind.as_str()), ("gunnar", "alice", "alice@example.com", "account.sign_in"), "{m:?}");
assert!(m.body.contains(&out.url), "the link the answer leads to: {m:?}");
assert!(m.subject.contains("gunnar.rs") && !m.subject.contains(&out.session), "{m:?}");
rig.start_account("bob").await.expect_err("a name already held");
assert_eq!(sink.mailed.lock().unwrap().len(), 1, "a refused start mails nothing");
sink.relay_down.store(true, std::sync::atomic::Ordering::SeqCst);
let carol = rig.make_account("carol").await.expect("the account is made though the mail did not go");
let st = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: carol.session }).await.unwrap().into_inner();
assert_eq!(st.state(), State::Created);
rig.server.stop().await;
}
/// ★ **The sign-in link, sent again** (owner 2026-10-11: the welcome page says where the
/// link went, with [Send again]): a CREATED signup's link — the console's welcome page for
/// that session — is mailed again to the address it was made with, and the answer names it
/// MASKED only. Once a minute per session; an unknown session, a page that is no https
/// welcome page for a session and a signup of another product are refused, and nothing is
/// mailed for them. (A page off the signup's site is `site_refusal`'s `[signup-other-site]`.)
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_created_signups_sign_in_link_is_sent_again_masked_and_not_too_often() {
let rig = Rig::selling_accounts(&[]).await;
let sink = rig.sink.clone().expect("the selling rig's sink");
let out = rig.make_account("alice").await.expect("made");
assert_eq!(sink.mailed.lock().unwrap().len(), 1);
let again = |session: &str, url: &str, product: &str| {
let mut c = rig.signup_client();
let r = pb::SignupSignInAgainRequest { product: product.into(), session: session.into(), welcome_url: url.into() };
async move { c.send_sign_in_again(r).await.map(tonic::Response::into_inner) }
};
let welcome = "https://gunnar.rs/welcome?session={session}";
let said = again(&out.session, welcome, "gunnar").await.expect("sent again");
assert!(said.words.contains("al***@example.com") && !said.words.contains("alice@example.com"), "masked only: {}", said.words);
let mailed = sink.mailed.lock().unwrap().clone();
assert_eq!(mailed.len(), 2, "{mailed:?}");
assert_eq!((mailed[1].to.as_str(), mailed[1].kind.as_str()), ("alice@example.com", "account.sign_in"));
assert!(mailed[1].body.contains(&out.url), "the same link: {:?}", mailed[1]);
let soon = again(&out.session, welcome, "gunnar").await.expect_err("once a minute");
assert_eq!(soon.code(), Code::ResourceExhausted, "{soon:?}");
let unknown = again("acct_00000000000000000000000000000000", welcome, "gunnar").await.expect_err("unknown");
assert_eq!(unknown.code(), Code::NotFound, "{unknown:?}");
for bad in ["http://gunnar.rs/welcome?session={session}", "https://gunnar.rs/welcome"] {
let refused = again(&out.session, bad, "gunnar").await.expect_err(bad);
assert_eq!(refused.code(), Code::InvalidArgument, "{bad}: {refused:?}");
}
let wrong = again(&out.session, welcome, "holger").await.expect_err("another product's");
assert_eq!(wrong.code(), Code::NotFound, "{wrong:?}");
assert_eq!(sink.mailed.lock().unwrap().len(), 2, "nothing more was mailed");
rig.server.stop().await;
}
/// ★ **An account at the shop is never offered as room**: `Signup.Plans`
/// lists the paid plans and not the account's, which sells nothing.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn an_account_at_the_shop_is_never_offered_as_room() {
let rig = Rig::selling_accounts(&[]).await;
let plans = rig.signup_client().plans(pb::SignupPlansRequest { product: "gunnar".into(), ..Default::default() }).await.unwrap().into_inner().plans;
assert!(plans.iter().all(|p| p.id != ACCOUNT_PLAN), "{plans:?}");
assert!(plans.iter().any(|p| p.id == PLAN), "the paid plan is still on offer");
rig.server.stop().await;
}
/// ★ **An account at the shop holds no room and never lapses**: its fact is
/// Free with no end (nothing to renew, so nothing to delete), its cap
/// [`monetize_server::signup::NO_ROOM_CAP`] (a zero cap is NO cap on gunnar);
/// it is no sale. And a plan with room is never free: refused, nothing made.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn an_account_at_the_shop_holds_no_room_and_never_lapses() {
let rig = Rig::selling_accounts(&[]).await;
rig.make_account("alice").await.expect("made");
let facts = monetize_server::signup::signup_facts(&rig.state, "gunnar").unwrap();
let alice = facts.iter().find(|f| f.tenant.0 == "alice").expect("alice's fact");
assert_eq!(alice.state, monetize_product::State::Free, "{alice:?}");
assert_eq!(alice.paid_until_unix_ms, None, "nothing ends: {alice:?}");
assert_eq!(alice.caps.get("pack_bytes"), Some(&monetize_server::signup::NO_ROOM_CAP), "{alice:?}");
let sales = monetize_server::signup::signup_sales(&rig.state, "gunnar").unwrap();
assert!(sales.iter().all(|s| s.tenant != "alice"), "an account at the shop is no sale: {sales:?}");
rig.state.signup_plans.set(
[("gunnar/odd".to_string(), SignupPlan { amount_minor: 0, currency: Currency::Sek, cap_bytes: 10_737_418_240, period_days: 30 })].into(),
Default::default(),
);
let refused = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(), username: "dora".into(), email: "dora@example.com".into(), plan: "gunnar/odd".into(), success_url: "https://gunnar.rs/welcome?session={session}".into(), ..Default::default() })
.await
.expect_err("room is never free");
assert_eq!(refused.code(), Code::FailedPrecondition, "{refused:?}");
assert!(rig.pay.signup_orders().is_empty());
rig.server.stop().await;
}
/// ★ **A request that names no plan never lands on the account plan**: the
/// functional test and an older page ask with none, and the shop picks the
/// paid plan as before (one per market) — the account plan beside it changes
/// nothing for them.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_request_naming_no_plan_never_lands_on_the_account_plan() {
let rig = Rig::selling_accounts(&[]).await;
let out = rig
.signup_client()
.start(pb::StartSignupRequest { product: "gunnar".into(), username: "erik".into(), email: "erik@example.com".into(), success_url: "https://gunnar.rs/welcome?session={session}".into(), ..Default::default() })
.await
.expect("the paid plan, as before")
.into_inner();
assert!(out.url.starts_with("https://checkout.example/"), "{}", out.url);
assert_eq!(rig.pay.signup_orders().len(), 1, "a checkout for the paid plan");
rig.server.stop().await;
}
// ---------------------------------------------------------------------------
// STINKY ST-23: the account mail's link is always on the estate's own site.
// ---------------------------------------------------------------------------
const TEST2_BEARER: &str = "estate-bearer-of-test2-0123456789abcd";
impl Rig {
/// [`Rig::selling_accounts`] on ONE box selling gunnar for two sites (`gunnar-rs` at
/// gunnar.rs, home; `test2-gunnar-rs` at test2.gunnar.rs), test2 enrolled with its bearer.
async fn selling_sites() -> Rig {
let pay = CallbackPay::with_checkout(SECRET, "https://checkout.example/");
let site = |site: &str| monetize_server::config::EstateConfig { product: "gunnar".into(), vendor: "mockpay".into(), owner: "ignalina".into(), site: site.into(), ..Default::default() };
let reg = Registry::new().sells("gunnar", pay.clone()).home("gunnar", "gunnar-rs").estate("gunnar-rs", site("gunnar.rs")).estate("test2-gunnar-rs", site("test2.gunnar.rs"));
let state = AppState::in_memory(reg);
state.signup_plans.set([(ACCOUNT_PLAN.to_string(), SignupPlan { amount_minor: 0, currency: Currency::Sek, cap_bytes: 0, period_days: 0 })].into(), Default::default());
let mut state = state;
let sink = HoldingSink::with(&[]);
state.signup_sink = monetize_server::signup::HotSink::per_product(std::collections::BTreeMap::from([("gunnar".to_string(), sink.clone() as monetize_server::signup::Sink)]));
state.fleet.know_bearer(&monetize_server::fleet::bearer_digest(TEST2_BEARER), "test2-gunnar-rs");
let state = Arc::new(state);
let server = TestServer::spawn(state.clone(), BEARER).await;
let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.expect("connect");
Rig { pay, state, server, channel, sink: Some(sink) }
}
async fn start_account_as(&self, bearer: &str, username: &str, success_url: &str) -> Result<pb::StartSignupResponse, tonic::Status> {
let b: MetadataValue<_> = format!("Bearer {bearer}").parse().unwrap();
SignupClient::with_interceptor(self.channel.clone(), move |mut r: Request<()>| {
r.metadata_mut().insert("authorization", b.clone());
Ok(r)
})
.start(pb::StartSignupRequest { username: username.into(), email: format!("{username}@example.com"), plan: ACCOUNT_PLAN.into(), product: "gunnar".into(), vendor: "stripe".into(), success_url: success_url.into(), ..Default::default() })
.await
.map(tonic::Response::into_inner)
}
/// [`Rig::start_account_as`] the whole way: the card checked, the welcome page's poll
/// (as the same estate) makes the account. Answers the welcome page with its session.
async fn make_account_as(&self, bearer: &str, username: &str, success_url: &str) -> Result<pb::StartSignupResponse, tonic::Status> {
let page = self.start_account_as(bearer, username, success_url).await?;
self.pay.check_card(&page.session, &format!("fp_{username}"));
let b: MetadataValue<_> = format!("Bearer {bearer}").parse().unwrap();
let st = SignupClient::with_interceptor(self.channel.clone(), move |mut r: Request<()>| {
r.metadata_mut().insert("authorization", b.clone());
Ok(r)
})
.status(pb::SignupStatusRequest { vendor: "stripe".into(), session: page.session.clone() })
.await?
.into_inner();
assert_eq!(st.state(), State::Created, "made once the card is checked: {st:?}");
Ok(pb::StartSignupResponse { url: success_url.replace("{session}", &page.session), session: page.session })
}
}
/// ★ **STINKY ST-23: an account's sign-in mail links only to its estate's own site.** A
/// Start that names no estate (the service bearer) for a product sold per site is refused —
/// nothing made, nothing mailed — never mailed to whatever host `success_url` names; from an
/// estate, a page off its site is refused for the account plan too; on its site the mail's
/// link and subject name that site.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn an_accounts_sign_in_mail_links_only_to_its_estates_site() {
let rig = Rig::selling_sites().await;
let sink = rig.sink.clone().unwrap();
let unnamed = rig.start_account_as(BEARER, "alice", "https://evil.example/welcome?session={session}").await.expect_err("no estate named");
assert!(unnamed.message().contains("estate-unnamed"), "{unnamed:?}");
let off = rig.start_account_as(TEST2_BEARER, "alice", "https://evil.example/welcome?session={session}").await.expect_err("off its site");
assert_eq!(off.code(), Code::InvalidArgument, "{off:?}");
assert!(off.message().contains("signup-other-site"), "{off:?}");
let other = rig.start_account_as(TEST2_BEARER, "alice", "https://gunnar.rs/welcome?session={session}").await.expect_err("another site's page");
assert!(other.message().contains("signup-other-site"), "{other:?}");
assert!(sink.mailed.lock().unwrap().is_empty(), "nothing was mailed for a refused start");
let made = rig.make_account_as(TEST2_BEARER, "alice", "https://test2.gunnar.rs/welcome?session={session}").await.expect("on its site");
let mailed = sink.mailed.lock().unwrap().clone();
assert_eq!(mailed.len(), 1, "{mailed:?}");
assert!(mailed[0].body.contains(&made.url) && mailed[0].subject.contains("https://test2.gunnar.rs"), "{:?}", mailed[0]);
rig.server.stop().await;
}
/// ★ ST-7 (STINKY 2026-10-11): **a verified webhook for a checkout this box never minted is
/// another system's money on the same merchant account** — answered 200 (the vendor stops),
/// and no account is made; the same session once this box minted it is a customer.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_session_this_box_never_minted_is_ignored_and_never_becomes_an_account() {
let rig = Rig::up().await;
let s = a_signup("cs_elsewhere", "mallory");
let (code, body) = rig.deliver_unminted(&s);
assert_eq!(code, 200, "{body}");
assert!(body.contains("\"ignored\":true") && body.contains("not minted by this monetize"), "{body}");
let out = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: "cs_elsewhere".into() }).await.expect("status").into_inner();
assert_eq!(out.state(), State::Unknown, "nothing was kept for another system's session");
let (code, body) = rig.deliver(&s);
assert_eq!(code, 200, "{body}");
assert!(body.contains("\"state\":\"CREATED\""), "minted here: a customer: {body}");
rig.server.stop().await;
}
/// ★ **A paid plan's account is mailed its sign-in link once it is made** (A11 fix 7,
/// 2026-10-23; Gunnar and Holger alike): the welcome page says "We've sent a sign-in link",
/// and before this only the free account's path sent one. The link is the welcome page the
/// buyer was sent back to, with the session; a second delivery of the same payment mails
/// nothing more; a payment this box keeps no welcome page for mails nothing.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_paid_plans_account_is_mailed_its_sign_in_link_once_it_is_made() {
let rig = Rig::selling(&[]).await;
let sink = rig.sink.clone().expect("the selling rig's sink");
let out = rig
.signup_client()
.start(pb::StartSignupRequest {
username: "olle".into(),
product: "gunnar".into(),
plan: String::new(),
vendor: String::new(),
success_url: "https://gunnar.rs/welcome?session={session}&next=%2F".into(),
cancel_url: "https://gunnar.rs/signup".into(),
email: "olle@example.com".into(),
market: 0,
})
.await
.expect("a checkout")
.into_inner();
assert!(!out.session.is_empty(), "{out:?}");
assert!(sink.mailed.lock().unwrap().is_empty(), "nothing is mailed before the payment");
let paid = PaidSignup { email: "olle@example.com".into(), ..a_signup(&out.session, "olle") };
assert_eq!(rig.deliver_unminted(&paid).0, 200);
let st = rig.signup_client().status(pb::SignupStatusRequest { vendor: "mockpay".into(), session: out.session.clone() }).await.unwrap().into_inner();
assert_eq!(st.state(), State::Created, "{st:?}");
let mailed = sink.mailed.lock().unwrap().clone();
assert_eq!(mailed.len(), 1, "{mailed:?}");
let m = &mailed[0];
assert_eq!((m.tenant.as_str(), m.to.as_str(), m.kind.as_str()), ("olle", "olle@example.com", "account.sign_in"), "{m:?}");
assert!(m.body.contains(&format!("https://gunnar.rs/welcome?session={}&next=%2F", out.session)), "the welcome page with its session: {m:?}");
assert!(!m.subject.contains(&out.session), "the link is in the body only: {m:?}");
assert_eq!(rig.deliver_unminted(&paid).0, 200);
assert_eq!(sink.mailed.lock().unwrap().len(), 1, "a second delivery mails nothing more");
rig.deliver(&a_signup("cs_elsewhere", "kim"));
assert_eq!(sink.mailed.lock().unwrap().len(), 1, "no welcome page kept, no mail");
rig.server.stop().await;
}
/// ★ **A made account is on the ledger before a bundle is raised from it** (the buy trials on
/// `linked()`, 2026-10-24): a free account's fact lives in the signup store until the poller's
/// next tick adopts it, while the site's product already holds it (`NO_ROOM_CAP`). A "more
/// room" order in that window was resolved against an EMPTY ledger, so the site's ticket (held
/// + delta) and the ONE box's caps differed and the raise was refused (`WHO_NOT_CONFIRMED`).
/// `adopt_signup` seeds the granted fact at once, as the tick would; twice is once.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_made_account_is_adopted_onto_the_ledger_before_a_raise() {
let rig = Rig::selling_accounts(&[]).await;
rig.make_account("alice").await.expect("made");
assert!(rig.state.ledger.entitlement("gunnar", "alice").unwrap().is_none(), "the signup store holds it, the ledger not yet");
assert!(monetize_server::poller::adopt_signup(&rig.state, "gunnar", "alice").expect("adopted"), "adopted now");
let fact = rig.state.ledger.entitlement("gunnar", "alice").unwrap().expect("on the ledger");
assert_eq!((fact.state, fact.caps.get("pack_bytes").copied()), (monetize_product::State::Free, Some(monetize_server::signup::NO_ROOM_CAP)), "{fact:?}");
assert!(!monetize_server::poller::adopt_signup(&rig.state, "gunnar", "alice").expect("no-op"), "twice is once");
assert!(!monetize_server::poller::adopt_signup(&rig.state, "gunnar", "nobody").expect("no-op"), "no signup, nothing adopted");
rig.server.stop().await;
}