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/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"));
}