crates/monetize-server/src/nonpay/grace_tests.rs
//! Red-first (owner 2026-10-11): **hosted accounts that stop paying are purged
//! after a grace period** — configurable per product, a default when nobody
//! said, and no keep-data path (no "never delete", no reclaim queue beside it).
use super::*;
/// No `[nonpay]` at all is NOT "keep the data forever": every product purges
/// after the default grace.
#[test]
fn no_nonpay_block_purges_after_the_default_grace() {
let cfg =
crate::config::Config::from_toml("data_dir = \"/nonexistent\"\n[policy]\ngrace_days = 0\n")
.unwrap();
assert_eq!(DEFAULT_DELETE_AFTER_DAYS, 30);
assert_eq!(
cfg.nonpay.delete_after_days_of("gunnar"),
DEFAULT_DELETE_AFTER_DAYS
);
assert!(cfg.nonpay.for_product("gunnar").deletes());
}
/// The grace is per product: `[nonpay.products.<id>] delete_after_days`.
#[test]
fn the_grace_is_per_product_and_the_estate_value_is_the_default() {
let cfg = crate::config::Config::from_toml(
"data_dir = \"/nonexistent\"\n[policy]\ngrace_days = 0\n[nonpay]\ndelete_after_days = 45\nwarn_days = [0, 10]\n[nonpay.products.holger]\ndelete_after_days = 14\n",
)
.unwrap();
assert_eq!(cfg.nonpay.delete_after_days_of("holger"), 14);
assert_eq!(
cfg.nonpay.delete_after_days_of("gunnar"),
45,
"a product nobody named takes the estate's"
);
let h = cfg.nonpay.for_product("holger");
assert_eq!(
(h.delete_after_days, h.warn_days()),
(14, vec![0, 10]),
"the warnings stay the estate's, those before the purge"
);
}
/// A grace of 0 kept an unpaid account's data forever: there is no such path.
#[test]
fn a_grace_of_zero_is_refused_by_name() {
for text in [
"[nonpay]\ndelete_after_days = 0\n",
"[nonpay.products.gunnar]\ndelete_after_days = 0\n",
] {
let e = crate::config::Config::from_toml(&format!(
"data_dir = \"/nonexistent\"\n[policy]\ngrace_days = 0\n{text}"
))
.unwrap_err()
.to_string();
assert!(
e.contains("delete_after_days = 0") && e.contains("purge"),
"{text}: {e}"
);
}
}
/// A per-product grace inside the ladder's write grace is refused like the
/// estate's: an account that can still write is never purged.
#[test]
fn a_per_product_grace_inside_the_write_grace_is_refused() {
let e = crate::config::Config::from_toml("data_dir = \"/nonexistent\"\n[policy]\ngrace_days = 20\n[nonpay]\ndelete_after_days = 30\n[nonpay.products.holger]\ndelete_after_days = 14\n").unwrap_err().to_string();
assert!(e.contains("holger") && e.contains("read-only FIRST"), "{e}");
}
/// ★ prod audit 2026-10-12 (NP-2): a fed `[nonpay]` that names only an exemption or a
/// product's grace — what the ONE kit feeds — still WARNS before the purge. Serde's
/// default for an absent `warn_days` is empty, and an empty list mailed nobody before
/// their data was deleted; the shipped warnings (the day it lapses, the day before the
/// purge) hold for the grace in force.
#[test]
fn a_fed_nonpay_without_warn_days_still_warns_before_the_purge() {
let cfg = crate::config::Config::from_toml(
"data_dir = \"/nonexistent\"\n[policy]\ngrace_days = 0\n[nonpay]\nexempt = [\"rickard\", \"@test2-gunnar-rs/rickard\"]\n[nonpay.products.gunnar]\ndelete_after_days = 21\n",
)
.unwrap();
assert_eq!(
cfg.nonpay.warn_days(),
vec![0, DEFAULT_DELETE_AFTER_DAYS - 1],
"the estate's default grace, warned like the shipped policy"
);
let g = cfg.nonpay.for_product("gunnar");
assert_eq!(
(g.delete_after_days, g.warn_days()),
(21, vec![0, 20]),
"the product's grace: warned the day it lapses and the day before the purge"
);
assert!(
g.exempt("rickard") && g.exempt("@test2-gunnar-rs/rickard"),
"the exemptions reach every product"
);
assert!(
!g.exempt("@acme/rickard"),
"an exemption names exactly one site's account"
);
let one_day = NonpayPolicy {
delete_after_days: 1,
..NonpayPolicy::default()
};
assert_eq!(
one_day.warn_days(),
vec![0],
"a one-day grace warns once, on the day it lapses"
);
}
/// The fragment the ONE kit feeds (private-monetize-ops `monetize_toml`, NP-1) is a config
/// monetize reads under the default ladder — a refused fragment would drop the WHOLE fed
/// `monetize.toml` (sites and signups with it).
#[test]
fn the_one_kits_fed_nonpay_is_read_under_the_default_ladder() {
let cfg = crate::config::Config::from_toml(
"data_dir = \"/nonexistent\"\n[nonpay]\nwarn_days = [0, 29]\nexempt = [\"@test2-gunnar-rs/nordisk\", \"@test2-gunnar-rs/rickard\", \"nordisk\", \"rickard\"]\n",
)
.unwrap();
assert_eq!(
(cfg.nonpay.delete_after_days, cfg.nonpay.warn_days()),
(DEFAULT_DELETE_AFTER_DAYS, vec![0, 29])
);
assert!(cfg.nonpay.exempt("nordisk") && cfg.nonpay.exempt("@test2-gunnar-rs/nordisk"));
}