crates/monetize-server/src/estate_order/tests/first_link.rs
//! **The operator sends a laid site's first sign-in link again, to a new address** (owner
//! 2026-10-22: holger-rs was ordered with a placeholder address). See
//! [`crate::estate_order::first_link`].
use super::*;
use crate::estate_order::first_link;
use crate::services::estate_order::EstateOrderService;
use monetize_proto as pb;
use monetize_proto::estate_order_server::EstateOrder as _;
const NEW: &str = "owner@example.org";
/// A laid, ready operator site `gunnar-rs` (run `gunnarrs`), its ready mail sent once, its
/// custody still held (the hand-off waits for the site to take its installer material).
fn ready_site(name: &str) -> SiteRig {
let rig = site_rig_with(name, false);
let (state, pay, _cloud, kit, _dir, _clock, mails) =
(&rig.0, &rig.1, &rig.2, &rig.3, &rig.4, &rig.5, &rig.6);
kit.bundles.store(true, Ordering::SeqCst);
let run = site::start_site(
state,
"gunnar-rs",
"placeholder@example.invalid",
&EstateSizes::default(),
)
.unwrap();
pay.authorize(&run.handle.clone().unwrap().id);
drive(state, "gunnarrs");
assert_eq!(
state.estates.get("gunnarrs").unwrap().phase,
phase::CAPTURED
);
assert_eq!(mails.count("estate.ready"), 1);
assert!(
state.estates.custody.as_ref().unwrap().holds("gunnarrs"),
"the custody still holds the invite"
);
rig
}
/// ★ **Handed off, an address given: its main mints the link and mails it THERE** (HOL-FIRST-ADMIN,
/// holger-rs 2026-10-30: the recorded address was the order's placeholder, and `--to` was refused
/// after the hand-off). The address becomes the first admin's recorded one only once the signed
/// `sign_in_again` order is queued; the door is not asked, the shop mails nothing itself.
#[test]
fn a_handed_off_sites_first_link_goes_to_the_given_address_through_its_main() {
let (state, _pay, _cloud, kit, _dir, _clock, mails) = ready_site("first-link-handed-off");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Open);
state
.estates
.custody
.as_ref()
.unwrap()
.forget("gunnarrs")
.unwrap();
let words = first_link::first_link(&state, "gunnar-rs", NEW).expect("queued for the main");
assert!(
words.contains("o***@example.org") && !words.contains(NEW),
"{words}"
);
let orders = sign_in_orders(&state, "gunnar-rs");
assert_eq!(orders.len(), 1, "one signed order for gunnar-rs's main");
let body: monetize_embed::fleet::SignInAgain = serde_json::from_str(&orders[0].body).unwrap();
assert_eq!(
(body.account.as_str(), body.to.as_str()),
("ignalina", NEW),
"the main mails the given address"
);
let run = state.estates.get("gunnarrs").unwrap();
assert_eq!(
run.email, NEW,
"the given address is the first admin's recorded one now"
);
assert!(
run.history
.iter()
.any(|t| t.note
== "the first admin's address was set by the operator to o***@example.org"),
"{:#?}",
run.history
);
assert!(
!run.history.iter().any(|t| t.note.contains(NEW)),
"{:#?}",
run.history
);
assert_eq!(
mails.count("estate.ready"),
1,
"the shop mails nothing itself"
);
assert_eq!(kit.first_door_asks.load(Ordering::SeqCst), 0);
// Asked again within ten minutes: refused, and the recorded address stays.
assert!(matches!(
first_link::first_link(&state, "gunnar-rs", "other@example.org"),
Err(StartRefusal::NotNow(_))
));
assert_eq!(
state.estates.get("gunnarrs").unwrap().email,
NEW,
"a refused ask changes nothing"
);
assert_eq!(sign_in_orders(&state, "gunnar-rs").len(), 1);
}
fn as_operator<T>(r: T) -> tonic::Request<T> {
let mut req = tonic::Request::new(r);
req.extensions_mut().insert(crate::auth::Operator);
req
}
fn ask(to: &str) -> pb::EstateOrderFirstLinkRequest {
pb::EstateOrderFirstLinkRequest {
estate: "gunnar-rs".into(),
to: to.into(),
}
}
/// ★ **An unspent door: the address is set and the ready mail goes again** to it, with the
/// custody's link (the one the site holds); the history names the operator and a masked
/// address, never the link.
#[test]
fn an_unspent_sites_first_link_is_mailed_again_to_the_new_address() {
let (state, _pay, _cloud, kit, _dir, _clock, mails) = ready_site("first-link-open");
let first = mails.last();
let link_of = |body: &str| {
body.lines()
.map(str::trim)
.find(|l| l.contains("/login?invite="))
.expect("a first sign-in link")
.to_string()
};
*kit.first_door.lock().unwrap() = Some(FirstDoor::Open);
let words = first_link::first_link(&state, "gunnar-rs", NEW).expect("re-sent");
assert!(
words.contains("o***@example.org") && !words.contains("invite"),
"{words}"
);
let run = state.estates.get("gunnarrs").unwrap();
assert_eq!(run.email, NEW);
assert!(run.told);
assert_eq!(mails.count("estate.ready"), 2, "the ready mail went again");
let again = mails.last();
assert_eq!(again.to, NEW);
assert_eq!(
link_of(&again.body),
link_of(&first.body),
"the link the site holds: the custody's invite"
);
assert!(
run.history
.iter()
.any(|t| t.note == "first sign-in link re-sent by the operator to o***@example.org"),
"{:#?}",
run.history
);
assert!(
!run.history.iter().any(|t| t.note.contains("invite=")),
"the link is never written: {:#?}",
run.history
);
assert_eq!(
kit.first_door_asks.load(Ordering::SeqCst),
1,
"the site's door was asked once"
);
}
/// ★ **A spent door is refused by name**, and nothing changes or goes.
#[test]
fn a_spent_first_admin_door_is_refused_by_name() {
let (state, _pay, _cloud, kit, _dir, _clock, mails) = ready_site("first-link-spent");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Spent);
match first_link::first_link(&state, "gunnar-rs", NEW) {
Err(StartRefusal::NotNow(w)) => assert_eq!(w, "[site-first-link-spent] gunnar-rs's first admin has signed in already; use passkeys/recovery on the site"),
other => panic!("{other:?}"),
}
assert_eq!(
state.estates.get("gunnarrs").unwrap().email,
"placeholder@example.invalid"
);
assert_eq!(mails.count("estate.ready"), 1);
}
/// ★ **An invite the site no longer takes: its main mints a new link** (HOL-FIRST-ADMIN,
/// test2.holger.rs 2026-10-30: the custody's invite answered "this invite is not valid any more"
/// and `site first-link` refused, so the first admin had no way in). A new invite minted here
/// would open nothing on the site, so the ONE box asks the site's MAIN, as after a hand-off: the
/// signed `sign_in_again` order, to the given address or the recorded one; the shop mails nothing
/// itself, and the status says the main's answer.
#[test]
fn an_invite_the_site_no_longer_takes_is_minted_again_by_its_main() {
let (state, _pay, _cloud, kit, _dir, clock, mails) = ready_site("first-link-expired");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Closed(
"the invite gave no passkey challenge".into(),
));
let svc = EstateOrderService(state.clone());
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let r = rt
.block_on(svc.first_link(as_operator(ask(""))))
.expect("queued for the main")
.into_inner();
let orders = sign_in_orders(&state, "gunnar-rs");
assert_eq!(orders.len(), 1, "{}", r.words);
assert_eq!(
r.asked, orders[0].seq,
"the CLI follows the main's answer: {}",
r.words
);
assert!(
r.words.contains("no longer takes") && r.words.contains("p***@example.invalid"),
"{}",
r.words
);
let body: monetize_embed::fleet::SignInAgain = serde_json::from_str(&orders[0].body).unwrap();
assert_eq!(
(body.account.as_str(), body.to.as_str()),
("ignalina", "placeholder@example.invalid")
);
assert_eq!(
mails.count("estate.ready"),
1,
"the shop mails nothing itself"
);
assert!(
state.estates.custody.as_ref().unwrap().holds("gunnarrs"),
"nothing is forgotten"
);
// The status says the main's answer, the address masked.
let s = status_of(&state);
assert_eq!(
(s.first_link_answered, s.first_link_mailed),
(0, false),
"{}",
s.first_admin_door_words
);
ack(
&state,
r.asked,
true,
"a new sign-in link for ignalina (24 hours) was mailed to placeholder@example.invalid",
);
let s = status_of(&state);
assert_eq!(
(s.first_link_answered, s.first_link_mailed),
(r.asked, true),
"{}",
s.first_admin_door_words
);
assert!(
s.first_admin_door_words.contains("answered")
&& s.first_admin_door_words.contains("p***@example.invalid")
&& !s.first_admin_door_words.contains("placeholder@"),
"{}",
s.first_admin_door_words
);
// With an address: the main mails it there, and it is the recorded one from now on.
clock.fetch_add(SIGN_IN_AGAIN_EVERY_MS + 1, Ordering::SeqCst);
first_link::first_link(&state, "gunnar-rs", NEW).expect("queued again");
let body: monetize_embed::fleet::SignInAgain =
serde_json::from_str(&sign_in_orders(&state, "gunnar-rs")[0].body).unwrap();
assert_eq!(body.to, NEW);
assert_eq!(state.estates.get("gunnarrs").unwrap().email, NEW);
assert_eq!(mails.count("estate.ready"), 1);
}
/// ★ **A malformed address, and a site not laid and ready, are refused**; nothing is asked.
#[test]
fn a_malformed_address_and_a_site_not_ready_are_refused() {
let (state, _pay, _cloud, kit, _dir, _clock, mails) = ready_site("first-link-bad");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Open);
// FIRST-LINK-1: an empty address is the recorded one (no `--to`), not a malformed one.
for bad in [
"owner",
"owner@",
"@example.org",
"a b@example.org",
"a@b@example.org",
"x@example",
"a@example.org, b@example.org",
] {
assert!(
matches!(first_link::first_link(&state, "gunnar-rs", bad), Err(StartRefusal::BadName(w)) if w.starts_with("[site-first-link-address]")),
"{bad:?}"
);
}
assert!(matches!(
first_link::first_link(&state, "nope", NEW),
Err(StartRefusal::NotFound(_))
));
assert_eq!(mails.count("estate.ready"), 1);
assert_eq!(kit.first_door_asks.load(Ordering::SeqCst), 0);
// A site still waiting for its payment is not laid.
let (state, ..) = site_rig_with("first-link-unlaid", false);
site::start_site(
&state,
"gunnar-rs",
"placeholder@example.invalid",
&EstateSizes::default(),
)
.unwrap();
assert!(
matches!(first_link::first_link(&state, "gunnar-rs", NEW), Err(StartRefusal::NotNow(w)) if w.starts_with("[site-first-link-not-ready]"))
);
}
/// ★ **The service: the operator's alone** — the service bearer and a site's own bearer are
/// refused; the operator is heard, and a spent door reaches him whole.
#[test]
fn the_service_hears_the_operator_alone() {
let (state, _pay, _cloud, kit, _dir, _clock, mails) = ready_site("first-link-wire");
let svc = EstateOrderService(state.clone());
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
*kit.first_door.lock().unwrap() = Some(FirstDoor::Open);
assert_eq!(
rt.block_on(svc.first_link(tonic::Request::new(ask(NEW))))
.unwrap_err()
.code(),
tonic::Code::PermissionDenied
);
let mut a_site = as_operator(ask(NEW));
a_site
.extensions_mut()
.insert(crate::auth::CallerEstate("gunnar-rs".into()));
assert_eq!(
rt.block_on(svc.first_link(a_site)).unwrap_err().code(),
tonic::Code::PermissionDenied
);
assert_eq!(mails.count("estate.ready"), 1, "nothing went");
let r = rt
.block_on(svc.first_link(as_operator(ask(NEW))))
.expect("the operator is heard")
.into_inner();
assert!(r.words.contains("o***@example.org"), "{r:?}");
assert_eq!(state.estates.get("gunnarrs").unwrap().email, NEW);
assert_eq!(mails.count("estate.ready"), 2);
*kit.first_door.lock().unwrap() = Some(FirstDoor::Spent);
let spent = rt
.block_on(svc.first_link(as_operator(ask("other@example.org"))))
.unwrap_err();
assert_eq!(spent.code(), tonic::Code::FailedPrecondition);
assert!(
spent
.message()
.starts_with("[site-first-link-spent] gunnar-rs"),
"{spent:?}"
);
let bad = rt
.block_on(svc.first_link(as_operator(ask("nobody"))))
.unwrap_err();
assert_eq!(bad.code(), tonic::Code::InvalidArgument, "{bad:?}");
}
// ── FIRST-LINK-1 (owner 2026-10-27): a handed-off site's first link, by its main ───────────
const RECORDED: &str = "first-admin@mock.invalid";
/// A laid, ready operator site whose first admin's address is [`RECORDED`], handed off: the
/// custody forgot its secrets.
fn handed_off_site(name: &str) -> SiteRig {
let rig = site_rig_with(name, false);
let (state, pay, kit, mails) = (&rig.0, &rig.1, &rig.3, &rig.6);
kit.bundles.store(true, Ordering::SeqCst);
let run = site::start_site(state, "gunnar-rs", RECORDED, &EstateSizes::default()).unwrap();
pay.authorize(&run.handle.clone().unwrap().id);
drive(state, "gunnarrs");
assert_eq!(
state.estates.get("gunnarrs").unwrap().phase,
phase::CAPTURED
);
assert_eq!(mails.count("estate.ready"), 1);
state
.estates
.custody
.as_ref()
.unwrap()
.forget("gunnarrs")
.unwrap();
rig
}
fn sign_in_orders(state: &AppState, fleet: &str) -> Vec<monetize_embed::fleet::SignedOrder> {
let g = state.fleet.guardian().unwrap();
g.store
.orders_for(fleet, "main", 0)
.into_iter()
.filter(|o| o.kind == monetize_embed::fleet::kind::SIGN_IN_AGAIN)
.collect()
}
/// ★ **A handed-off site, no address given: its main mints and mails the link.** The ONE box
/// holds no invite after the hand-off, so `site first-link <site>` queues the signed
/// `sign_in_again` order for the site's MAIN under the site's LABEL (its boxes pull as
/// `gunnar-rs`, not `gunnarrs`), to the first admin's recorded address; the shop mails nothing
/// and asks no door; the words and the history mask the address; once per ten minutes.
#[test]
fn a_handed_off_sites_first_link_is_a_signed_order_for_its_main_under_its_label() {
let (state, _pay, _cloud, kit, _dir, clock, mails) = handed_off_site("first-link-by-main");
let words = first_link::first_link(&state, "gunnar-rs", "").expect("queued for the main");
assert!(
words.contains("f***@mock.invalid") && !words.contains(RECORDED),
"{words}"
);
let orders = sign_in_orders(&state, "gunnar-rs");
assert_eq!(orders.len(), 1, "one signed order for gunnar-rs's main");
assert!(
sign_in_orders(&state, "gunnarrs").is_empty(),
"never under the run's company name"
);
let o = &orders[0];
let pk =
ed25519_dalek::VerifyingKey::from_bytes(&state.signer_for("gunnar").public_key()).unwrap();
monetize_embed::fleet::verify_order(
o,
&pk,
("gunnar", "gunnar-rs", "main"),
clock.load(Ordering::SeqCst),
0,
)
.expect("signed for gunnar-rs's main");
let body: monetize_embed::fleet::SignInAgain = serde_json::from_str(&o.body).unwrap();
assert_eq!(
(body.account.as_str(), body.to.as_str(), body.host.as_str()),
("ignalina", RECORDED, "gunnar.rs")
);
assert!(
!o.body.contains("invite"),
"no login secret rides the order: {}",
o.body
);
assert_eq!(mails.count("estate.ready"), 1, "the shop mails nothing");
assert_eq!(
kit.first_door_asks.load(Ordering::SeqCst),
0,
"no door is asked: the ONE box holds no invite"
);
let run = state.estates.get("gunnarrs").unwrap();
assert_eq!(run.email, RECORDED);
let asked: Vec<_> = run
.history
.iter()
.filter(|t| t.note.contains("sign_in_again"))
.collect();
assert!(
asked.len() == 1
&& asked
.iter()
.all(|t| !t.note.contains(RECORDED) && t.note.contains("gunnar-rs's main")),
"{:#?}",
run.history
);
assert!(
matches!(
first_link::first_link(&state, "gunnar-rs", ""),
Err(StartRefusal::NotNow(_))
),
"once per ten minutes"
);
clock.fetch_add(SIGN_IN_AGAIN_EVERY_MS + 1, Ordering::SeqCst);
first_link::first_link(&state, "gunnar-rs", "").expect("again after the wait");
let again = sign_in_orders(&state, "gunnar-rs");
assert_eq!(
again.len(),
1,
"the newer supersedes the unpulled older one"
);
assert!(again[0].seq > o.seq);
}
/// ★ **An unspent site, no address given: the ready mail goes again to the recorded address.**
#[test]
fn an_unspent_sites_first_link_with_no_address_goes_to_the_recorded_one() {
let (state, _pay, _cloud, kit, _dir, _clock, mails) = ready_site("first-link-recorded");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Open);
let words = first_link::first_link(&state, "gunnar-rs", "").expect("re-sent");
assert!(words.contains("p***@example.invalid"), "{words}");
assert_eq!(mails.count("estate.ready"), 2);
assert_eq!(mails.last().to, "placeholder@example.invalid");
}
fn ack(state: &AppState, seq: u64, ok: bool, detail: &str) {
let g = state.fleet.guardian().unwrap();
let report = monetize_embed::fleet::Report {
acked: vec![monetize_embed::fleet::OrderAck {
seq,
ok,
detail: detail.into(),
receipt_json: String::new(),
}],
..Default::default()
};
g.store
.record_report("gunnar-rs", "main", state.now(), &report)
.unwrap();
}
fn status_door(state: &Arc<AppState>) -> (String, String) {
let svc = EstateOrderService(state.clone());
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let r = rt
.block_on(svc.status(as_operator(pb::EstateOrderStatusRequest {
company: "gunnar-rs".into(),
actor_ticket: Vec::new(),
})))
.unwrap()
.into_inner();
(r.first_admin_door, r.first_admin_door_words)
}
/// ★ **`site status` says the first-admin door**: open, spent or expired while the custody
/// holds the invite (the site's door is asked), handed off after; for a handed-off site the
/// main's newest answer, its address masked — spent when it says a passkey is held.
#[test]
fn the_site_status_says_whether_the_first_admin_door_is_open_spent_expired_or_handed_off() {
let (state, _pay, _cloud, kit, _dir, _clock, _mails) = ready_site("first-link-status");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Open);
assert_eq!(status_door(&state).0, "open");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Spent);
assert_eq!(status_door(&state).0, "spent");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Closed("no passkey challenge".into()));
assert_eq!(status_door(&state).0, "expired");
let (state, ..) = handed_off_site("first-link-status-handed-off");
let (door, words) = status_door(&state);
assert_eq!(door, "handed_off", "{words}");
first_link::first_link(&state, "gunnar-rs", "").unwrap();
let (door, words) = status_door(&state);
assert!(
door == "handed_off" && words.contains("not pulled"),
"{door}: {words}"
);
let seq = sign_in_orders(&state, "gunnar-rs")[0].seq;
ack(
&state,
seq,
true,
&format!("a new sign-in link for ignalina (24 hours) was mailed to {RECORDED}"),
);
let (door, words) = status_door(&state);
assert!(
door == "handed_off" && words.contains("f***@mock.invalid") && !words.contains(RECORDED),
"{door}: {words}"
);
ack_again(&state);
let (door, words) = status_door(&state);
assert!(
door == "spent" && words.contains("passkey") && !words.contains(RECORDED),
"{door}: {words}"
);
}
/// The main refuses the next ask: its first admin holds a passkey now.
fn ack_again(state: &Arc<AppState>) {
if let Some(mut r) = state.estates.get("gunnarrs") {
r.sign_in_again_at_ms = 0;
let _pen = state
.estates
.pen_for_a_call(state.estates.cfg.as_ref().unwrap(), &r.company)
.unwrap();
crate::estate_order::save(state, &r).unwrap();
}
first_link::first_link(state, "gunnar-rs", "").unwrap();
let seq = sign_in_orders(state, "gunnar-rs")[0].seq;
ack(state, seq, false, &format!("sign_in_again: ignalina already signs in with a passkey (1 held) — mailed nothing to {RECORDED}"));
}
/// Pure: every address in a box's words is masked, the rest kept.
#[test]
fn every_address_in_a_mains_answer_is_masked() {
assert_eq!(
first_link::masked_in(
"mailed to first-admin@mock.invalid (24 hours); cc <b@mock.invalid>."
),
"mailed to f***@mock.invalid (24 hours); cc <b***@mock.invalid>."
);
assert_eq!(first_link::masked_in("no address here"), "no address here");
}
// ── HOL-MAIL (2026-10-27): the answer is the main's, never a promise ─────────────────────────
/// The site's whole status, as the operator's door reads it.
fn status_of(state: &Arc<AppState>) -> pb::EstateOrderStatusResponse {
let svc = EstateOrderService(state.clone());
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(svc.status(as_operator(pb::EstateOrderStatusRequest {
company: "gunnar-rs".into(),
actor_ticket: Vec::new(),
})))
.unwrap()
.into_inner()
}
/// What holger-rs's main answered on 2026-10-27: the shop fed it no relay.
const NO_RELAY: &str = "this box cannot mail (mail is off: this box was fed no relay (secret.smtp_url), so it dials nothing), so it mints no sign-in link for ignalina it could not mail";
/// ★ **A handed-off site's first link promises nothing** (HOL-MAIL, holger-rs 2026-10-27): the CLI
/// said "a new sign-in link is on its way: holger.rs mints it and mails it" while the site's main
/// refused the order (no relay was fed to it), so the owner waited for a mail that never came.
/// The answer names the signed order it queued (`asked`) and says nothing is mailed until the
/// main answers; the status says the main's answer to the newest order it answered
/// (`first_link_answered`), refused by name with its reason, or minted and mailed.
#[test]
fn a_handed_off_sites_first_link_promises_nothing_and_the_status_says_the_mains_answer() {
let (state, ..) = handed_off_site("first-link-says-the-answer");
let svc = EstateOrderService(state.clone());
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let r = rt
.block_on(svc.first_link(as_operator(ask(""))))
.expect("queued for the main")
.into_inner();
let seq = sign_in_orders(&state, "gunnar-rs")[0].seq;
assert_eq!(
r.asked, seq,
"the answer names the order it queued: {}",
r.words
);
for promise in [
"on its way",
"mints it and mails it",
"mints the link and mails it",
] {
assert!(
!r.words.contains(promise),
"a promise ({promise}): {}",
r.words
);
}
assert!(
r.words.contains("nothing is mailed until")
&& r.words.contains("f***@mock.invalid")
&& !r.words.contains(RECORDED),
"{}",
r.words
);
let s = status_of(&state);
assert_eq!(
(s.first_link_answered, s.first_link_mailed),
(0, false),
"not answered yet: {}",
s.first_admin_door_words
);
// The main refuses it: the status says so, with the main's reason.
ack(&state, seq, false, NO_RELAY);
let s = status_of(&state);
assert_eq!(
(s.first_link_answered, s.first_link_mailed),
(seq, false),
"{}",
s.first_admin_door_words
);
assert!(
s.first_admin_door_words.contains("refused")
&& s.first_admin_door_words
.contains("fed no relay (secret.smtp_url)"),
"{}",
s.first_admin_door_words
);
// A later ask, minted and mailed: the newest answer, its address masked.
if let Some(mut run) = state.estates.get("gunnarrs") {
run.sign_in_again_at_ms = 0;
let _pen = state
.estates
.pen_for_a_call(state.estates.cfg.as_ref().unwrap(), &run.company)
.unwrap();
crate::estate_order::save(&state, &run).unwrap();
}
let again = first_link::first_link_asking(&state, "gunnar-rs", "").expect("asked again");
assert!(
again.asked > seq,
"a newer order: {} after {seq}",
again.asked
);
let s = status_of(&state);
assert_eq!(
(s.first_link_answered, s.first_link_mailed),
(seq, false),
"the newer order is not answered yet"
);
ack(
&state,
again.asked,
true,
&format!("a new sign-in link for ignalina (24 hours) was mailed to {RECORDED}"),
);
let s = status_of(&state);
assert_eq!(
(s.first_link_answered, s.first_link_mailed),
(again.asked, true),
"{}",
s.first_admin_door_words
);
assert!(
s.first_admin_door_words.contains("f***@mock.invalid")
&& !s.first_admin_door_words.contains(RECORDED),
"{}",
s.first_admin_door_words
);
// A site whose custody still holds the invite asks no main: nothing to follow.
let (state, _pay, _cloud, kit, ..) = ready_site("first-link-no-main");
*kit.first_door.lock().unwrap() = Some(FirstDoor::Open);
assert_eq!(
first_link::first_link_asking(&state, "gunnar-rs", NEW)
.unwrap()
.asked,
0
);
}