crates/monetize-server/src/estate_order/tests/start_retry.rs
//! ★ **F-B-01 (A17, 2026-10-23): the same buyer's retry of `EstateOrder.Start`.** A Start that
//! ran past the relay's 20 s answers 504, and the buyer's page asks again: the SAME account (a
//! visitor: the same new account at the same address), the same name, the same sizes, while its
//! order is still ORDERED and its page payable, is answered that order's own checkout page — no
//! second hold. Anyone else still reads "taken".
use monetize_proto as pb;
use monetize_proto::estate_order_server::EstateOrder as _;
use super::*;
use crate::services::estate_order::EstateOrderService;
/// A retry of the same order is that order: the same page, no second hold.
#[test]
fn the_same_accounts_retry_is_answered_its_own_unpaid_orders_page() {
let r = rig("start-retry");
let first = start_for(
&r.state,
"acme",
"buyer@example.com",
"",
"alice",
&EstateSizes::default(),
)
.unwrap();
let held = r.pay.0.requests.lock().unwrap().len();
let again = start_for(
&r.state,
"acme",
"buyer@example.com",
"",
"alice",
&EstateSizes::default(),
)
.expect("the buyer's own retry is answered, not taken");
assert_eq!(
(
again.reference.as_str(),
again.checkout_url.as_str(),
again.handle.clone()
),
(
first.reference.as_str(),
first.checkout_url.as_str(),
first.handle.clone()
)
);
assert!(!again.checkout_url.is_empty());
assert_eq!(
r.pay.0.requests.lock().unwrap().len(),
held,
"no second hold"
);
assert_eq!(
r.state.estates.get("acme").unwrap().ordered_at_ms,
first.ordered_at_ms,
"the run is the same"
);
}
/// Another account, other sizes, or a page past its time: "taken", as before.
#[test]
fn another_account_other_sizes_or_a_stale_page_are_still_taken() {
let r = rig("start-retry-taken");
start_for(
&r.state,
"acme",
"buyer@example.com",
"",
"alice",
&EstateSizes::default(),
)
.unwrap();
assert!(
matches!(
start_for(
&r.state,
"acme",
"bob@example.com",
"",
"bob",
&EstateSizes::default()
),
Err(StartRefusal::Taken(_))
),
"another account"
);
let bigger = EstateSizes {
main_data_gib: 200,
..EstateSizes::default()
};
assert!(
matches!(
start_for(&r.state, "acme", "buyer@example.com", "", "alice", &bigger),
Err(StartRefusal::Taken(_))
),
"other sizes are another order"
);
r.clock.fetch_add(2 * 3_600_000, Ordering::SeqCst);
assert!(
matches!(
start_for(
&r.state,
"acme",
"buyer@example.com",
"",
"alice",
&EstateSizes::default()
),
Err(StartRefusal::Taken(_))
),
"a page past its time is not answered again"
);
// A paid order is never answered as a page to pay again.
let r = rig("start-retry-paid");
let run = start_for(
&r.state,
"acme",
"buyer@example.com",
"",
"alice",
&EstateSizes::default(),
)
.unwrap();
r.pay.authorize(&run.handle.unwrap().id);
drive(&r.state, "acme");
assert!(matches!(
start_for(
&r.state,
"acme",
"buyer@example.com",
"",
"alice",
&EstateSizes::default()
),
Err(StartRefusal::Taken(_))
));
}
const WELCOME: &str =
"https://gunnar.rs/welcome?session={session}&next=%2Fsignup%3Fplan%3Dprivate%26company%3Dacme";
fn visitor(account: &str, email: &str) -> pb::EstateOrderStartRequest {
pb::EstateOrderStartRequest {
company: "acme".into(),
email: email.into(),
account: account.into(),
new_account: true,
welcome_url: WELCOME.into(),
..Default::default()
}
}
/// A visitor (no account yet) retries with the same new account and address: its own page.
/// Another address for that name is refused, as before.
#[test]
fn a_visitors_retry_with_the_same_name_and_address_is_answered_its_own_page() {
let r = rig("start-retry-visitor");
r.mails.3.store(true, Ordering::SeqCst);
let plans = std::collections::BTreeMap::from([(
"gunnar/account".to_string(),
crate::config::SignupPlan {
amount_minor: 0,
currency: monetize_pay::Currency::Sek,
cap_bytes: 0,
period_days: 0,
},
)]);
r.state.signup_plans.set(plans, Default::default());
let svc = EstateOrderService(r.state.clone());
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let first = rt
.block_on(svc.start(tonic::Request::new(visitor("newbie", "new@example.com"))))
.expect("a visitor's order starts")
.into_inner();
let session = r
.state
.estates
.get("acme")
.unwrap()
.new_account
.unwrap()
.session;
let again = rt
.block_on(svc.start(tonic::Request::new(visitor("newbie", "New@Example.com"))))
.expect("the visitor's own retry is answered")
.into_inner();
assert_eq!(
(again.reference.as_str(), again.checkout_url.as_str()),
(first.reference.as_str(), first.checkout_url.as_str())
);
let run = r.state.estates.get("acme").unwrap();
assert_eq!(
run.new_account.as_ref().unwrap().session,
session,
"the order's own account, as it was"
);
let hold = r
.state
.signups
.name_hold(
"gunnar",
&r.state.registry.home_of("gunnar").unwrap_or_default(),
"newbie",
r.state.now() as i64,
)
.unwrap()
.expect("the name is still held for the page");
assert_eq!(hold.session, session, "held for the order's own session");
let other = rt
.block_on(svc.start(tonic::Request::new(visitor("newbie", "other@example.com"))))
.unwrap_err();
assert_ne!(other.code(), tonic::Code::Ok);
assert_eq!(
r.state
.estates
.get("acme")
.unwrap()
.new_account
.unwrap()
.email,
"new@example.com"
);
}