#![cfg(feature = "replication")]
use std::sync::Arc;
use gunnar_core::ssh_key::PrivateKey;
use gunnar_core::{Principal, PrincipalKind};
use gunnar_repair::exec::execute;
use gunnar_repair::{Order, OrderAck};
use gunnar_server::control_plane::CoreControlPlane;
use gunnar_server::repair::{Entitlements, ServerEstate};
use gunnar_server::stores::ServerStores;
const GIB: u64 = 1 << 30;
struct Rig {
_dir: tempfile::TempDir,
stores: Arc<ServerStores>,
}
impl Rig {
fn serving() -> Rig {
let dir = tempfile::tempdir().expect("tempdir");
let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
stores.auth().add_principal(Principal { id: "acme".into(), name: "acme".into(), kind: PrincipalKind::User, created: 0 }).expect("acme");
stores.catalog().create_namespace("acme").expect("namespace");
Rig { _dir: dir, stores }
}
fn entitlements(&self) -> Entitlements {
Entitlements::of(Arc::clone(&self.stores)).with_control(Arc::new(CoreControlPlane::new(Arc::clone(&self.stores))))
}
fn estate(&self) -> ServerEstate {
let pem = gunnar_core::mint_ed25519().expect("mint");
let key = PrivateKey::from_openssh(std::str::from_utf8(&pem).unwrap()).expect("parse");
ServerEstate { key, own: "127.0.0.1:1".into(), entitlements: self.entitlements() }
}
async fn run(&self, seq: u64, order: Order) -> OrderAck {
execute(&self.estate(), seq, &order, &|| false).await
}
}
fn add_users(ask: serde_json::Value, order: &str) -> Order {
Order::AddUsers { tenant: "acme".into(), ask, order: order.into() }
}
#[tokio::test]
async fn a_paid_named_user_is_granted_on_the_account_and_the_ack_is_the_answer() {
let r = Rig::serving();
let a = r.run(1, add_users(serde_json::json!({"count": 1, "room_bytes": 0, "name": "eve"}), "gunnar/acme/users-1")).await;
assert!(a.ok, "{a:?}");
let answer: serde_json::Value = serde_json::from_str(&a.detail).expect("the UsersAnswer as JSON");
assert_eq!(answer["principal"], "eve", "{answer}");
assert_eq!(answer["room_bytes"], 10 * GIB, "the product's default room per user: {answer}");
assert_eq!(answer["unchanged"], false, "{answer}");
assert!(answer["invite_secret"].as_str().is_some_and(|s| s.len() >= 32), "a new named user's invite rides the first answer: {answer}");
assert!(r.stores.auth().principal("eve").unwrap().is_some(), "eve is made");
let again = r.run(2, add_users(serde_json::json!({"count": 1, "room_bytes": 0, "name": "eve"}), "gunnar/acme/users-1")).await;
assert!(again.ok, "monetize lost the ack and asks again: {again:?}");
let again: serde_json::Value = serde_json::from_str(&again.detail).unwrap();
assert_eq!(again["unchanged"], true, "the retry answers the first grant: {again}");
assert_eq!(again["invite_secret"].as_str().unwrap_or_default(), "", "no second invite: {again}");
}
#[tokio::test]
async fn seats_are_granted_unnamed_and_a_bad_ask_is_refused_finally() {
let r = Rig::serving();
let a = r.run(1, add_users(serde_json::json!({"count": 3, "room_bytes": 0}), "gunnar/acme/seats-1")).await;
assert!(a.ok, "{a:?}");
let answer: serde_json::Value = serde_json::from_str(&a.detail).unwrap();
assert_eq!(answer["seats"], 3, "{answer}");
let a = r.run(2, add_users(serde_json::json!({"count": 1, "room_bytes": 0, "name": "console-x"}), "gunnar/acme/users-2")).await;
assert!(!a.ok && a.detail.contains("console-x"), "a reserved name is refused, final: {a:?}");
let a = r.run(3, add_users(serde_json::json!({"count": 1, "room_bytes": GIB, "name": "zed"}), "gunnar/acme/users-3")).await;
assert!(!a.ok, "below the room's floor: {a:?}");
}
#[tokio::test]
async fn room_sold_past_the_set_is_kept_and_said_to_the_operator() {
let r = Rig::serving();
let a = r.run(1, Order::GrowRoom { need_bytes: 30 * GIB, reason: "acme: needs 30 GiB past the 10 the main has".into() }).await;
assert!(a.ok, "{a:?}");
assert!(a.detail.contains("30.0 GiB") || a.detail.contains("32212254720"), "{}", a.detail);
let alarms = gunnar_server::feed::alarms();
assert!(alarms.iter().any(|al| al.event == "grow.room_wanted" && al.detail.contains("acme")), "the operator is told on Admin.Health: {alarms:?}");
}
#[tokio::test]
async fn the_mains_report_carries_each_accounts_room_for_add_users() {
let r = Rig::serving();
let text = r.entitlements().product(1_900_000_000_000).expect("a serving main reports its product");
let report: serde_json::Value = serde_json::from_str(&text).unwrap();
let acme = &report["accounts"]["acme"];
assert_eq!(acme["room_bytes"], 10 * GIB, "the room one user holds: {report}");
assert_eq!(acme["seats"], 0, "{report}");
}