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

nordisk/monetize

crates/monetize-server/src/signup/tests.rs

//! The store, the checks and the engine, on the host. The webhook and the gRPC
//! service are driven over their real wires in `tests/paid_signup.rs`.

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

use monetize_pay::{Currency, Money};

use super::*;
use crate::config::SignupPlan;

pub const PLAN: &str = "gunnar/10gb-1m";

/// The plan table a test deployment sells, in the shape `monetize.toml` writes:
/// `[signups.gunnar.plans."gunnar/10gb-1m"] amount_minor = 10000, currency = "Sek"`.
pub fn plans() -> BTreeMap<String, SignupPlan> {
    BTreeMap::from([(
        PLAN.to_string(),
        SignupPlan {
            amount_minor: 10_000,
            currency: Currency::Sek,
            cap_bytes: 10_737_418_240,
            period_days: 30,
        },
    )])
}

pub 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,
    }
}

/// A sink that counts, so "created once" is a measured number and not a guess.
pub struct CountingSink {
    /// **What `name_verdict` says**, and it is a list of names this sink holds
    /// rather than a flag: the whole question `Signup.CheckName` and
    /// `Signup.Rename` ask is about ONE name, and a sink that refused every name
    /// or none could not show that a rename to a free one works.
    pub taken: Mutex<Vec<String>>,
    /// Whether the sink can even be ASKED. A name check that cannot reach the
    /// product is not "taken", and the tests hold that apart.
    pub reachable: Mutex<bool>,
    pub calls: AtomicU64,
    pub answer: Mutex<Result<SignupOutcome, SignupError>>,
}

impl CountingSink {
    pub fn creating() -> Arc<CountingSink> {
        Arc::new(CountingSink {
            taken: Mutex::new(Vec::new()),
            reachable: Mutex::new(true),
            calls: AtomicU64::new(0),
            answer: Mutex::new(Ok(SignupOutcome::Created {
                namespace: "bob".into(),
                username: "bob".into(),
            })),
        })
    }
    pub fn answering(a: Result<SignupOutcome, SignupError>) -> Arc<CountingSink> {
        Arc::new(CountingSink {
            taken: Mutex::new(Vec::new()),
            reachable: Mutex::new(true),
            calls: AtomicU64::new(0),
            answer: Mutex::new(a),
        })
    }
    /// This deployment already has these names.
    pub fn holding(names: &[&str]) -> Arc<CountingSink> {
        let sink = CountingSink::creating();
        *sink.taken.lock().unwrap() = names.iter().map(|n| (*n).to_owned()).collect();
        sink
    }
}

impl SignupSink for CountingSink {
    fn paid(&self, _s: &PaidSignup) -> Result<SignupOutcome, SignupError> {
        self.calls.fetch_add(1, Ordering::SeqCst);
        self.answer.lock().unwrap().clone()
    }

    fn name_verdict(&self, username: &str) -> Result<NameVerdict, SignupError> {
        if !*self.reachable.lock().unwrap() {
            return Err(SignupError::Unavailable(
                "the product is not answering".into(),
            ));
        }
        if self.taken.lock().unwrap().iter().any(|n| n == username) {
            return Ok(NameVerdict::Taken(format!(
                "username {username:?} is taken on this appliance"
            )));
        }
        Ok(NameVerdict::Free)
    }
}

/// An `AppState` whose clock a test can move, with the sink and the plans it asks for.
pub fn state_with(sink: Arc<dyn SignupSink>, now: Arc<AtomicU64>) -> Arc<crate::state::AppState> {
    let mut state = crate::state::AppState::in_memory(crate::registry::Registry::new());
    state.signup_sink = sink;
    state.signup_plans = plans().into();
    let tick = Arc::clone(&now);
    state.clock = Box::new(move || tick.load(Ordering::SeqCst));
    Arc::new(state)
}

#[test]
fn the_second_webhook_for_a_session_creates_nothing_and_answers_what_the_first_did() {
    let sink = CountingSink::creating();
    let now = Arc::new(AtomicU64::new(1_788_436_800_000));
    let state = state_with(sink.clone(), now);
    let s = a_signup("cs_1", "bob");

    let first = settle(&state, &s);
    assert!(
        matches!(&first, SignupState::Created { username, namespace, .. } if username == "bob" && namespace == "bob"),
        "{first:?}"
    );
    // The vendor retries. It must be a no-op, and it must still say Created.
    let second = settle(&state, &s);
    assert_eq!(first, second, "the retry answered something else");
    assert_eq!(
        sink.calls.load(Ordering::SeqCst),
        1,
        "the sink ran twice for one payment"
    );
    assert_eq!(
        state.signups.list(0).unwrap().len(),
        1,
        "one payment, one row"
    );
}

#[test]
fn a_username_that_is_not_a_name_is_refused_before_the_sink_is_asked() {
    let sink = CountingSink::creating();
    let now = Arc::new(AtomicU64::new(2));
    let state = state_with(sink.clone(), now);
    let out = settle(&state, &a_signup("cs_2", "Bob!"));
    let SignupState::Refused { reason, .. } = out else {
        panic!("{out:?}")
    };
    assert!(reason.contains("Bob!"), "{reason}");
    assert!(
        reason.contains(monetize_pay::signup::USERNAME_RULE),
        "{reason}"
    );
    assert_eq!(
        sink.calls.load(Ordering::SeqCst),
        0,
        "a refused name still reached the sink"
    );
}

#[test]
fn a_plan_the_config_does_not_name_is_refused_by_name() {
    let sink = CountingSink::creating();
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(3)));
    let mut s = a_signup("cs_3", "bob");
    s.plan = "gunnar/1tb-forever".into();
    let out = settle(&state, &s);
    let SignupState::Refused { reason, .. } = out else {
        panic!("{out:?}")
    };
    assert!(reason.contains("gunnar/1tb-forever"), "{reason}");
    assert!(
        reason.contains(PLAN),
        "the refusal did not say what IS sold: {reason}"
    );
    assert_eq!(sink.calls.load(Ordering::SeqCst), 0);
}

#[test]
fn an_amount_that_is_not_the_plans_price_is_refused_with_both_numbers() {
    let sink = CountingSink::creating();
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(4)));
    let mut s = a_signup("cs_4", "bob");
    s.amount = Money {
        minor: 1,
        currency: Currency::Sek,
    };
    let out = settle(&state, &s);
    let SignupState::Refused { reason, .. } = out else {
        panic!("{out:?}")
    };
    assert!(
        reason.contains("100.00 SEK") && reason.contains("paid 0.01 SEK"),
        "both amounts, in kronor: {reason}"
    );
    assert_eq!(sink.calls.load(Ordering::SeqCst), 0);

    // The same number in the wrong currency is a mismatch too, never converted.
    let mut s = a_signup("cs_5", "bob");
    s.amount = Money {
        minor: 10_000,
        currency: Currency::Eur,
    };
    let out = settle(&state, &s);
    assert!(matches!(out, SignupState::Refused { .. }), "{out:?}");
}

/// **The refusal that would have taken a customer's money and given them
/// nothing.** A checkout switched to collect 25 % VAT hands up a total a quarter
/// larger than the plan price. Measured against the GROSS that reads as a page
/// whose price drifted — and `SignupError::Refused` is final and never retried,
/// so the charge stands and no account is ever made. The plan price is a NET
/// price, and the net is what it is compared against.
#[test]
fn a_signup_that_collected_vat_is_measured_on_its_net_and_not_its_total() {
    let plans = plans();

    // 100,00 SEK + 25 % moms: 125,00 taken, 25,00 of it the state's.
    let mut taxed = a_signup("cs_vat", "bob");
    taxed.amount = Money {
        minor: 12_500,
        currency: Currency::Sek,
    };
    taxed.tax = Money {
        minor: 2_500,
        currency: Currency::Sek,
    };
    assert_eq!(
        refusal(&plans, &taxed),
        None,
        "the net is the plan price; the tax was never the deployment's"
    );

    // The drift check still bites underneath the tax: 90,00 + moms is still a
    // page whose price drifted, and the refusal names the net it measured.
    let mut drifted = a_signup("cs_drift", "bob");
    drifted.amount = Money {
        minor: 11_250,
        currency: Currency::Sek,
    };
    drifted.tax = Money {
        minor: 2_250,
        currency: Currency::Sek,
    };
    let (code, reason) =
        refusal(&plans, &drifted).expect("a net of 90.00 SEK is not the plan's 100.00 SEK");
    assert!(
        reason.contains("90.00 SEK"),
        "the refusal names the net it measured, in kronor: {reason}"
    );
    assert_eq!(
        code,
        Refusal::Final,
        "a drifted price is not something another username fixes"
    );
}

/// **Every amount in the refusal is in major units with its currency**
/// (owner, live 2026-10-12: the console read "costs 11000 SEK here and the
/// vendor took 12500 SEK" for a 125.00 SEK payment — öre printed as kronor).
#[test]
fn a_price_refusal_says_every_amount_in_kronor_never_in_ore() {
    let mut plans = plans();
    plans.get_mut("gunnar/10gb-1m").unwrap().amount_minor = 11_000;
    let mut kicka = a_signup("cs_kicka", "kicka");
    kicka.amount = Money {
        minor: 12_500,
        currency: Currency::Sek,
    };
    kicka.tax = Money {
        minor: 2_500,
        currency: Currency::Sek,
    };
    let (_, reason) = refusal(&plans, &kicka).expect("a net of 100.00 is not the plan's 110.00");
    assert_eq!(
        reason,
        "paid 125.00 SEK (25.00 SEK of it tax, 100.00 SEK net), but plan \"gunnar/10gb-1m\" costs 110.00 SEK here",
        "major units, the currency beside each figure"
    );
    for ore in ["11000", "12500", "2500", "10000"] {
        assert!(
            !reason.contains(ore),
            "no öre figure in the words: {reason}"
        );
    }
}

/// The rows written before the fix are re-said in kronor when the console
/// reads them; any other reason is untouched.
#[test]
fn a_price_refusal_stored_in_ore_is_shown_in_kronor() {
    let old = "plan \"gunnar/10gb-1m\" costs 11000 SEK here and the vendor took 12500 SEK (2500 of it tax), a net of 10000 SEK";
    assert_eq!(
        crate::signup::legacy_price_refusal(old).as_deref(),
        Some("paid 125.00 SEK (25.00 SEK of it tax, 100.00 SEK net), but plan \"gunnar/10gb-1m\" costs 110.00 SEK here")
    );
    assert_eq!(
        crate::signup::legacy_price_refusal("username \"Bob!\" contains '!'"),
        None
    );
}

#[test]
fn a_sink_that_refuses_by_name_stores_the_name_and_is_not_retried() {
    let sink =
        CountingSink::answering(Err(SignupError::Refused("gunnar already has a bob".into())));
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(5)));
    let out = settle(&state, &a_signup("cs_6", "bob"));
    let SignupState::Refused { reason, .. } = out else {
        panic!("{out:?}")
    };
    assert_eq!(reason, "gunnar already has a bob");
    // A refusal is final: the poll reads the row and calls nobody.
    let again = status_with_retry(&state, "mockpay", "cs_6")
        .unwrap()
        .unwrap();
    assert_eq!(
        again.state,
        SignupState::Refused {
            reason: "gunnar already has a bob".into(),
            at_unix_ms: 5,
            refusal: Refusal::Final,
            words: Some(words::COULD_NOT_CREATE.into()),
            owed: Owed::RefundAndTheNameMayBeSpent,
        }
    );
    assert_eq!(sink.calls.load(Ordering::SeqCst), 1);
}

#[test]
fn unavailable_keeps_it_pending_and_the_next_poll_tries_again() {
    let sink = CountingSink::answering(Err(SignupError::Unavailable(
        "gunnar is not answering".into(),
    )));
    let now = Arc::new(AtomicU64::new(1_000));
    let state = state_with(sink.clone(), Arc::clone(&now));
    let s = a_signup("cs_7", "bob");

    assert_eq!(settle(&state, &s), SignupState::Pending);
    // The return page polls. That is what drives the retry — nothing else will.
    let polled = status_with_retry(&state, "mockpay", "cs_7")
        .unwrap()
        .unwrap();
    assert_eq!(polled.state, SignupState::Pending);
    assert_eq!(
        sink.calls.load(Ordering::SeqCst),
        2,
        "the poll did not retry"
    );

    // The product comes back. The next poll creates the customer, through the
    // same code the webhook would have.
    *sink.answer.lock().unwrap() = Ok(SignupOutcome::Created {
        namespace: "bob".into(),
        username: "bob".into(),
    });
    now.store(2_000, Ordering::SeqCst);
    let polled = status_with_retry(&state, "mockpay", "cs_7")
        .unwrap()
        .unwrap();
    assert_eq!(
        polled.state,
        SignupState::Created {
            namespace: "bob".into(),
            username: "bob".into(),
            at_unix_ms: 2_000
        }
    );
    // And it stayed created: the poll after that calls nobody.
    let calls = sink.calls.load(Ordering::SeqCst);
    status_with_retry(&state, "mockpay", "cs_7").unwrap();
    assert_eq!(sink.calls.load(Ordering::SeqCst), calls);
}

#[test]
fn a_signup_pending_for_a_day_becomes_a_named_refusal() {
    let sink = CountingSink::answering(Err(SignupError::Unavailable(
        "gunnar is not answering".into(),
    )));
    let now = Arc::new(AtomicU64::new(1_000));
    let state = state_with(sink, Arc::clone(&now));
    assert_eq!(
        settle(&state, &a_signup("cs_8", "bob")),
        SignupState::Pending
    );

    // A minute short of the ceiling it is still a retry.
    now.store(1_000 + PENDING_CEILING_MS as u64 - 60_000, Ordering::SeqCst);
    assert!(status_with_retry(&state, "mockpay", "cs_8")
        .unwrap()
        .unwrap()
        .state
        .is_pending());

    // Past it, the customer is owed an answer, and it names why.
    now.store(1_000 + PENDING_CEILING_MS as u64, Ordering::SeqCst);
    let out = status_with_retry(&state, "mockpay", "cs_8")
        .unwrap()
        .unwrap();
    let SignupState::Refused { reason, .. } = out.state else {
        panic!("{out:?}")
    };
    assert!(reason.starts_with(NEVER_ANSWERED), "{reason}");
    assert!(
        reason.contains("gunnar is not answering"),
        "the outage's own words were dropped: {reason}"
    );
}

#[test]
fn a_session_nobody_has_seen_is_absent_and_not_an_error() {
    let state = state_with(CountingSink::creating(), Arc::new(AtomicU64::new(9)));
    assert_eq!(
        status_with_retry(&state, "mockpay", "cs_never").unwrap(),
        None
    );
}

#[test]
fn the_loud_sink_creates_nothing_and_answers_created() {
    let s = a_signup("cs_10", "bob");
    let out = LoudSink.paid(&s).expect("the open sink answers");
    assert_eq!(
        out,
        SignupOutcome::Created {
            namespace: "bob".into(),
            username: "bob".into()
        }
    );
}

#[test]
fn the_store_lists_newest_first_and_keeps_the_email_off_nothing_but_itself() {
    let state = state_with(CountingSink::creating(), Arc::new(AtomicU64::new(100)));
    settle(&state, &a_signup("cs_a", "bob"));
    let rows = state.signups.list(0).unwrap();
    assert_eq!(rows.len(), 1);
    // The record keeps it, because a retry needs it. What goes on the wire is
    // `services::signup`'s business and is asserted there.
    assert_eq!(rows[0].signup.email, "bob@example.com");
    assert_eq!(rows[0].recorded_at_unix_ms, 100);
}

/// ★ **A settled paid signup is a sale the tax return counts — exactly once.**
/// 125.00 SEK taken incl. 25 % moms on 2026-09-15: 100.00 net and 25.00 VAT in
/// 2026-Q3, however many times the vendor and the return page hand it up.
#[test]
fn a_created_signup_is_one_tax_sale_in_its_quarter_and_a_replay_adds_nothing() {
    let state = state_with(
        CountingSink::creating(),
        Arc::new(AtomicU64::new(1_789_468_200_000)),
    );
    let mut paid = a_signup("cs_live_q3", "bob");
    paid.amount = Money {
        minor: 12_500,
        currency: Currency::Sek,
    };
    paid.tax = Money {
        minor: 2_500,
        currency: Currency::Sek,
    };
    paid.paid_at_unix_ms = 1_789_468_154_000; // 2026-09-15T10:29:14Z
    assert!(matches!(settle(&state, &paid), SignupState::Created { .. }));
    // The replay: a second webhook, a return-page poll.
    settle(&state, &paid);
    assert!(matches!(
        status_with_retry(&state, "mockpay", "cs_live_q3")
            .unwrap()
            .unwrap()
            .state,
        SignupState::Created { .. }
    ));
    // A refused signup is money owed back, not a sale.
    settle(&state, &a_signup("cs_refused", "Bob!"));

    let books = monetize::Books {
        home_country: "SE".into(),
        supplier_countries: BTreeMap::new(),
    };
    let sales = tax_sales(&state.signups, &books).unwrap();
    assert_eq!(sales.len(), 1, "one payment, one sale: {sales:?}");
    let s = &sales[0];
    assert_eq!(s.reference, "signup:mockpay:cs_live_q3");
    assert_eq!(
        (
            s.net.minor,
            s.tax.minor,
            s.basis_points,
            s.customer_country.as_str()
        ),
        (10_000, 2_500, 2_500, "SE")
    );

    let view = monetize::tax::LedgerView {
        home_country: "SE".into(),
        sales,
    };
    let quarters = monetize::tax::PeriodKind::Quarterly.periods(2026);
    assert_eq!(view.in_period(&quarters[2]).count(), 1, "2026-Q3 holds it");
    assert_eq!(
        quarters
            .iter()
            .map(|q| view.in_period(q).count())
            .sum::<usize>(),
        1,
        "and no other quarter does"
    );
}

/// A sink that creates and records every welcome resend it is asked for.
struct MailSink {
    resent: Mutex<Vec<(String, String, bool)>>,
}

impl SignupSink for MailSink {
    fn paid(&self, s: &PaidSignup) -> Result<SignupOutcome, SignupError> {
        // The product settles on a name that differs from the one asked for,
        // so the test can see which one the welcome is sent with.
        Ok(SignupOutcome::Created {
            namespace: format!("{}-ns", s.username),
            username: format!("{}-made", s.username),
        })
    }
    fn name_verdict(&self, _: &str) -> Result<NameVerdict, SignupError> {
        Ok(NameVerdict::Free)
    }
    fn resend_welcome(&self, s: &PaidSignup, force: bool) -> Result<String, SignupError> {
        self.resent
            .lock()
            .unwrap()
            .push((s.username.clone(), s.email.clone(), force));
        Ok(format!("sent to {}", s.email))
    }
}

/// **The resend door: only a CREATED signup, with the name the product made.**
#[test]
fn resend_welcome_reaches_the_sink_only_for_a_created_signup_with_its_settled_name() {
    let sink = Arc::new(MailSink {
        resent: Mutex::new(Vec::new()),
    });
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(100)));
    assert_eq!(
        resend_welcome(&state, "mockpay", "cs_none", false),
        Err(ResendRefusal::Unknown)
    );

    settle(&state, &a_signup("cs_ok", "bob"));
    let out = resend_welcome(&state, "mockpay", "cs_ok", true)
        .expect("a created signup can be welcomed again");
    assert_eq!(out, "sent to bob@example.com");
    assert_eq!(
        *sink.resent.lock().unwrap(),
        vec![("bob-made".to_owned(), "bob@example.com".to_owned(), true)],
        "the settled name, the stored address, the operator's force"
    );

    // Refused over the shape rule: nothing was created, nothing to welcome.
    settle(&state, &a_signup("cs_bad", "Bob!"));
    assert!(
        matches!(resend_welcome(&state, "mockpay", "cs_bad", false), Err(ResendRefusal::NotSendable(why)) if why.contains("not CREATED"))
    );
    assert_eq!(
        sink.resent.lock().unwrap().len(),
        1,
        "the sink was asked about a signup with no account"
    );
}

/// The open sink sends no mail and says so by name.
#[test]
fn the_default_resend_is_a_refusal_by_name_never_a_quiet_sent() {
    let state = state_with(CountingSink::creating(), Arc::new(AtomicU64::new(100)));
    settle(&state, &a_signup("cs_ok", "bob"));
    assert!(
        matches!(resend_welcome(&state, "mockpay", "cs_ok", false), Err(ResendRefusal::NotSendable(why)) if why.contains("sends no welcome mail"))
    );
}

#[test]
fn refusal_checks_the_three_assertions_a_stranger_made() {
    let plans = plans();
    assert_eq!(refusal(&plans, &a_signup("cs", "bob")), None);
    assert!(refusal(&BTreeMap::new(), &a_signup("cs", "bob"))
        .unwrap()
        .1
        .contains("not sold here"));
}

/// ★ **The refusal says whether another name would fix it, as a VALUE.**
///
/// The page that has to choose between offering one text field and offering the
/// support address reads this, and nothing else. Before it existed the only
/// evidence was the prose, which is a thing somebody improves.
#[test]
fn a_refusal_records_whether_another_name_would_fix_it() {
    // The name: the shape rule, and a sink that says the name is held.
    let state = state_with(CountingSink::creating(), Arc::new(AtomicU64::new(1)));
    let out = settle(&state, &a_signup("cs_shape", "Bob!"));
    assert!(
        matches!(
            out,
            SignupState::Refused {
                refusal: Refusal::Name,
                ..
            }
        ),
        "{out:?}"
    );

    let taken = CountingSink::answering(Err(SignupError::NameTaken(
        "username \"bob\" is taken on this appliance".into(),
    )));
    let state = state_with(taken, Arc::new(AtomicU64::new(1)));
    let out = settle(&state, &a_signup("cs_taken", "bob"));
    assert!(
        matches!(
            out,
            SignupState::Refused {
                refusal: Refusal::Name,
                ..
            }
        ),
        "{out:?}"
    );

    // The order: a plan nobody sells. Nothing the customer types changes it.
    let state = state_with(CountingSink::creating(), Arc::new(AtomicU64::new(1)));
    let mut s = a_signup("cs_plan", "bob");
    s.plan = "gunnar/1tb-forever".into();
    let out = settle(&state, &s);
    assert!(
        matches!(
            out,
            SignupState::Refused {
                refusal: Refusal::Final,
                ..
            }
        ),
        "{out:?}"
    );

    // And a sink that refuses for its own reasons is final too.
    let state = state_with(
        CountingSink::answering(Err(SignupError::Refused(
            "gunnar refused the entitlement".into(),
        ))),
        Arc::new(AtomicU64::new(1)),
    );
    let out = settle(&state, &a_signup("cs_ent", "bob"));
    assert!(
        matches!(
            out,
            SignupState::Refused {
                refusal: Refusal::Final,
                ..
            }
        ),
        "{out:?}"
    );

    assert!(Refusal::Name.renameable());
    assert!(!Refusal::Final.renameable());
    // A row written before the field existed. Not renameable: nobody knows what
    // it was about, and re-running a sink on a guess spends somebody's money.
    assert!(!Refusal::Unknown.renameable());
}

/// **A row this store wrote before the refusal field existed still parses**, and
/// reads as `Unknown`. The table IS the idempotency key: a store that refused
/// its own history would run a sink again for a payment already answered.
#[test]
fn a_refused_row_from_before_the_field_existed_reads_as_unknown() {
    let old = serde_json::json!({ "Refused": { "reason": "username \"Bob!\" contains '!'", "at_unix_ms": 7 } });
    let state: SignupState = serde_json::from_value(old).expect("the old shape still parses");
    assert!(
        matches!(
            state,
            SignupState::Refused {
                refusal: Refusal::Unknown,
                ..
            }
        ),
        "{state:?}"
    );
}

/// ★ **The race, closed without a refund.** A name free when it was checked,
/// taken by the time the card cleared: the customer types another one and the
/// SAME money makes the account.
#[test]
fn a_signup_refused_over_a_taken_name_is_finished_by_another_name() {
    // The appliance already has a bob, and says so the way a real sink does.
    let sink = CountingSink::holding(&["bob"]);
    *sink.answer.lock().unwrap() = Err(SignupError::NameTaken(
        "username \"bob\" is taken on this appliance".into(),
    ));
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(10)));
    let first = settle(&state, &a_signup("cs_r", "bob"));
    assert!(
        matches!(
            first,
            SignupState::Refused {
                refusal: Refusal::Name,
                ..
            }
        ),
        "{first:?}"
    );

    // The customer types another one, and the same money makes the account.
    *sink.answer.lock().unwrap() = Ok(SignupOutcome::Created {
        namespace: "bob2".into(),
        username: "bob2".into(),
    });
    let out = rename(&state, "mockpay", "cs_r", "bob2").expect("a free name finishes it");
    assert!(matches!(out.state, SignupState::Created { .. }), "{out:?}");
    // **The row carries the new name**, or the console shows an operator a name
    // nobody has and a retry asks the sink for it again.
    assert_eq!(out.signup.username, "bob2");
    assert_eq!(
        state
            .signups
            .get("mockpay", "cs_r")
            .unwrap()
            .unwrap()
            .signup
            .username,
        "bob2"
    );
    // The money did not move: the record is the same payment, same session.
    assert_eq!(out.signup.reference, "pi_1");
    assert_eq!(
        out.recorded_at_unix_ms, 10,
        "the rename rewrote when the webhook landed"
    );
}

/// **A second name that is also taken is the ordinary case**, and it leaves the
/// record renameable so the page can simply ask again.
#[test]
fn a_rename_to_a_name_that_is_also_taken_is_refused_and_asks_again() {
    let sink = CountingSink::holding(&["bob", "bob2"]);
    *sink.answer.lock().unwrap() = Err(SignupError::NameTaken(
        "username \"bob\" is taken on this appliance".into(),
    ));
    let state = state_with(sink, Arc::new(AtomicU64::new(11)));
    settle(&state, &a_signup("cs_r2", "bob"));

    let err = rename(&state, "mockpay", "cs_r2", "bob2").expect_err("also taken");
    assert!(
        matches!(err, RenameRefusal::Name(ref why) if why.contains("bob2")),
        "{err:?}"
    );
    // Untouched: still refused over the NAME, so the page offers the field again.
    let held = state.signups.get("mockpay", "cs_r2").unwrap().unwrap();
    assert!(
        matches!(
            held.state,
            SignupState::Refused {
                refusal: Refusal::Name,
                ..
            }
        ),
        "{held:?}"
    );
    assert_eq!(
        held.signup.username, "bob",
        "a refused rename rewrote the row"
    );

    // And a name that is not a name at all never reaches the sink's list.
    assert!(matches!(
        rename(&state, "mockpay", "cs_r2", "Bob!"),
        Err(RenameRefusal::Name(_))
    ));
}

/// ★ **A FINAL refusal is not renameable**, and the refusal it answers with is
/// the original one — which is what the page shows instead of a text field.
#[test]
fn a_signup_refused_over_the_order_is_not_renameable() {
    let state = state_with(CountingSink::creating(), Arc::new(AtomicU64::new(12)));
    let mut s = a_signup("cs_f", "bob");
    s.plan = "gunnar/1tb-forever".into();
    settle(&state, &s);

    let err = rename(&state, "mockpay", "cs_f", "bob2")
        .expect_err("a plan nobody sells is not a name problem");
    let RenameRefusal::NotRenameable(why) = err else {
        panic!("{err:?}")
    };
    assert!(
        why.contains("gunnar/1tb-forever"),
        "the page shows the ORIGINAL refusal: {why}"
    );
    // Nothing ran and nothing changed.
    assert_eq!(
        state
            .signups
            .get("mockpay", "cs_f")
            .unwrap()
            .unwrap()
            .signup
            .username,
        "bob"
    );
}

/// **Idempotent per session.** A return page that posts twice — a double tap, a
/// retry over a dropped connection — must not push a second name at a sink that
/// has already made an account.
#[test]
fn renaming_a_created_signup_runs_nothing_and_answers_what_it_became() {
    let sink = CountingSink::creating();
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(13)));
    settle(&state, &a_signup("cs_done", "bob"));
    let calls = sink.calls.load(Ordering::SeqCst);

    let out = rename(&state, "mockpay", "cs_done", "somebodyelse").expect("answers what it became");
    assert!(matches!(out.state, SignupState::Created { .. }));
    assert_eq!(
        out.signup.username, "bob",
        "a created signup was renamed out from under its owner"
    );
    assert_eq!(
        sink.calls.load(Ordering::SeqCst),
        calls,
        "the sink ran again for a signup already created"
    );
}

/// A session nobody has heard of, and one still pending: two different
/// sentences, because a page answers them differently.
#[test]
fn a_rename_of_an_unknown_or_pending_signup_is_refused_by_name() {
    let sink = CountingSink::answering(Err(SignupError::Unavailable(
        "gunnar is not answering".into(),
    )));
    let state = state_with(sink, Arc::new(AtomicU64::new(14)));
    assert_eq!(
        rename(&state, "mockpay", "cs_nope", "bob"),
        Err(RenameRefusal::Unknown)
    );

    settle(&state, &a_signup("cs_pend", "bob"));
    let err =
        rename(&state, "mockpay", "cs_pend", "bob2").expect_err("nothing has been refused yet");
    assert!(matches!(err, RenameRefusal::NotRenameable(_)), "{err:?}");
}

/// ★ **A sink that cannot be asked is not "taken".**
///
/// A console that said "choose another username" because the appliance was
/// rebooting would be lying to somebody whose name is free — and they would
/// spend it on a second-choice name they keep forever.
#[test]
fn a_name_check_against_an_unreachable_sink_is_an_outage_and_not_a_refusal() {
    let sink = CountingSink::holding(&["bob"]);
    assert_eq!(sink.name_verdict("olle"), Ok(NameVerdict::Free));
    assert!(matches!(
        sink.name_verdict("bob"),
        Ok(NameVerdict::Taken(_))
    ));

    *sink.reachable.lock().unwrap() = false;
    assert!(matches!(
        sink.name_verdict("olle"),
        Err(SignupError::Unavailable(_))
    ));

    // And a rename through an unreachable sink leaves the record alone.
    *sink.reachable.lock().unwrap() = true;
    *sink.answer.lock().unwrap() = Err(SignupError::NameTaken(
        "username \"bob\" is taken on this appliance".into(),
    ));
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(15)));
    settle(&state, &a_signup("cs_u", "bob"));
    *sink.reachable.lock().unwrap() = false;
    assert!(matches!(
        rename(&state, "mockpay", "cs_u", "olle"),
        Err(RenameRefusal::Unavailable(_))
    ));
    let held = state.signups.get("mockpay", "cs_u").unwrap().unwrap();
    assert_eq!(held.signup.username, "bob", "an outage rewrote the row");
}

/// **The open build's sink holds nothing, so it invents no occupant.** It
/// creates no principal; answering "taken" for a well-formed name would be a
/// deployment refusing a customer over a name nobody has.
#[test]
fn the_open_sink_answers_the_shape_rule_and_nothing_more() {
    assert_eq!(LoudSink.name_verdict("bob"), Ok(NameVerdict::Free));
    // Even the names a real deployment reserves: that list is the deployment's,
    // and a self-hoster's is their own.
    assert_eq!(LoudSink.name_verdict("admin"), Ok(NameVerdict::Free));
    let taken = LoudSink
        .name_verdict("Bob!")
        .expect("the shape rule needs nobody");
    assert!(
        matches!(taken, NameVerdict::Taken(ref why) if why.contains('!')),
        "{taken:?}"
    );
}

// ── 2026-09-14: the pending signup nobody is watching, and what the customer reads ──

/// The words a customer must never read, checked case-blind.
fn assert_customer_safe(sentence: &str) {
    let lower = sentence.to_lowercase();
    for word in words::NEVER_SHOWN {
        assert!(
            !lower.contains(word),
            "a customer sentence carries {word:?}: {sentence:?}"
        );
    }
}

/// gunnar's own refusal for a valid key on a skewed clock, as the sink reports
/// it once it has classified it transient.
const SKEW: &str = "gunnar Access.ListPrincipals -> Unauthenticated: the credential is 412000 ms away from this server's clock; the window is ±300000 ms";

/// **A pending signup's customer words say it is being set up, and nothing about the
/// clock** (PAID-WORDS: never maintenance for a skew; it is not the product saying so).
/// The operator's copy of the skew is on `waiting_on` (which `SignupAdmin.List` carries)
/// and nowhere a customer reads.
#[test]
fn a_skew_pending_signup_tells_the_customer_setting_up_and_the_operator_the_skew() {
    let sink = CountingSink::answering(Err(SignupError::Unavailable(SKEW.into())));
    let state = state_with(sink, Arc::new(AtomicU64::new(1_000)));
    let s = a_signup("cs_skew", "bob");
    assert_eq!(
        settle(&state, &s),
        SignupState::Pending,
        "a skew is not a refusal"
    );

    let rec = state.signups.get("mockpay", "cs_skew").unwrap().unwrap();
    let (said, reason) = customer_words(&state.signups, &rec, state.now() as i64);
    assert_eq!(said, words::BEING_CREATED);
    assert!(reason.is_empty(), "a pending signup has no refusal to show");
    assert_customer_safe(&said);
    assert_eq!(
        state.signups.waiting_on("mockpay", "cs_skew").as_deref(),
        Some(SKEW),
        "the operator keeps the real reason"
    );

    // Before the sink has said "not now", the page says the account is being made.
    let fresh = state_with(CountingSink::creating(), Arc::new(AtomicU64::new(1)));
    fresh
        .signups
        .record_pending(&a_signup("cs_fresh", "olle"), 1)
        .unwrap();
    let rec = fresh.signups.get("mockpay", "cs_fresh").unwrap().unwrap();
    assert_eq!(
        customer_words(&fresh.signups, &rec, fresh.now() as i64).0,
        words::BEING_CREATED
    );
}

/// ★ **PAID-WORDS: a "not now" that is not the product saying maintenance is a settle**, in the
/// buyer's words: a product that does not answer, a timeout, a queue. Never "maintenance".
#[test]
fn a_generic_not_now_after_paying_reads_setting_up_never_maintenance() {
    for why in [
        "gunnar is not answering",
        "status: Unavailable, message: \"tcp connect error\"",
        "deadline has elapsed",
        "queued: gunnar.rs's main creates \"bob\" when it next calls monetize",
    ] {
        let state = state_with(
            CountingSink::answering(Err(SignupError::Unavailable(why.into()))),
            Arc::new(AtomicU64::new(1_000)),
        );
        assert_eq!(
            settle(&state, &a_signup("cs_blip", "bob")),
            SignupState::Pending
        );
        let rec = state.signups.get("mockpay", "cs_blip").unwrap().unwrap();
        let said = customer_words(&state.signups, &rec, state.now() as i64).0;
        assert!(
            !said.to_lowercase().contains("maintenance"),
            "{why:?} said maintenance: {said:?}"
        );
        assert!(
            said.starts_with("Payment received. Setting up your account"),
            "{why:?}: {said:?}"
        );
        assert_customer_safe(&said);
    }
}

/// ★ **The pending sentence, as a pure choice**: maintenance only by name; past the bound,
/// taking longer (the mail promised only where a sign-in link is mailed); else setting up.
#[test]
fn pending_words_say_maintenance_only_by_name_and_taking_longer_past_the_bound() {
    let bound = SETTLE_BOUND_MS;
    assert_eq!(pending_words(false, 0, true), words::BEING_CREATED);
    assert_eq!(pending_words(false, bound - 1, false), words::BEING_CREATED);
    assert_eq!(pending_words(true, 0, true), words::MAINTENANCE);
    assert_eq!(
        pending_words(true, bound * 10, true),
        words::MAINTENANCE,
        "the product's own word holds"
    );
    assert_eq!(
        pending_words(false, bound, true),
        words::TAKING_LONGER_MAILED
    );
    assert_eq!(pending_words(false, bound, false), words::TAKING_LONGER);
    assert!(
        words::TAKING_LONGER_MAILED.contains("e-mail") && !words::TAKING_LONGER.contains("mail"),
        "a mail is promised only where one is sent"
    );
}

/// ★ **"Down for maintenance" only when the product says so by name**: its refusal is
/// `SignupError::Maintenance` (RA4-20), and the operator keeps the product's own words.
#[test]
fn a_product_that_says_it_is_in_maintenance_reads_maintenance() {
    let state = state_with(
        CountingSink::answering(Err(SignupError::Maintenance(
            "the shop resizes gunnar.rs".into(),
        ))),
        Arc::new(AtomicU64::new(1_000)),
    );
    assert_eq!(
        settle(&state, &a_signup("cs_maint", "bob")),
        SignupState::Pending
    );
    let rec = state.signups.get("mockpay", "cs_maint").unwrap().unwrap();
    assert_eq!(
        customer_words(&state.signups, &rec, state.now() as i64).0,
        words::MAINTENANCE
    );
    assert_eq!(
        state.signups.waiting_on("mockpay", "cs_maint").as_deref(),
        Some("the shop resizes gunnar.rs"),
        "the operator keeps the product's words"
    );
    // Past the bound the product's own word still holds.
    let later = state_with(
        CountingSink::answering(Err(SignupError::Maintenance("resizing".into()))),
        Arc::new(AtomicU64::new(1_000)),
    );
    assert_eq!(
        settle(&later, &a_signup("cs_maint2", "bob")),
        SignupState::Pending
    );
    let rec = later.signups.get("mockpay", "cs_maint2").unwrap().unwrap();
    assert_eq!(
        customer_words(&later.signups, &rec, 1_000 + 10 * SETTLE_BOUND_MS).0,
        words::MAINTENANCE
    );
}

/// ★ **Words that merely begin "maintenance: " are a settle, not maintenance** (RA4-20): the
/// refusal is by name, never by a prefix of `Unavailable`'s words.
#[test]
fn an_unavailable_that_begins_maintenance_is_a_settle() {
    let state = state_with(
        CountingSink::answering(Err(SignupError::Unavailable(
            "maintenance: the shop resizes gunnar.rs".into(),
        ))),
        Arc::new(AtomicU64::new(1_000)),
    );
    assert_eq!(
        settle(&state, &a_signup("cs_prefix", "bob")),
        SignupState::Pending
    );
    let rec = state.signups.get("mockpay", "cs_prefix").unwrap().unwrap();
    assert_eq!(
        customer_words(&state.signups, &rec, state.now() as i64).0,
        words::BEING_CREATED
    );
}

/// ★ **RA4-19: a pending answer sends AHEAD the sentence for the shop's silence**, due at the
/// settle bound from the payment: the page keeps no bound and no words of its own.
#[test]
fn a_pending_answer_sends_ahead_its_sentence_for_the_shops_silence() {
    let now = Arc::new(AtomicU64::new(1_000));
    let state = state_with(
        CountingSink::answering(Err(SignupError::Unavailable(
            "gunnar is not answering".into(),
        ))),
        Arc::clone(&now),
    );
    assert_eq!(
        settle(&state, &a_signup("cs_ahead", "bob")),
        SignupState::Pending
    );
    let rec = state.signups.get("mockpay", "cs_ahead").unwrap().unwrap();
    let t0 = rec.recorded_at_unix_ms;
    assert_eq!(
        words_ahead(&state.signups, &rec, t0),
        (words::TAKING_LONGER.to_owned(), SETTLE_BOUND_MS)
    );
    assert_eq!(
        words_ahead(&state.signups, &rec, t0 + 30_000),
        (words::TAKING_LONGER.to_owned(), SETTLE_BOUND_MS - 30_000)
    );
    assert_eq!(
        words_ahead(&state.signups, &rec, t0 + 10 * SETTLE_BOUND_MS),
        (words::TAKING_LONGER.to_owned(), 0)
    );
    let maint = state_with(
        CountingSink::answering(Err(SignupError::Maintenance("resizing".into()))),
        Arc::new(AtomicU64::new(1_000)),
    );
    assert_eq!(
        settle(&maint, &a_signup("cs_ahead_m", "bob")),
        SignupState::Pending
    );
    let rec = maint.signups.get("mockpay", "cs_ahead_m").unwrap().unwrap();
    assert_eq!(
        words_ahead(&maint.signups, &rec, rec.recorded_at_unix_ms).0,
        words::MAINTENANCE
    );
}

/// ★ **A settle past its bound says so honestly**: taking longer than usual, the payment safe,
/// and nothing to pay again; never maintenance.
#[test]
fn a_settle_past_its_bound_says_taking_longer_and_the_payment_is_safe() {
    let now = Arc::new(AtomicU64::new(1_000));
    let state = state_with(
        CountingSink::answering(Err(SignupError::Unavailable(
            "gunnar is not answering".into(),
        ))),
        Arc::clone(&now),
    );
    assert_eq!(
        settle(&state, &a_signup("cs_long", "bob")),
        SignupState::Pending
    );
    now.store(1_000 + 10 * 60 * 1000, Ordering::SeqCst);
    assert_eq!(
        settle(&state, &a_signup("cs_long", "bob")),
        SignupState::Pending
    );
    let rec = state.signups.get("mockpay", "cs_long").unwrap().unwrap();
    let said = customer_words(&state.signups, &rec, state.now() as i64).0;
    assert!(
        said.contains("This is taking longer than usual") && said.contains("your payment is safe"),
        "{said:?}"
    );
    assert!(
        said.contains("you do not need to pay again")
            && !said.to_lowercase().contains("maintenance"),
        "{said:?}"
    );
    assert_customer_safe(&said);
}

/// **Every fixed customer sentence passes the same check**, and a product's
/// refusal reaches the customer as those words while the operator keeps the
/// product's own. Twin: a name refusal is written for the customer and passes
/// through untouched, because the page needs to say WHICH name.
#[test]
fn the_product_s_own_refusal_never_reaches_the_customer_but_a_name_refusal_does() {
    for w in [
        words::BEING_CREATED,
        words::TAKING_LONGER,
        words::TAKING_LONGER_MAILED,
        words::NOT_ANSWERING_PAID,
        words::MAINTENANCE,
        words::READY,
        words::COULD_NOT_CREATE,
        words::SHOP_DOWN,
    ] {
        assert_customer_safe(w);
    }
    let raw = "gunnar Access.AddPrincipal -> Unauthenticated: credential did not verify; the sink's identity key is not registered on this gunnar";
    let state = state_with(
        CountingSink::answering(Err(SignupError::Refused(raw.into()))),
        Arc::new(AtomicU64::new(1)),
    );
    let out = settle(&state, &a_signup("cs_key", "bob"));
    assert!(
        matches!(&out, SignupState::Refused { reason, refusal: Refusal::Final, .. } if reason == raw),
        "the operator's row keeps it verbatim: {out:?}"
    );
    let rec = state.signups.get("mockpay", "cs_key").unwrap().unwrap();
    let (said, reason) = customer_words(&state.signups, &rec, state.now() as i64);
    assert_eq!(
        (said.as_str(), reason.as_str()),
        (words::COULD_NOT_CREATE, words::COULD_NOT_CREATE)
    );

    let named = "username \"bob\" is taken on this appliance";
    let state = state_with(
        CountingSink::answering(Err(SignupError::NameTaken(named.into()))),
        Arc::new(AtomicU64::new(1)),
    );
    settle(&state, &a_signup("cs_name", "bob"));
    let rec = state.signups.get("mockpay", "cs_name").unwrap().unwrap();
    assert_eq!(
        customer_words(&state.signups, &rec, state.now() as i64).1,
        named
    );
}

/// **Past the ceiling a skew outage becomes a refusal — with customer words.**
/// What `PENDING_CEILING_MS` did before, it still does; the customer reads that
/// they need not pay again, and not `sink never answered: … clock`.
#[test]
fn a_skew_outage_longer_than_the_ceiling_is_refused_in_customer_words() {
    let now = Arc::new(AtomicU64::new(1_000));
    let state = state_with(
        CountingSink::answering(Err(SignupError::Unavailable(SKEW.into()))),
        Arc::clone(&now),
    );
    settle(&state, &a_signup("cs_long", "bob"));
    now.store(1_000 + PENDING_CEILING_MS as u64, Ordering::SeqCst);
    assert_eq!(settle_pending(&state), 1, "the background pass decides it");
    let rec = state.signups.get("mockpay", "cs_long").unwrap().unwrap();
    assert!(
        matches!(&rec.state, SignupState::Refused { reason, .. } if reason.starts_with(NEVER_ANSWERED) && reason.contains("clock")),
        "{rec:?}"
    );
    let (said, _) = customer_words(&state.signups, &rec, state.now() as i64);
    assert_eq!(said, words::COULD_NOT_CREATE);
    assert_customer_safe(&said);
}

/// **The background pass settles a pending signup with no `Signup.Status`
/// call.** RED before: nothing but the return page's poll moved it, so a
/// customer who closed the tab stayed PENDING until somebody opened that URL.
#[test]
fn a_pending_signup_is_settled_in_the_background_once_the_product_answers() {
    let sink = CountingSink::answering(Err(SignupError::Unavailable(SKEW.into())));
    let now = Arc::new(AtomicU64::new(1_000));
    let state = state_with(sink.clone(), Arc::clone(&now));
    assert_eq!(
        settle(&state, &a_signup("cs_bg", "bob")),
        SignupState::Pending
    );

    // Still skewed: the pass asks, decides nothing, and the row stays pending.
    assert_eq!(settle_pending(&state), 0);
    assert_eq!(sink.calls.load(Ordering::SeqCst), 2);

    // The clocks agree. No page is open; the poller's tick finishes it.
    *sink.answer.lock().unwrap() = Ok(SignupOutcome::Created {
        namespace: "bob".into(),
        username: "bob".into(),
    });
    now.store(301_000, Ordering::SeqCst);
    let tick = crate::poller::tick(&state);
    assert_eq!(tick.signups_settled, 1, "{tick:?}");
    let rec = state.signups.get("mockpay", "cs_bg").unwrap().unwrap();
    assert_eq!(
        rec.state,
        SignupState::Created {
            namespace: "bob".into(),
            username: "bob".into(),
            at_unix_ms: 301_000
        }
    );
    assert_eq!(
        state.signups.waiting_on("mockpay", "cs_bg"),
        None,
        "a decided signup is not waiting on anything"
    );

    // And a decided signup is not asked again by the next pass.
    assert_eq!(settle_pending(&state), 0);
    assert_eq!(sink.calls.load(Ordering::SeqCst), 3);
}

/// A sink whose `paid` blocks until the test lets it go — so two callers can be
/// made to overlap on one session, deterministically.
struct GateSink {
    entered: std::sync::mpsc::SyncSender<()>,
    release: Mutex<std::sync::mpsc::Receiver<()>>,
    calls: AtomicU64,
}

impl SignupSink for GateSink {
    fn paid(&self, s: &PaidSignup) -> Result<SignupOutcome, SignupError> {
        // Only the FIRST call is held, so a second caller that wrongly reaches
        // the sink fails the assertion below instead of hanging the test.
        if self.calls.fetch_add(1, Ordering::SeqCst) == 0 {
            let _ = self.entered.send(());
            let _ = self.release.lock().unwrap().recv();
        }
        Ok(SignupOutcome::Created {
            namespace: s.username.clone(),
            username: s.username.clone(),
        })
    }
    fn name_verdict(&self, _username: &str) -> Result<NameVerdict, SignupError> {
        Ok(NameVerdict::Free)
    }
}

/// **A background settle and a status poll on the same session create the
/// account once.** The background pass is held inside the sink; the poll that
/// arrives meanwhile answers PENDING and calls nothing, and once the pass is
/// released both read CREATED. RED before the flight: the poll ran the sink a
/// second time for the same payment.
#[test]
fn a_background_settle_and_a_status_poll_on_one_session_create_the_account_once() {
    let (entered_tx, entered_rx) = std::sync::mpsc::sync_channel(4);
    let (release_tx, release_rx) = std::sync::mpsc::channel();
    let sink = Arc::new(GateSink {
        entered: entered_tx,
        release: Mutex::new(release_rx),
        calls: AtomicU64::new(0),
    });
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(1_000)));
    let s = a_signup("cs_race", "bob");
    // Pending, as a webhook that met an outage leaves it.
    state.signups.record_pending(&s, 1_000).unwrap();

    let bg_state = Arc::clone(&state);
    let background = std::thread::spawn(move || settle_pending(&bg_state));
    entered_rx
        .recv_timeout(std::time::Duration::from_secs(10))
        .expect("the background pass reaches the sink");

    // The customer's page polls while the pass is inside the sink.
    let polled = status_with_retry(&state, "mockpay", "cs_race")
        .unwrap()
        .unwrap();
    assert_eq!(
        polled.state,
        SignupState::Pending,
        "the poll must not run the sink beside the pass"
    );
    assert_eq!(sink.calls.load(Ordering::SeqCst), 1);

    release_tx.send(()).unwrap();
    assert_eq!(background.join().unwrap(), 1);
    let polled = status_with_retry(&state, "mockpay", "cs_race")
        .unwrap()
        .unwrap();
    assert!(
        matches!(polled.state, SignupState::Created { .. }),
        "{polled:?}"
    );
    assert_eq!(
        sink.calls.load(Ordering::SeqCst),
        1,
        "one payment, one account, one sink call"
    );
    assert!(
        state.signups.fly("mockpay", "cs_race").is_some(),
        "the flight is released when the call ends"
    );
}

/// A state that links gunnar (so `Entitlement` and `Ledger.Sales` answer for
/// it), with the counting sink and the test plans.
fn linked_state(sink: Arc<dyn SignupSink>, now: u64) -> Arc<crate::state::AppState> {
    let mut state = crate::state::AppState::in_memory(
        crate::registry::Registry::new().product(monetize::testing::MockProduct::gunnar()),
    );
    state.signup_sink = sink;
    state.signup_plans = plans().into();
    state.clock = Box::new(move || now);
    Arc::new(state)
}

/// (r) **A paid signup's tenant shows its cap.** Measured on the live front
/// 2026-09-15: `elvira` paid, the sink pushed pack_bytes = 10 GiB for 30 days
/// and gunnar accepted it, and the console listed her as "no cap" — a signup
/// writes no ledger row, and `Entitlement.List` read the ledger alone.
#[tokio::test]
async fn a_created_signup_is_an_entitlement_with_its_cap_and_a_replay_adds_no_second() {
    use monetize_proto::entitlement_server::Entitlement as _;
    let now = 1_788_436_900_000;
    let sink = CountingSink::answering(Ok(SignupOutcome::Created {
        namespace: "elvira".into(),
        username: "elvira".into(),
    }));
    let state = linked_state(sink, now);
    let s = a_signup("cs_elvira", "elvira");
    assert!(matches!(settle(&state, &s), SignupState::Created { .. }));
    settle(&state, &s); // the vendor re-delivers

    let svc = crate::services::entitlement::EntitlementService(state.clone());
    let facts = svc
        .list(tonic::Request::new(
            monetize_proto::ListEntitlementsRequest {
                product: "gunnar".into(),
            },
        ))
        .await
        .unwrap()
        .into_inner()
        .facts;
    assert_eq!(facts.len(), 1, "one signup, one fact: {facts:?}");
    let f = &facts[0];
    assert_eq!(f.tenant, "elvira");
    assert_eq!(
        f.caps.get("pack_bytes"),
        Some(&10_737_418_240),
        "the plan's cap: {f:?}"
    );
    assert_eq!(
        f.paid_until_unix_ms,
        Some(s.paid_at_unix_ms as u64 + 30 * 24 * 60 * 60 * 1000)
    );
    assert_eq!(f.state(), monetize_proto::State::Paid);
    let rust = crate::convert::fact_from_pb(f).unwrap();
    assert!(
        monetize::embed::signing::verify_fact(&rust, &state.signer_for("gunnar").verifying_key())
            .is_ok(),
        "signed by monetize's own key"
    );

    let got = svc
        .get(tonic::Request::new(monetize_proto::GetEntitlementRequest {
            product: "gunnar".into(),
            tenant: "elvira".into(),
        }))
        .await
        .unwrap()
        .into_inner();
    assert_eq!(
        got.fact.and_then(|f| f.caps.get("pack_bytes").copied()),
        Some(10_737_418_240)
    );

    // A fact the LEDGER holds for the tenant wins: a later purchase or an
    // operator's hand replaced the signup's.
    let mut later = rust.clone();
    later.caps = BTreeMap::from([("pack_bytes".to_string(), 20_u64 << 30)]);
    state.ledger.set_entitlement("gunnar", &later).unwrap();
    let facts = svc
        .list(tonic::Request::new(
            monetize_proto::ListEntitlementsRequest {
                product: "gunnar".into(),
            },
        ))
        .await
        .unwrap()
        .into_inner()
        .facts;
    assert_eq!(facts.len(), 1);
    assert_eq!(facts[0].caps.get("pack_bytes"), Some(&(20_u64 << 30)));
}

/// (r) **"Recently bought" includes a paid signup**, one line per vendor
/// session, and a REFUSED signup is not a sale. RED on the live front: "Nothing
/// bought yet" beside a paying tenant.
#[tokio::test]
async fn a_created_signup_is_recently_bought_once_and_a_refused_one_is_not() {
    use monetize_proto::ledger_server::Ledger as _;
    let sink = CountingSink::answering(Ok(SignupOutcome::Created {
        namespace: "elvira".into(),
        username: "elvira".into(),
    }));
    let state = linked_state(sink.clone(), 1_788_436_900_000);
    let mut s = a_signup("cs_elvira", "elvira");
    s.amount = Money {
        minor: 12_500,
        currency: Currency::Sek,
    };
    s.tax = Money {
        minor: 2_500,
        currency: Currency::Sek,
    };
    settle(&state, &s);
    settle(&state, &s);
    *sink.answer.lock().unwrap() = Err(SignupError::Refused("the product refused".into()));
    settle(&state, &a_signup("cs_refused", "bjorn"));

    let svc = crate::services::ledger::LedgerService(state.clone());
    let sales = svc
        .sales(tonic::Request::new(monetize_proto::SalesRequest {
            product: "gunnar".into(),
            limit: 10,
        }))
        .await
        .unwrap()
        .into_inner()
        .sales;
    assert_eq!(
        sales.len(),
        1,
        "one CREATED session, one line — replay and refusal add none: {sales:?}"
    );
    let sale = &sales[0];
    assert_eq!(sale.reference, "signup:mockpay:cs_elvira");
    assert_eq!(
        (
            sale.tenant.as_str(),
            sale.outcome.as_str(),
            sale.drawn,
            sale.needs_you
        ),
        ("elvira", "granted", true, false)
    );
    assert_eq!(
        sale.price.as_ref().map(|m| m.minor),
        Some(10_000),
        "net of the vendor's tax"
    );
    assert_eq!(sale.caps.get("pack_bytes"), Some(&10_737_418_240));
    assert!(sale.fee.is_none(), "not reported is not zero");
    assert_eq!(
        sale.livemode, None,
        "the mock vendor's session carries no mode marker: not live, not test — not said"
    );

    // The all-products ledger page lists it once too.
    let all = svc
        .sales(tonic::Request::new(monetize_proto::SalesRequest {
            product: String::new(),
            limit: 0,
        }))
        .await
        .unwrap()
        .into_inner()
        .sales;
    assert_eq!(all.len(), 1);
}

/// (r) **MONEY TAKEN, ACCOUNT NOT CREATED, RECORDED AS SUCH — and the job
/// named.**
///
/// The shape this exists for, measured against `monetize-signup-impl`'s own
/// order of work: the sink creates the gunnar principal at step (b) and pushes
/// the entitlement at step (c), so a `ProductError::Refused` at (c) is a
/// customer whose card cleared, whose name is spent on the appliance, and whose
/// signup is `Refusal::Final` — never retried (`settle`), not renameable
/// (`rename`), and nothing calls `reclaim::purge_tenant`. Before this the only
/// record of it was prose in `reason`, `tax_sales` counted the CREATED rows
/// alone, and its comment said the rest was "owed back by hand" without there
/// being a hand to count.
///
/// Asserted on the ARM and on the remedy's substance, not on a bool: a queue
/// that said "refund" for a signup whose name is burnt would send an operator
/// to do half the job.
#[test]
fn money_taken_for_an_account_that_was_never_created_is_recorded_as_owed_back_with_the_job_named() {
    let sink = CountingSink::answering(Err(SignupError::Refused(
        "Entitlement.Set: quota rejected".into(),
    )));
    let state = state_with(sink.clone(), Arc::new(AtomicU64::new(7_000)));
    let out = settle(&state, &a_signup("cs_owed", "bob"));

    let SignupState::Refused { owed, refusal, .. } = &out else {
        panic!("{out:?}")
    };
    assert_eq!(
        *refusal,
        Refusal::Final,
        "the pre-existing shape: nothing the customer types fixes it"
    );
    assert_eq!(
        *owed,
        Owed::RefundAndTheNameMayBeSpent,
        "the sink had already made the principal when it refused"
    );
    let remedy = owed.remedy().to_lowercase();
    assert!(
        remedy.contains("refund"),
        "the operator is not told to refund: {remedy}"
    );
    assert!(
        remedy.contains("name"),
        "the burnt username is not mentioned: {remedy}"
    );

    // THE QUEUE. One row, and it carries the buyer and the money — a list that
    // said only "1 owed" would be a number nobody can act on.
    let owed_back = state.signups.owed_back().unwrap();
    assert_eq!(owed_back.len(), 1);
    assert_eq!(owed_back[0].signup.session, "cs_owed");
    assert_eq!(owed_back[0].signup.email, "bob@example.com");
    assert_eq!(
        owed_back[0].signup.amount,
        Money {
            minor: 10_000,
            currency: Currency::Sek
        }
    );
    assert_eq!(state.signups.owed_back_count().unwrap(), 1);

    // A CREATED signup is not on it, and neither is a PENDING one: the first is
    // the account and the second may still become one.
    *sink.answer.lock().unwrap() = Ok(SignupOutcome::Created {
        namespace: "ada".into(),
        username: "ada".into(),
    });
    settle(&state, &a_signup("cs_made", "ada"));
    *sink.answer.lock().unwrap() = Err(SignupError::Unavailable("gunnar is restarting".into()));
    settle(&state, &a_signup("cs_waiting", "eva"));
    assert_eq!(
        state.signups.owed_back_count().unwrap(),
        1,
        "only the refusal is owed back"
    );

    // And the OTHER arms are reachable and different, so the queue is not one
    // verdict wearing four names. A plan nobody sells is refused before the
    // sink runs: nothing was created, so the name is not spent.
    let mut wrong_plan = a_signup("cs_plan", "carl");
    wrong_plan.plan = "gunnar/1tb-forever".into();
    assert_eq!(settle(&state, &wrong_plan).owed(), Some(Owed::Refund));
    // A name refusal is the one the customer can still finish themselves.
    assert_eq!(
        settle(&state, &a_signup("cs_shape", "Bob!")).owed(),
        Some(Owed::RenameOrRefund)
    );
    let arms: Vec<&str> = state
        .signups
        .owed_back()
        .unwrap()
        .iter()
        .filter_map(|r| r.state.owed())
        .map(|o| o.name())
        .collect();
    assert_eq!(
        arms,
        [
            "refund_and_the_name_may_be_spent",
            "refund",
            "rename_or_refund"
        ],
        "oldest first, one arm each"
    );
}

/// (r) **The owed-back queue survives a restart**, because the rows do — and a
/// row written before the field existed reads as `unrecorded` rather than as
/// nothing owed.
///
/// A queue that lived in memory would empty itself on every deploy, which for
/// this particular list means a customer's hundred kronor quietly ceasing to
/// exist. The pre-field row is the other half: `Owed` is `#[serde(default)]`,
/// and the default must put the row IN the queue with "go and look", never
/// leave it out.
#[test]
fn the_owed_back_queue_is_still_there_after_the_store_is_closed_and_reopened() {
    let dir = std::env::temp_dir().join(format!("monetize-owed-back-{}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let path = dir.join("signups.redb");
    let _ = std::fs::remove_file(&path);

    {
        let store = SignupStore::open(&path).unwrap();
        let s = a_signup("cs_restart", "bob");
        store.record_pending(&s, 1_000).unwrap();
        store
            .set(
                "mockpay",
                "cs_restart",
                SignupState::Refused {
                    reason: "Entitlement.Set: quota rejected".into(),
                    at_unix_ms: 1_001,
                    refusal: Refusal::Final,
                    words: Some(words::COULD_NOT_CREATE.into()),
                    owed: Owed::RefundAndTheNameMayBeSpent,
                },
            )
            .unwrap();
        assert_eq!(store.owed_back_count().unwrap(), 1);
    }

    // A row written by a monetize from BEFORE this field: the JSON has no
    // `owed` key at all. Poked in through the store's own restore path shape —
    // the point is the bytes, which is what a live signups.redb holds.
    let store = SignupStore::open(&path).unwrap();
    assert_eq!(
        store.owed_back_count().unwrap(),
        1,
        "the refusal did not survive the reopen"
    );
    let rec = store.get("mockpay", "cs_restart").unwrap().unwrap();
    assert_eq!(rec.state.owed(), Some(Owed::RefundAndTheNameMayBeSpent));

    let old_json = serde_json::json!({
        "signup": serde_json::to_value(a_signup("cs_ancient", "eva")).unwrap(),
        "state": { "Refused": { "reason": "plan \"gunnar/1tb\" is not sold here", "at_unix_ms": 900 } },
        "recorded_at_unix_ms": 900,
    });
    let ancient: SignupRecord =
        serde_json::from_value(old_json).expect("a row written before the field still parses");
    assert_eq!(
        ancient.state.owed(),
        Some(Owed::Unrecorded),
        "a row from before the field is IN the queue, not out of it"
    );
    assert!(
        ancient
            .state
            .owed()
            .unwrap()
            .remedy()
            .to_lowercase()
            .contains("unknown"),
        "{}",
        ancient.state.owed().unwrap().remedy()
    );
    let _ = std::fs::remove_file(&path);
}

/// ★ **A FULL SHOP IS NOT MAINTENANCE — the second half of the 2026-09-20 fix.**
///
/// `monetize::transaction::words` stopped blaming maintenance for a store that
/// was simply too small; this constant kept doing it for one more repository,
/// because its doc pointed at that sibling as the justification. Maintenance
/// ends by itself. A shop with no room left does not — somebody has to act —
/// so the one word that must never appear here is the one that tells a
/// customer to wait.
///
/// It also asserts what the sentence DOES carry, because "does not say
/// maintenance" is satisfied by a blank string: the true half a frightened
/// buyer needs first, and who acts next.
#[test]
fn the_no_room_sentence_never_blames_maintenance() {
    let s = words::NO_ROOM;
    assert!(
        !s.to_lowercase().contains("maintenance"),
        "the full-shop refusal still blames maintenance: {s:?}"
    );
    assert!(
        s.contains("Nothing has been charged"),
        "the half that is always true is missing: {s:?}"
    );
    assert!(
        s.contains("ask us"),
        "nothing tells the customer that somebody has to act: {s:?}"
    );
    assert_customer_safe(s);

    // The control: the one sentence that is HONESTLY about maintenance (the product said
    // so by name) keeps the word, so this test cannot be passing because the needle is
    // wrong. A generic "not now" (SHOP_DOWN) never says it (PAID-WORDS).
    assert!(words::MAINTENANCE.to_lowercase().contains("maintenance"));
    assert!(!words::SHOP_DOWN.to_lowercase().contains("maintenance"));
}

/// **The address a tenant signed up with is the payer of its later orders**
/// (owner 2026-09-28: Stripe's page asked a logged-in tenant on test2 for
/// "E-postadress" again). Only a signup that CREATED the namespace counts; a
/// pending one, another product's, or another namespace's does not.
#[test]
fn the_email_of_a_tenant_is_the_one_its_created_signup_carried() {
    let store = SignupStore::in_memory().unwrap();
    let mut alice = a_signup("cs_alice", "alice");
    alice.email = " alice@example.com ".into();
    store.record_pending(&alice, 1).unwrap();
    assert_eq!(
        store.email_of("gunnar", "alice").unwrap(),
        None,
        "a pending signup made nobody yet"
    );
    store
        .set(
            "mockpay",
            "cs_alice",
            SignupState::Created {
                namespace: "alice".into(),
                username: "alice".into(),
                at_unix_ms: 2,
            },
        )
        .unwrap();
    assert_eq!(
        store.email_of("gunnar", "alice").unwrap().as_deref(),
        Some("alice@example.com")
    );
    assert_eq!(
        store.email_of("holger", "alice").unwrap(),
        None,
        "another product's tenant"
    );
    assert_eq!(
        store.email_of("gunnar", "bob").unwrap(),
        None,
        "a namespace no signup made (an operator's account)"
    );
}

/// ★ **g-nonpay / g-renewal: the front's ONE sink delivers a customer notice
/// through the HOST's relay when the sink itself sends no mail.** On the gunnar
/// front the sink is a signup GUEST (or none), which sends nothing — so until
/// this existed every nonpay warning and renewal reminder there was recorded
/// "not sent". The relay is the host's: `secret.smtp_url` / `secret.smtp_from`
/// in the secrets dir (fed by the installer), the env names as the fallback.
/// Asked of a relay nobody listens on, the answer is `Unavailable` — the relay
/// WAS asked — where before it was the sink's refusal.
#[test]
fn a_notice_goes_through_the_hosts_relay_when_the_sink_sends_no_mail() {
    let n = monetize_pay::CustomerNotice {
        product: "gunnar".into(),
        tenant: "bob".into(),
        to: "bob@example.com".into(),
        kind: "nonpay.day0".into(),
        subject: "s".into(),
        body: "b".into(),
    };
    let dir = std::env::temp_dir().join(format!("monetize-host-relay-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&dir);
    let hot = HotSink::per_product(BTreeMap::from([(
        "gunnar".to_string(),
        Arc::new(NoSink) as Sink,
    )]));
    hot.mail_from(&dir);
    // No relay anywhere: the refusal says so, by name.
    let e = hot.notify(&n).unwrap_err();
    assert!(
        matches!(&e, SignupError::Refused(why) if why.contains("smtp_url")),
        "{e:?}"
    );
    // A fed relay, down: the host's relay was ASKED.
    let dead = std::net::TcpListener::bind("127.0.0.1:0")
        .unwrap()
        .local_addr()
        .unwrap()
        .port();
    monetize_product_wasm::secrets::write(
        &dir,
        "smtp_url",
        format!("smtp://u:p@127.0.0.1:{dead}").as_bytes(),
    )
    .unwrap();
    monetize_product_wasm::secrets::write(&dir, "smtp_from", b"gunnar.rs <no-reply@gunnar.rs>")
        .unwrap();
    let e = hot.notify(&n).unwrap_err();
    assert!(
        matches!(&e, SignupError::Unavailable(why) if why.contains("nonpay.day0")),
        "{e:?}"
    );
    let _ = std::fs::remove_dir_all(&dir);
}

/// ★ Review #11: a signup row that will not parse is a store ERROR on every
/// read and write, never "no such signup": read as absent, `settle` would call
/// the sink again and a reversal would be answered "not ours".
#[test]
fn a_signup_row_that_will_not_parse_is_an_error_never_absent() {
    let store = SignupStore::in_memory().unwrap();
    let s = a_signup("cs_broken", "alice");
    store.record_pending(&s, 1).unwrap();
    {
        let txn = store.db.begin_write().unwrap();
        txn.open_table(SIGNUPS)
            .unwrap()
            .insert(
                (s.vendor.as_str(), s.session.as_str()),
                b"{not json".as_slice(),
            )
            .unwrap();
        txn.commit().unwrap();
    }
    assert!(
        store.get(&s.vendor, &s.session).is_err(),
        "get read a broken row as absent"
    );
    assert!(
        store
            .set(&s.vendor, &s.session, SignupState::Pending)
            .is_err(),
        "set read a broken row as absent"
    );
    assert!(
        store
            .rewrite(&s.vendor, &s.session, &s, SignupState::Pending)
            .is_err(),
        "rewrite read a broken row as absent"
    );
}

/// ★ **G4: each product's signup answers through its OWN sink** (one monetize,
/// gunnar and holger, 2026-10-10). The name check, the paid signup and "does
/// it create accounts" go to the sink of the product they name; a product
/// with no `[signups.<product>]` is refused by name, never handed to another
/// product's sink; a name asked with no product is refused.
#[test]
fn the_hot_sink_answers_each_product_with_its_own_sink() {
    let gunnar = CountingSink::holding(&["rust-dev"]);
    let holger = CountingSink::creating();
    let hot = HotSink::per_product(BTreeMap::from([
        ("gunnar".to_string(), gunnar.clone() as Sink),
        ("holger".to_string(), holger.clone() as Sink),
    ]));
    assert!(
        matches!(
            hot.name_verdict_for("gunnar", "rust-dev"),
            Ok(NameVerdict::Taken(_))
        ),
        "gunnar holds the name"
    );
    assert!(
        matches!(
            hot.name_verdict_for("holger", "rust-dev"),
            Ok(NameVerdict::Free)
        ),
        "holger's names are holger's"
    );
    assert!(
        matches!(hot.name_verdict_for("idun", "rust-dev"), Err(SignupError::Refused(why)) if why.contains("idun"))
    );
    assert!(
        matches!(hot.name_verdict("rust-dev"), Err(SignupError::Refused(_))),
        "a name is checked FOR a product"
    );
    assert!(
        hot.creates_accounts_for("gunnar")
            && hot.creates_accounts_for("holger")
            && !hot.creates_accounts_for("idun")
    );
    let mut s = a_signup("cs_h", "rust-dev");
    s.product = "holger".into();
    s.plan = "holger/base-se".into();
    assert!(hot.paid(&s).is_ok());
    assert_eq!(
        (
            gunnar.calls.load(Ordering::SeqCst),
            holger.calls.load(Ordering::SeqCst)
        ),
        (0, 1),
        "holger's signup went to holger's sink"
    );
    s.product = "idun".into();
    assert!(
        matches!(hot.paid(&s), Err(SignupError::Refused(why)) if why.contains("[signups.idun]"))
    );
    hot.replace("holger", Arc::new(NoSink));
    assert!(
        !hot.creates_accounts_for("holger") && hot.creates_accounts_for("gunnar"),
        "one product's sink is replaced alone"
    );
}

/// ★ **G4: each product's plans and meter** — gunnar's caps land on
/// `pack_bytes`, holger's on `disk_bytes`, and a product's plans are its own.
#[test]
fn each_product_has_its_own_plans_and_its_own_meter() {
    let mut all = plans();
    all.insert(
        "holger/base-se".into(),
        SignupPlan {
            amount_minor: 10_000,
            currency: Currency::Sek,
            cap_bytes: 5 << 30,
            period_days: 30,
        },
    );
    let p = Plans::of(
        all,
        BTreeMap::from([("holger".to_string(), "disk_bytes".to_string())]),
    );
    assert_eq!(p.cap_meter_of("holger"), "disk_bytes");
    assert_eq!(p.cap_meter_of("gunnar"), SIGNUP_CAP_METER);
    assert_eq!(
        p.snapshot_of("holger")
            .keys()
            .map(String::as_str)
            .collect::<Vec<_>>(),
        ["holger/base-se"]
    );
    assert_eq!(
        p.snapshot_of("gunnar")
            .keys()
            .map(String::as_str)
            .collect::<Vec<_>>(),
        [PLAN]
    );
}

/// ★ **A signup's tax line is in the BUYER's country, never the currency's**
/// (stinky monetize-server #15, 5.4): a German buyer paying in EUR was booked
/// under Finnish VAT because EUR's market is FI. The vendor's billing country
/// wins; only a row that names none (written before the field, or a vendor
/// that does not say) falls back to the currency's market. The reconciler's
/// copy of a payment carries it the same way.
#[test]
fn a_signups_tax_line_is_in_the_buyers_billing_country_and_never_the_currencys() {
    let eur_plans = BTreeMap::from([(
        PLAN.to_string(),
        SignupPlan {
            amount_minor: 10_000,
            currency: Currency::Eur,
            cap_bytes: 10_737_418_240,
            period_days: 30,
        },
    )]);
    let state = {
        let mut state = crate::state::AppState::in_memory(crate::registry::Registry::new());
        state.signup_sink = CountingSink::creating();
        state.signup_plans = eur_plans.clone().into();
        state.clock = Box::new(|| 1_789_468_200_000);
        Arc::new(state)
    };
    let eur = |session: &str, user: &str, country: Option<&str>| {
        let mut p = a_signup(session, user);
        p.amount = Money {
            minor: 11_900,
            currency: Currency::Eur,
        };
        p.tax = Money {
            minor: 1_900,
            currency: Currency::Eur,
        };
        p.customer_country = country.map(str::to_owned);
        p
    };
    assert!(matches!(
        settle(&state, &eur("cs_de", "bob", Some("DE"))),
        SignupState::Created { .. }
    ));
    assert!(matches!(
        settle(&state, &eur("cs_old", "eve", None)),
        SignupState::Created { .. }
    ));

    let books = monetize::Books {
        home_country: "SE".into(),
        supplier_countries: BTreeMap::new(),
    };
    let sales = tax_sales(&state.signups, &books).unwrap();
    let country = |r: &str| {
        sales
            .iter()
            .find(|s| s.reference == r)
            .map(|s| s.customer_country.clone())
            .unwrap()
    };
    assert_eq!(
        country("signup:mockpay:cs_de"),
        "DE",
        "the buyer's billing country: {sales:?}"
    );
    assert_eq!(
        country("signup:mockpay:cs_old"),
        "FI",
        "no country said: the currency's market, as before"
    );

    let fact = monetize_pay::reconcile::PaidAtVendor {
        vendor: "mockpay".into(),
        session: "cs_rec".into(),
        reference: "pi_rec".into(),
        paid_at_unix_ms: 1_789_468_154_000,
        amount_minor: 11_900,
        tax_minor: 1_900,
        currency: "eur".into(),
        livemode: false,
        metadata: BTreeMap::from([
            ("product".into(), "gunnar".into()),
            ("plan".into(), PLAN.into()),
            ("username".into(), "olle".into()),
        ]),
        email: String::new(),
        customer_country: Some("DE".into()),
    };
    let s = crate::reconcile::signup_of(&fact, &eur_plans).unwrap();
    assert_eq!(s.customer_country.as_deref(), Some("DE"), "{s:?}");
}

/// The "stays pending" line is said once per change, not on every tick.
#[test]
fn stays_pending_is_said_once_per_change() {
    assert!(
        SignupStore::says_pending(None, "sink down"),
        "the first pending pass says it"
    );
    assert!(
        !SignupStore::says_pending(Some("sink down"), "sink down"),
        "the same reason again is quiet"
    );
    assert!(
        SignupStore::says_pending(Some("sink down"), "sink slow"),
        "a new reason is said"
    );
}