crates/monetize-server/tests/server_iron.rs
//! **The open-source deployment: this box's own iron, no price list, and no
//! payment vendor linked at all.** `Purchase.Start` over gRPC lands
//! `Reserved / Ensured(OnHand) / Captured / EntitlementPushed` with zero vendor
//! calls — there is no vendor to call — the pool refuses the sale it cannot
//! cover naming the shortfall, `Purchase.Quote` prices it at nothing and says
//! whether the box can serve it, and the fleet's cloud block shows the box
//! itself under `provenance: server` with nothing orphaned.
//!
//! Measure, never assert: every claim has its red twin here; every assertion is
//! on applied
//! output — ledger rows off the wire, the pool's own sold count, the product's
//! pushed facts — never a round-trip.
use std::sync::Arc;
use monetize::testing::{MockProduct, NOW};
use monetize_cloud::cap::CannotSpend;
use monetize_cloud::server::{Server, ServerConfig};
use monetize_cloud::{Resource, ServerId};
use monetize_pay::Market;
use monetize_proto::ledger_client::LedgerClient;
use monetize_proto::purchase_client::PurchaseClient;
use monetize_proto::query_ledger_request::By;
use monetize_proto::{self as pb, PurchaseState};
use monetize_server::config::Config;
use monetize_server::registry::Registry;
use monetize_server::services::usage::{cloud_inventory_pb, EstateIron};
use monetize_server::state::{AppState, TermsStore};
use monetize_server::testing::TestServer;
use tonic::metadata::MetadataValue;
use tonic::transport::Channel;
use tonic::Request;
const BEARER: &str = "test-bearer-token-of-sufficient-length";
const GIB: u64 = 1 << 30;
struct Rig {
product: Arc<MockProduct>,
iron: Arc<Server>,
state: Arc<AppState>,
#[allow(dead_code)]
server: TestServer,
channel: Channel,
}
impl Rig {
/// No vendor at all — `vendors []` — no price list, and the box has
/// `pool_gib` GiB for tenants, walled by the product saying it can serve
/// `servable_gib`, with 4 000 millicores declared as what the box has.
async fn up(pool_gib: u64, servable_gib: u64) -> Rig {
let product = Arc::new(MockProduct::gunnar());
product.can_serve(servable_gib * GIB, 300 * GIB);
let product_dyn: Arc<dyn monetize_product::Product> = product.clone();
let mut cfg = ServerConfig::new(product_dyn.clone(), pool_gib);
cfg.ceilings = [("cpu_millicores".to_string(), 4000u64)].into();
let iron = Arc::new(Server::new(cfg).unwrap());
let registry = Registry::new().shared_product(product_dyn).cloud(CannotSpend::new(iron.clone()));
assert!(registry.vendor_names().is_empty(), "the claim of this file: no payment vendor is linked");
let mut state = AppState::in_memory(registry);
// THE OPEN-SOURCE TERMS: no list at all. Every order is free.
state.terms = std::sync::RwLock::new(TermsStore { hosts: [("gunnar".to_string(), "this-box".to_string())].into(), ..TermsStore::default() });
let state = Arc::new(state);
let server = TestServer::spawn(state.clone(), BEARER).await;
let channel = Channel::from_shared(server.endpoint()).unwrap().connect().await.unwrap();
Rig { product, iron, state, server, channel }
}
fn authed<T>(&self, req: T) -> Request<T> {
let mut r = Request::new(req);
r.metadata_mut().insert("authorization", MetadataValue::try_from(format!("Bearer {BEARER}")).unwrap());
r
}
/// An order for `gib` GiB of pack_bytes, naming `vendor` (which at price
/// 0 is ignored).
async fn start(&self, tenant: &str, gib: u64, vendor: &str) -> Result<pb::StartPurchaseResponse, tonic::Status> {
PurchaseClient::new(self.channel.clone())
.start(self.authed(pb::StartPurchaseRequest {
product: "gunnar".into(),
tenant: tenant.into(),
caps: [("pack_bytes".to_string(), gib * GIB)].into(),
vendor: vendor.into(),
payer: String::new(),
starts_unix_ms: NOW,
renew: false,
actor_ticket: Vec::new(),
bundle: String::new(),
market: pb::Market::Se as i32,
users: None,
}))
.await
.map(|r| r.into_inner())
}
async fn quote(&self, tenant: &str, caps: &[(&str, u64)]) -> pb::QuoteResponse {
PurchaseClient::new(self.channel.clone())
.quote(self.authed(pb::QuoteRequest {
product: "gunnar".into(),
tenant: tenant.into(),
caps: caps.iter().map(|(k, v)| (k.to_string(), *v)).collect(),
market: pb::Market::Se as i32,
renew: false,
starts_unix_ms: NOW,
actor_ticket: Vec::new(),
bundle: String::new(),
}))
.await
.expect("a quote is answered")
.into_inner()
}
async fn rows(&self, reference: &str) -> Vec<pb::LedgerRow> {
LedgerClient::new(self.channel.clone())
.query(self.authed(pb::QueryLedgerRequest { by: Some(By::Reference(reference.into())) }))
.await
.unwrap()
.into_inner()
.rows
}
}
/// **The whole thing, over the wire, with nobody to pay.** Four rows, the
/// grant `OnHand`, the handle `none`, the fact's source `payment:none:`, the
/// pool's sold count moved, the product told — and the vendor field of the
/// request ignored, because at price 0 there is nobody it could name.
#[tokio::test]
async fn a_free_order_on_this_box_is_sold_over_the_wire_with_no_vendor_linked() {
let rig = Rig::up(100, 200).await;
let r = rig.start("alice", 10, "").await.unwrap();
assert_eq!(r.state, PurchaseState::Paid as i32, "{r:?}");
assert_eq!((r.handle_vendor.as_str(), r.handle_id.as_str()), ("none", r.reference.as_str()));
assert!(r.redirect_url.is_empty(), "nowhere to send a customer who owes nothing");
let fact = r.fact.expect("paid carries the fact");
assert_eq!(fact.source, format!("payment:none:{}", r.reference));
assert_eq!(fact.plan, r.reference, "the fact names the order");
assert_eq!(fact.caps["pack_bytes"], 10 * GIB);
let rows = rig.rows(&r.reference).await;
let kinds: Vec<&str> = rows.iter().map(|r| r.kind.as_str()).collect();
assert_eq!(kinds, ["Reserved", "Ensured", "Captured", "EntitlementPushed"]);
assert!(rows[1].json.contains("\"OnHand\""), "on hand, never bought: {}", rows[1].json);
assert!(!rows[1].json.contains("Provisioned"), "{}", rows[1].json);
assert!(rows[2].json.contains("nothing to draw"), "{}", rows[2].json);
assert_eq!((rig.iron.sold_gib(), rig.iron.free_gib()), (10, 90), "the pool moved by exactly the grant");
assert_eq!(rig.product.pushed().len(), 1, "the product was told");
assert!(rig.state.ledger.unfinished().unwrap().is_empty());
// A vendor NAME on a free purchase is ignored, not looked up: the same
// request naming a vendor that does not exist still sells.
let r2 = rig.start("bob", 10, "klarna").await.unwrap();
assert_eq!(r2.state, PurchaseState::Paid as i32, "{r2:?}");
assert_eq!(rig.iron.sold_gib(), 20);
// Twin: put a PRICE LIST on the deployment and the same order, with no
// vendor linked, is refused by name — nobody can take 220 SEK — and
// nothing moves. The list is per market: 22.00 SEK per GiB-month in SE.
rig.state.terms.write().unwrap().prices = [("gunnar".to_string(), [(Market::Se, monetize::testing::price_list())].into())].into();
let err = rig.start("mira", 10, "klarna").await.expect_err("no vendor can take 220 SEK");
assert_eq!(err.code(), tonic::Code::NotFound, "{err}");
assert!(err.message().contains("220.00 SEK") && err.message().contains("serving: []"), "{err}");
let err = rig.start("mira", 10, "").await.expect_err("nor with no vendor named");
assert!(err.message().contains("[products.gunnar] names no `vendor`") && err.message().contains("serving: []"), "{err}");
assert_eq!(rig.iron.sold_gib(), 20, "a refused order grants nothing");
assert_eq!(rig.product.pushed().len(), 2);
assert!(rig.state.ledger.attempt(&format!("gunnar/mira/2026-09-03+pack_bytes={}", 10 * GIB)).unwrap().is_none(), "and writes nothing");
// The quote says the price and still answers: the box CAN serve it.
let q = rig.quote("mira", &[("pack_bytes", 10 * GIB)]).await;
assert_eq!((q.price.as_ref().unwrap().minor, q.can_serve), (22_000, true), "{q:?}");
}
/// **Refuse twin: the box refuses what it cannot cover, over the wire, naming
/// the shortfall — and nothing is granted.** Declared pool first; then the
/// product's own measurement, which the declaration cannot talk over.
#[tokio::test]
async fn refuse_twin_the_pool_and_the_products_measurement_both_refuse_over_the_wire() {
let rig = Rig::up(15, 200).await;
assert_eq!(rig.start("alice", 10, "").await.unwrap().state, PurchaseState::Paid as i32);
// THE QUOTE REFUSES FIRST, by name, before the order is placed: the same
// sentence `ensure` would refuse with, and nothing written.
let q = rig.quote("bob", &[("pack_bytes", 10 * GIB)]).await;
assert!(!q.can_serve, "{q:?}");
assert!(q.reason.contains("10 GiB already granted") && q.reason.contains("short 5 GiB"), "{}", q.reason);
assert_eq!(q.price.as_ref().unwrap().minor, 0, "free, and still refused: the box is full");
let err = rig.start("bob", 10, "").await.expect_err("the order is refused where the quote was");
assert_eq!(err.code(), tonic::Code::FailedPrecondition, "{err}");
assert!(err.message().contains("short 5 GiB"), "{err}");
assert!(rig.state.ledger.attempt(&q.reference).unwrap().is_none(), "nothing reserved, nothing to destroy");
assert_eq!(rig.iron.sold_gib(), 10, "the refused sale granted nothing");
assert_eq!(rig.product.pushed().len(), 1);
let rig = Rig::up(100, 4).await;
let err = rig.start("alice", 10, "").await.expect_err("the disk has 4 GiB");
assert!(err.message().contains("gunnar measured") && err.message().contains("4.0 GiB more"), "{err}");
assert_eq!(rig.iron.sold_gib(), 0);
// What no tenant owns: the box declares 4 000 millicores, so a product-scope
// cap of 8 000 is refused by name — and the declared number is the one
// named, because this mock product measures no cores.
let rig = Rig::up(100, 200).await;
let q = rig.quote("alice", &[("pack_bytes", 10 * GIB), ("cpu_millicores", 8000)]).await;
assert!(!q.can_serve && q.reason.contains("cpu_millicores = 8000 is past what this box has (4000)"), "{q:?}");
let q = rig.quote("alice", &[("pack_bytes", 10 * GIB), ("cpu_millicores", 4000)]).await;
assert!(q.can_serve, "4000 exactly is not past it: {q:?}");
}
/// **The fleet's cloud block shows the box itself**: one `dataset` row,
/// `provenance: server`, sized by the product's total, priced at zero, attached
/// to the product — and NOT an orphan, which is what it was before
/// `Reconciliation::own` existed. The OnHand grant is neither matched nor
/// missing, as ever.
#[tokio::test]
async fn the_cloud_block_shows_the_box_as_its_own_iron_and_never_as_an_orphan() {
let rig = Rig::up(100, 200).await;
rig.start("alice", 10, "").await.unwrap();
let cloud = rig.state.registry.transaction_slot().expect("a cloud a sale spends at");
assert_eq!(cloud.name(), "server");
assert_eq!(cloud.spend_ceiling(), None, "cannot spend, so no ceiling");
let c = cloud_inventory_pb("gunnar", cloud, &rig.state.ledger, &EstateIron::default());
assert!(c.available, "{}", c.error);
assert_eq!(c.owned.len(), 1, "{c:?}");
let o = &c.owned[0];
assert_eq!((o.provider.as_str(), o.id.as_str(), o.kind.as_str(), o.provenance.as_str()), ("server", "dataset:gunnar", "dataset", "server"));
assert_eq!((o.gib, o.attached_to.as_str(), o.reference.as_str(), o.tenant.as_str()), (300, "gunnar", "", ""));
assert_eq!((o.priced, o.monthly_minor), (true, 0), "zero is a price, not an unpriced row");
assert!(c.orphans.is_empty() && c.missing.is_empty(), "{c:?}");
assert_eq!((c.total_bytes, c.monthly_minor, c.foreign_count), (300 * GIB, 0, 0));
// Another product's view of the same box does not list gunnar's data set.
let other = cloud_inventory_pb("holger", cloud, &rig.state.ledger, &EstateIron::default());
assert!(other.owned.is_empty() && other.orphans.is_empty(), "{other:?}");
}
/// **`Purchase.Quote` on this box: free, costs nothing, the iron named, the
/// reference the order would run under — and NOTHING written.** Then the order
/// for MORE: a tenant on 10 GiB quoted at 100 is quoted the 90 they lack, and
/// the box, which has 100 declared and 10 granted, says no by 0 — exactly the
/// boundary — while 95 fits.
#[tokio::test]
async fn a_quote_on_this_box_is_free_names_the_iron_and_writes_nothing() {
let rig = Rig::up(100, 200).await;
let q = rig.quote("alice", &[("pack_bytes", 10 * GIB)]).await;
assert_eq!(q.reference, format!("gunnar/alice/2026-09-03+pack_bytes={}", 10 * GIB));
assert_eq!((q.price.as_ref().unwrap().minor, q.cost.as_ref().unwrap().minor), (0, 0), "no list, and the box's own iron costs nothing");
assert_eq!(q.price.as_ref().unwrap().currency, pb::Currency::Sek as i32, "the market's currency, even at zero");
assert!(q.current.is_empty() && q.delta["pack_bytes"] == 10 * GIB && q.caps["pack_bytes"] == 10 * GIB);
assert!(matches!(&q.resources[0].kind, Some(pb::resource::Kind::Disk(d)) if d.gib == 10 && d.server == "this-box"), "{:?}", q.resources);
assert!(q.can_serve && q.reason.is_empty(), "{q:?}");
assert_eq!(q.period, "month");
assert_eq!(rig.state.ledger.step_count().unwrap(), 0, "a quote writes nothing");
assert_eq!(rig.iron.sold_gib(), 0, "and grants nothing");
// Sold, then quoted for more: the delta, and the box's boundary.
rig.start("alice", 10, "").await.unwrap();
let q = rig.quote("alice", &[("pack_bytes", 100 * GIB)]).await;
assert_eq!((q.current["pack_bytes"], q.delta["pack_bytes"]), (10 * GIB, 90 * GIB));
assert!(q.can_serve, "90 more on a box with 90 left fits exactly: {q:?}");
let q = rig.quote("alice", &[("pack_bytes", 101 * GIB)]).await;
assert!(!q.can_serve && q.reason.contains("short 1 GiB"), "one GiB past the box: {q:?}");
// A downgrade and a no-op are not quotes at all.
let err = PurchaseClient::new(rig.channel.clone())
.quote(rig.authed(pb::QuoteRequest { product: "gunnar".into(), tenant: "alice".into(), caps: [("pack_bytes".to_string(), GIB)].into(), market: 0, starts_unix_ms: NOW, renew: false, actor_ticket: Vec::new(), bundle: String::new() }))
.await
.expect_err("a downgrade");
assert_eq!(err.code(), tonic::Code::FailedPrecondition);
assert!(err.message().contains("SHRINK"), "{err}");
// A cap on a meter the product does not declare is refused by name.
let err = PurchaseClient::new(rig.channel.clone())
.quote(rig.authed(pb::QuoteRequest { product: "gunnar".into(), tenant: "alice".into(), caps: [("moon_phase".to_string(), 1u64)].into(), market: 0, starts_unix_ms: NOW, renew: false, actor_ticket: Vec::new(), bundle: String::new() }))
.await
.expect_err("an undeclared meter");
assert_eq!(err.code(), tonic::Code::InvalidArgument);
assert!(err.message().contains("moon_phase"), "{err}");
}
/// **The registry builds `[clouds.server]` with no `cap`, and STILL refuses
/// an enabled `upcloud` without one.** The exemption is by name, explicit, and
/// enforced by `CannotSpend`; every other cloud stays behind the ceiling.
#[test]
fn the_registry_builds_server_uncapped_and_still_refuses_upcloud_without_a_cap() {
let dir = std::env::temp_dir().join(format!("monetize-server-iron-registry-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let base = format!("data_dir = {:?}\n[products.holger]\nendpoint = \"127.0.0.1:1\"\n", dir.display());
// The open build links no product plugin (owner ruling 2026-09-22), so
// the holger block builds only through a registered factory — here the
// Holger-shaped TEST product's (v2.0 P2 12.0).
let mut linked = monetize_server::registry::Linked::open();
linked.product(monetize_server::testing::products::HOLGER_FACTORY);
let reg = Registry::from_config_with(&Config::from_toml(&format!("{base}[clouds.server]\nenabled = true\npool_gib = 100\n")).unwrap(), &linked).unwrap();
assert_eq!(reg.cloud_names(), vec!["server"]);
let cloud = reg.transaction_slot().expect("a cloud a sale spends at");
assert_eq!(cloud.spend_ceiling(), None);
// Applied output: a grant through the registry's cloud lands in the data
// dir, OnHand, and the file says so.
let res = Resource::Disk { server: ServerId("this-box".into()), gib: 10 };
assert!(matches!(monetize_cloud::ask::ensure(&cloud, "holger/alice/2026-09", &res, None).unwrap(), monetize_cloud::Ensured::OnHand { pool_free_after: 90 }));
let state = std::fs::read_to_string(dir.join("server-pool.json")).expect("the registry gives a deployment a home for its grants");
assert!(state.contains("\"kind\": \"on_hand\"") && !state.contains("provisioned"), "{state}");
assert!(monetize_server::registry::Linked::open().find_cloud("server").unwrap().config_envs.is_empty(), "no credential env for the box");
// Refuse twins, each naming what is missing.
let err = Registry::from_config_with(&Config::from_toml(&format!("{base}[clouds.server]\nenabled = true\n")).unwrap(), &linked).err().map(|e| e.to_string()).expect("no pool_gib");
assert!(err.contains("[clouds.server]") && err.contains("no `pool_gib`") && err.contains("0 is a valid answer"), "{err}");
// An unlinked product is SKIPPED by name at start (release-3 3ed6028: a
// self-registering product arrives after monetize serves), never the end of it.
let reg = Registry::from_config_with(&Config::from_toml(&format!("{base}[clouds.server]\nenabled = true\npool_gib = 1\nproduct = \"idun\"\n")).unwrap(), &linked).expect("the server starts");
assert!(reg.cloud_names().is_empty());
let said: Vec<String> = reg.vendor_start_refusals().iter().map(|r| r.to_string()).collect();
assert!(said.iter().any(|l| l.contains("cloud-product-not-linked-at-start") && l.contains("\"idun\"") && l.contains("[products.idun]")), "{said:?}");
let err = Registry::from_config(&Config::from_toml(&format!("data_dir = {:?}\n[clouds.server]\nenabled = true\npool_gib = 1\n", dir.display())).unwrap())
.err()
.map(|e| e.to_string())
.expect("no product at all");
assert!(err.contains("none is linked"), "{err}");
// THE exemption does not leak: upcloud enabled with no cap is refused on the
// cap, whether or not the provider is compiled into this build.
let err = Registry::from_config_with(&Config::from_toml(&format!("{base}[clouds.upcloud]\nenabled = true\n")).unwrap(), &linked).err().map(|e| e.to_string()).expect("no ceiling must refuse");
assert!(err.contains("[clouds.upcloud]") && err.contains("no `cap`"), "{err}");
// And the sandbox, the other spender, likewise.
let err = Registry::from_config_with(&Config::from_toml(&format!("{base}[clouds.sandbox]\nenabled = true\n")).unwrap(), &linked).err().map(|e| e.to_string()).expect("no ceiling must refuse");
assert!(err.contains("[clouds.sandbox]") && err.contains("no `cap`"), "{err}");
}