crates/gunnar-grpc/tests/operator_claim.rs
//! **`Accounts.ClaimOperator` — the account the first admin's passkey IS**
//! (owner, 2026-10-04: "a YubiKey is used to register me as an admin user. I
//! can THEN select to ADD ssh keys to it" / "The UI shall not DO anything such
//! as create a user — it shall CALL gunnar-server").
//!
//! MEASURED on test2 the same morning: the console's first-admin registration
//! called `Access.AddPrincipal "rickard"` and gunnar refused it — "cannot create
//! the account namespace "rickard" for this user: namespace already exists" —
//! because a first push with the installer's admin key (`rickard/t2-scratch`)
//! had conjured that namespace with no owner. The passkey bound to nothing, and
//! every `/api/account/*` page answered 404. These rows drive the verb on the
//! wire, with authentication ON, on the test double; the fed name and the real
//! catalog are `gunnar-server`'s rows (`operator_claim.rs` there).
use std::sync::Arc;
use gunnar_grpc::{
AccountsService, Caller, ControlPlane, EventBus, Identity as _, MemoryControlPlane,
ServerConfig, ServiceContext,
};
use gunnar_proto::accounts_server::Accounts as _;
use gunnar_proto::*;
use tonic::{Code, Request};
const OPERATOR: &str = "rickard";
fn caller(id: &str) -> Caller {
Caller {
principal_id: id.to_owned(),
fingerprint: format!("SHA256:{id}"),
machine: false,
authenticated: true,
}
}
fn with<T>(caller: &Caller, body: T) -> Request<T> {
let mut request = Request::new(body);
request.extensions_mut().insert(caller.clone());
request
}
struct Rig {
plane: Arc<MemoryControlPlane>,
accounts: AccountsService,
/// The console's own principal: a machine, server-wide Admin.
console: Caller,
}
impl Rig {
fn new() -> Rig {
let plane = Arc::new(MemoryControlPlane::default());
plane
.add_principal("gunnar-ui", "gunnar-ui", PrincipalKind::Machine)
.expect("the console's principal");
plane.grant_server("gunnar-ui", GrantLevel::Admin);
let control: Arc<dyn ControlPlane> = plane.clone();
let ctx = ServiceContext::new(
control,
Arc::new(EventBus::new(16)),
ServerConfig::default(),
);
Rig {
accounts: AccountsService::new(ctx),
plane,
console: caller("gunnar-ui"),
}
}
async fn claim(
&self,
who: &Caller,
name: &str,
) -> Result<ClaimOperatorResponse, tonic::Status> {
self.accounts
.claim_operator(with(
who,
ClaimOperatorRequest {
name: name.into(),
..Default::default()
},
))
.await
.map(|r| r.into_inner())
}
}
/// ★ **The first claim MAKES the operator: a person, named after the account,
/// the account's namespace with it, gunnar-admin — and no key.** The console's
/// own credential is then the principal's first key, which gunnar takes with no
/// attestation, and the person's SSH keys come after it.
#[tokio::test]
async fn the_first_claim_makes_the_operator_a_keyless_gunnar_admin_with_its_account() {
let rig = Rig::new();
let claimed = rig
.claim(&rig.console, OPERATOR)
.await
.expect("the console may claim");
let principal = claimed.principal.expect("the operator comes back");
assert_eq!(principal.id, OPERATOR);
assert_eq!(principal.name, OPERATOR);
assert_eq!(
principal.kind,
PrincipalKind::User as i32,
"the operator is a person"
);
assert!(claimed.created, "this call made it");
assert!(
!claimed.adopted_namespace,
"there was no namespace to adopt"
);
assert_eq!(claimed.named_by, "asked", "the double keeps no feed");
assert!(
rig.plane.namespace(OPERATOR).expect("ask").is_some(),
"the operator's account namespace was not made with it"
);
assert!(
rig.plane
.may(
OPERATOR,
&gunnar_grpc::auth::Access::Server,
GrantLevel::Admin
)
.expect("ask"),
"the operator is the estate's admin"
);
assert!(
rig.plane
.list_keys(Some(OPERATOR))
.expect("keys")
.is_empty(),
"a key on the operator before the console's would make the console's owe an attestation"
);
}
/// ★ **The test2 morning: an ownerless namespace of the operator's name is
/// ADOPTED, not refused.** Before this the first admin's passkey bound to
/// nothing, for ever.
#[tokio::test]
async fn an_ownerless_namespace_of_the_operators_name_is_adopted_not_refused() {
let rig = Rig::new();
rig.plane
.create_namespace(OPERATOR, "conjured by a first push")
.expect("the namespace a first push conjured");
let claimed = rig
.claim(&rig.console, OPERATOR)
.await
.expect("the claim succeeds");
assert!(claimed.created);
assert!(
claimed.adopted_namespace,
"the conjured namespace was not adopted"
);
assert_eq!(claimed.principal.expect("principal").id, OPERATOR);
}
/// **Idempotent**: a second claim answers the same row and makes nothing.
#[tokio::test]
async fn a_second_claim_answers_the_same_operator_and_makes_nothing() {
let rig = Rig::new();
let first = rig.claim(&rig.console, OPERATOR).await.expect("first");
let again = rig.claim(&rig.console, OPERATOR).await.expect("again");
assert!(!again.created);
assert!(!again.adopted_namespace);
assert_eq!(
first.principal, again.principal,
"the pin is to the same row and stamp"
);
}
/// **Only a server Admin claims.** The account a person owns intrinsically is
/// not enough, and nobody unauthenticated gets near it.
#[tokio::test]
async fn only_a_server_admin_may_claim_the_operator() {
let rig = Rig::new();
rig.plane
.add_principal("anna", "anna", PrincipalKind::User)
.expect("anna");
rig.plane
.create_namespace("anna", "")
.expect("anna's account");
let refused = rig
.claim(&caller("anna"), OPERATOR)
.await
.expect_err("anna is not the estate's admin");
assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
let refused = rig
.accounts
.claim_operator(Request::new(ClaimOperatorRequest {
name: OPERATOR.into(),
..Default::default()
}))
.await
.expect_err("no caller at all");
assert_eq!(refused.code(), Code::Unauthenticated, "{refused:?}");
assert!(
rig.plane
.principal_by_name(OPERATOR)
.expect("ask")
.is_none(),
"a refused claim made something"
);
}
/// **A machine is never the operator**, and no name at all is no claim.
#[tokio::test]
async fn a_machine_under_the_name_or_no_name_at_all_is_a_precondition() {
let rig = Rig::new();
rig.plane
.add_principal("monetize", "monetize", PrincipalKind::Machine)
.expect("monetize");
let refused = rig
.claim(&rig.console, "monetize")
.await
.expect_err("a machine");
assert_eq!(refused.code(), Code::FailedPrecondition, "{refused:?}");
let refused = rig.claim(&rig.console, " ").await.expect_err("nobody");
assert_eq!(refused.code(), Code::FailedPrecondition, "{refused:?}");
}
/// The one rule both planes keep for WHICH name: the fed one wins.
#[test]
fn the_fed_name_wins_over_the_asked_one() {
use gunnar_grpc::seam::operator_name;
assert_eq!(
operator_name(Some("rickard"), "anna").unwrap(),
("rickard", "fed")
);
assert_eq!(
operator_name(Some(" "), "anna").unwrap(),
("anna", "asked")
);
assert_eq!(operator_name(None, " anna ").unwrap(), ("anna", "asked"));
assert!(operator_name(None, "").is_err());
}