crates/gunnar-server/tests/add_principal.rs
//! **`add_principal`** (5.4, ONE monetize, G1 pull): monetize never dials an estate, so a
//! paid signup's account leaves monetize as a signed order the main pulls. The main makes
//! the principal and its namespace (the `Access.AddPrincipal` path) and acks monetize's
//! contract (monetize-product `pull::order::ADD_PRINCIPAL`):
//!
//! * ok, the detail the namespace made — also on a repeat for the same `signup` (monetize
//! asks again when it lost the ack), across a restart;
//! * not ok, the detail starting `taken` — the name is somebody else's;
//! * not ok, anything else — final, in words (a box that does not serve, an `org`, a
//! reserved name).
//!
//! Driven through `gunnar_repair::exec::execute`, the one dispatch a pulled order takes,
//! on the server's own [`ServerEstate`].
#![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;
struct Rig {
dir: tempfile::TempDir,
stores: Arc<ServerStores>,
}
impl Rig {
/// A main that serves: alice (a person), and a namespace `team` nobody's principal.
fn serving() -> Rig {
let r = Rig::fresh();
r.stores
.auth()
.add_principal(Principal {
id: "alice".into(),
name: "alice".into(),
kind: PrincipalKind::User,
created: 0,
})
.expect("alice");
for ns in ["alice", "team"] {
r.stores.catalog().create_namespace(ns).expect("namespace");
}
r
}
/// A re-laid main nobody restored: it does not serve.
fn fresh() -> Rig {
let dir = tempfile::tempdir().expect("tempdir");
let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
Rig { dir, stores }
}
/// The same box after a restart: its stores reopened from disk.
fn restarted(self) -> Rig {
let Rig { dir, stores } = self;
drop(stores);
let stores = Arc::new(ServerStores::open(dir.path()).expect("stores again"));
Rig { dir, 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");
let plane = CoreControlPlane::new(Arc::clone(&self.stores));
ServerEstate {
key,
own: "127.0.0.1:1".into(),
entitlements: Entitlements::of(Arc::clone(&self.stores)).with_control(Arc::new(plane)),
}
}
async fn add(&self, seq: u64, username: &str, kind: &str, signup: &str) -> OrderAck {
let order = Order::AddPrincipal {
username: username.into(),
principal_kind: kind.into(),
signup: signup.into(),
};
execute(&self.estate(), seq, &order, &|| false).await
}
fn has(&self, name: &str) -> (bool, bool) {
(
self.stores.auth().principal(name).expect("auth").is_some(),
self.stores
.catalog()
.namespace(name)
.expect("catalog")
.is_some(),
)
}
}
#[tokio::test]
async fn a_paid_signup_gets_its_person_and_namespace_and_the_ack_names_the_namespace() {
let r = Rig::serving();
let a = r.add(1, "bob", "person", "stripe:cs_1").await;
assert!(a.ok, "{a:?}");
assert_eq!((a.seq, a.detail.as_str()), (1, "bob"));
assert_eq!(r.has("bob"), (true, true));
let bob = r.stores.auth().principal("bob").unwrap().unwrap();
assert_eq!(bob.kind, PrincipalKind::User, "a person");
}
#[tokio::test]
async fn the_same_signup_asked_again_is_ok_also_after_a_restart() {
let r = Rig::serving();
assert!(r.add(1, "bob", "person", "stripe:cs_1").await.ok);
let a = r.add(2, "bob", "person", "stripe:cs_1").await;
assert!(a.ok, "monetize lost the ack and asks again: {a:?}");
assert_eq!(a.detail, "bob");
let r = r.restarted();
let a = r.add(3, "bob", "person", "stripe:cs_1").await;
assert!(a.ok, "the signup's record outlives a restart (it is in the control store, which the twin follows): {a:?}");
assert_eq!(
r.stores
.auth()
.principals()
.unwrap()
.iter()
.filter(|p| p.id == "bob")
.count(),
1
);
}
#[tokio::test]
async fn a_name_that_is_somebody_elses_is_taken() {
let r = Rig::serving();
assert!(r.add(1, "bob", "person", "stripe:cs_1").await.ok);
for (seq, name, signup) in [
(2, "bob", "stripe:cs_2"),
(3, "alice", "stripe:cs_3"),
(4, "team", "stripe:cs_4"),
] {
let a = r.add(seq, name, "person", signup).await;
assert!(!a.ok, "{name}: {a:?}");
assert!(
a.detail.starts_with("taken"),
"{name}: monetize reads `taken` and the buyer picks another: {}",
a.detail
);
}
assert_eq!(
r.has("team"),
(false, true),
"a taken name is left as it was"
);
}
#[tokio::test]
async fn an_org_and_a_reserved_name_are_refused_finally_and_not_as_taken() {
let r = Rig::serving();
let a = r.add(1, "acme", "org", "stripe:cs_1").await;
assert!(
!a.ok && a.detail.starts_with("bad_order:") && a.detail.contains("org"),
"{a:?}"
);
let a = r.add(2, "console-x", "person", "stripe:cs_2").await;
assert!(
!a.ok && !a.detail.starts_with("taken") && a.detail.contains("reserved"),
"{a:?}"
);
assert_eq!(
(r.has("acme"), r.has("console-x")),
((false, false), (false, false))
);
}
#[tokio::test]
async fn a_box_that_does_not_serve_makes_no_account_and_says_so() {
let r = Rig::fresh();
let a = r.add(1, "bob", "person", "stripe:cs_1").await;
assert!(!a.ok && a.detail.starts_with("not_serving:"), "{a:?}");
assert_eq!(r.has("bob"), (false, false));
}
#[tokio::test]
async fn a_box_without_its_control_plane_refuses_by_name() {
let r = Rig::serving();
let estate = ServerEstate {
entitlements: Entitlements::of(Arc::clone(&r.stores)),
..r.estate()
};
let a = execute(
&estate,
1,
&Order::AddPrincipal {
username: "bob".into(),
principal_kind: "person".into(),
signup: "s".into(),
},
&|| false,
)
.await;
assert!(
!a.ok && a.detail.starts_with("bad_order:") && a.detail.contains("account"),
"{a:?}"
);
assert_eq!(r.has("bob"), (false, false));
}