use std::sync::Arc;
use gunnar_core::{Grant, Level, NewRepo, Principal, PrincipalKind, RepoRef};
use gunnar_grpc::{ControlError, ControlPlane as _};
use gunnar_proto::RepoPath;
use gunnar_server::control_plane::CoreControlPlane;
use gunnar_server::stores::ServerStores;
use gunnar_ssh::command::GitService;
use gunnar_ssh::server::{Dispatch, DispatchError};
use gunnar_ssh::{Control, DiskFloor, DiskHeadroomGate};
struct Rig {
_dir: tempfile::TempDir,
stores: Arc<ServerStores>,
}
impl Rig {
fn new() -> Rig {
let dir = tempfile::tempdir().expect("tempdir");
let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
stores
.catalog()
.create_namespace("team")
.expect("namespace");
stores
.auth()
.add_principal(Principal::new("owner", "owner", PrincipalKind::User).expect("legal"))
.expect("principal");
stores
.auth()
.grant(Grant::namespace("owner", "team", Level::Admin))
.expect("grant");
Rig { _dir: dir, stores }
}
fn gate(&self, bytes: u64) -> Arc<DiskHeadroomGate> {
Arc::new(DiskHeadroomGate::new(
self.stores.root(),
DiskFloor { percent: 0, bytes },
))
}
fn front(&self, gate: Arc<DiskHeadroomGate>) -> Control {
#[cfg(feature = "metered")]
{
gunnar_server::serve::front_control(&self.stores, Default::default(), gate, None)
}
#[cfg(not(feature = "metered"))]
{
gunnar_server::serve::front_control(&self.stores, Default::default(), gate)
}
}
fn create(&self, plane: &CoreControlPlane, name: &str) -> Result<(), ControlError> {
plane
.create_repo(gunnar_grpc::NewRepo {
path: RepoPath::new("team", name),
hash_kind: gunnar_proto::HashKind::Unspecified,
default_branch: String::new(),
visibility: gunnar_proto::Visibility::Private,
description: String::new(),
adopt: false,
})
.map(|_| ())
}
fn rows(&self) -> Vec<String> {
self.stores
.catalog()
.list(Some("team"))
.expect("list")
.iter()
.map(|r| r.name.clone())
.collect()
}
}
fn owner() -> Principal {
Principal::new("owner", "owner", PrincipalKind::User).expect("legal")
}
#[test]
fn repos_create_under_the_floor_is_refused_with_the_gates_line_and_leaves_nothing_behind() {
let rig = Rig::new();
let gate = rig.gate(u64::MAX);
let room = gate.headroom().expect("statvfs");
let plane = CoreControlPlane::new(Arc::clone(&rig.stores)).with_headroom(Arc::clone(&gate));
let err = rig
.create(&plane, "proj")
.expect_err("a full disk must refuse a new repository");
let ControlError::ResourceExhausted(line) = &err else {
panic!("RESOURCE_EXHAUSTED, not {err:?}");
};
assert_eq!(
Some(line.as_str()),
gate.refusal().as_deref(),
"the RPC's line IS the gate's line — one function, both doors"
);
assert!(
line.starts_with(gunnar_ssh::AT_FLOOR)
&& !line.contains(&gunnar_ssh::headroom::human(room.free)),
"the line says the storage is full, without numbers: {line}"
);
assert!(
rig.rows().is_empty(),
"no catalog row may be left: {:?}",
rig.rows()
);
let path = RepoRef::new("team", "proj").expect("legal");
assert!(
!rig.stores.repo_dir(&path).expect("a path").exists(),
"no directory may be left behind either"
);
}
#[test]
fn repos_create_with_room_under_the_floor_is_allowed() {
let rig = Rig::new();
let plane = CoreControlPlane::new(Arc::clone(&rig.stores)).with_headroom(rig.gate(1));
rig.create(&plane, "proj")
.expect("room, so the repository is created");
assert_eq!(rig.rows(), ["proj"]);
}
#[test]
fn a_bare_plane_gates_nothing() {
let rig = Rig::new();
let plane = CoreControlPlane::new(Arc::clone(&rig.stores));
rig.create(&plane, "proj")
.expect("no gate wired, no refusal");
}
#[test]
fn the_front_door_serve_builds_refuses_the_push_under_the_floor_and_allows_it_with_room() {
let rig = Rig::new();
let path = RepoRef::new("team", "proj").expect("legal");
rig.stores
.create_repo(NewRepo::new(path.clone()), false)
.expect("repo");
let control = rig.front(rig.gate(u64::MAX));
assert_eq!(
control
.plan_gates()
.iter()
.map(|g| g.name())
.collect::<Vec<_>>()[0],
"disk-headroom",
"the disk gate is the FIRST link of the front door's chain: {control:?}"
);
let err = Dispatch::new(control)
.authorise_service(&owner(), None, GitService::ReceivePack, path.clone())
.expect_err("a full disk refuses the push before any pack byte");
assert!(
matches!(&err, DispatchError::Plan { line, .. } if line.starts_with(gunnar_ssh::AT_FLOOR)),
"{err:?}"
);
Dispatch::new(rig.front(rig.gate(1)))
.authorise_service(&owner(), None, GitService::ReceivePack, path.clone())
.expect("room under the floor is an allowed push");
let fresh = RepoRef::new("team", "brand-new").expect("legal");
let err = Dispatch::new(rig.front(rig.gate(u64::MAX)))
.authorise_service(&owner(), None, GitService::ReceivePack, fresh)
.expect_err("push-to-create on a full disk is refused before the row");
assert!(matches!(&err, DispatchError::Plan { .. }), "{err:?}");
assert!(
rig.rows().iter().all(|r| r != "brand-new"),
"{:?}",
rig.rows()
);
}