mod common;
use std::sync::Arc;
use common::{mint_key, plane_with_key, principal, the_repo};
use gunnar_core::PrincipalKind;
use gunnar_ssh::command::{GitService, Operation};
use gunnar_ssh::server::{Dispatch, DispatchError};
use gunnar_ssh::{Access, DiskFloor, DiskHeadroomGate, PlanAction, PlanGate, PlanVerdict};
fn full_disk(root: &std::path::Path) -> Arc<DiskHeadroomGate> {
Arc::new(DiskHeadroomGate::new(
root,
DiskFloor {
percent: 0,
bytes: u64::MAX,
},
))
}
fn roomy_disk(root: &std::path::Path) -> Arc<DiskHeadroomGate> {
Arc::new(DiskHeadroomGate::new(
root,
DiskFloor {
percent: 0,
bytes: 1,
},
))
}
struct LapsedPlan;
impl PlanGate for LapsedPlan {
fn verdict(&self, _: &str, action: PlanAction) -> PlanVerdict {
match action {
PlanAction::Write => PlanVerdict::Refuse("plan 10gb expired: writes closed".into()),
PlanAction::AnonymousRead => PlanVerdict::Allow,
}
}
fn pack_quota(&self, _: &str) -> PlanVerdict {
PlanVerdict::Allow
}
fn name(&self) -> &'static str {
"lapsed-plan"
}
}
struct OpenGate;
impl PlanGate for OpenGate {
fn verdict(&self, _: &str, _: PlanAction) -> PlanVerdict {
PlanVerdict::Allow
}
fn pack_quota(&self, _: &str) -> PlanVerdict {
PlanVerdict::Allow
}
fn name(&self) -> &'static str {
"open"
}
}
fn push(dispatch: &Dispatch) -> Result<(), DispatchError> {
dispatch
.authorise_service(
&principal("principal-1"),
None,
GitService::ReceivePack,
the_repo(),
)
.map(|_| ())
}
#[test]
fn a_floor_above_the_free_space_refuses_the_push_before_any_pack_byte_and_names_the_numbers() {
let dir = tempfile::tempdir().expect("tempdir");
let key = mint_key();
let plane = plane_with_key(&key, Access::Write);
let gate = full_disk(dir.path());
let room = gate.headroom().expect("statvfs");
let dispatch = Dispatch::new(plane.control.with_plan_gate(gate));
let err = push(&dispatch).expect_err("a full disk must refuse the push");
let DispatchError::Plan { namespace, line } = &err else {
panic!("the refusal must be the gate's own arm, not a denial: {err:?}");
};
assert_eq!(namespace, "nordisk");
assert!(
line.starts_with(gunnar_ssh::AT_FLOOR)
&& !line.contains(&gunnar_ssh::headroom::human(room.floor)),
"the line says the storage is full, without numbers: {line}"
);
assert_eq!(
err.client_message().as_deref(),
Some(line.as_str()),
"the client is told the whole line — the numbers are what the operator needs"
);
let lfs = dispatch
.authorise_service(
&principal("principal-1"),
None,
GitService::LfsTransfer(Operation::Upload),
the_repo(),
)
.expect_err("an LFS upload grows the disk too");
assert!(matches!(lfs, DispatchError::Plan { .. }), "{lfs:?}");
dispatch
.authorise_service(
&principal("principal-1"),
None,
GitService::UploadPack,
the_repo(),
)
.expect("a clone from a full disk still works");
}
#[test]
fn a_floor_below_the_free_space_allows_the_push() {
let dir = tempfile::tempdir().expect("tempdir");
let key = mint_key();
let plane = plane_with_key(&key, Access::Write);
let dispatch = Dispatch::new(plane.control.with_plan_gate(roomy_disk(dir.path())));
push(&dispatch).expect("room under the floor is an allowed push");
}
#[test]
fn the_chain_is_asked_in_order_and_the_first_refusal_wins() {
let dir = tempfile::tempdir().expect("tempdir");
let key = mint_key();
let plane = plane_with_key(&key, Access::Write);
let dispatch = Dispatch::new(
plane
.control
.with_plan_gate(Arc::new(OpenGate))
.with_plan_gate(full_disk(dir.path()))
.with_plan_gate(Arc::new(LapsedPlan)),
);
let err = push(&dispatch).expect_err("refused");
let DispatchError::Plan { line, .. } = &err else {
panic!("{err:?}");
};
assert!(
line.starts_with(gunnar_ssh::AT_FLOOR) && !line.contains("plan 10gb"),
"the DISK's line must win over the plan's: {line}"
);
assert_eq!(
dispatch
.control()
.plan_gates()
.iter()
.map(|g| g.name())
.collect::<Vec<_>>(),
["open", "disk-headroom", "lapsed-plan"],
"the chain is exactly the order it was built in"
);
let plane = plane_with_key(&key, Access::Write);
let dispatch = Dispatch::new(
plane
.control
.with_plan_gate(Arc::new(LapsedPlan))
.with_plan_gate(full_disk(dir.path())),
);
let err = push(&dispatch).expect_err("refused");
let DispatchError::Plan { line, .. } = &err else {
panic!("{err:?}");
};
assert!(
line.contains("plan 10gb"),
"the PLAN's line must win here: {line}"
);
let plane = plane_with_key(&key, Access::Write);
let dispatch = Dispatch::new(
plane
.control
.with_plan_gate(roomy_disk(dir.path()))
.with_plan_gate(Arc::new(LapsedPlan)),
);
let err = push(&dispatch).expect_err("the plan still refuses");
assert!(matches!(&err, DispatchError::Plan { line, .. } if line.contains("plan 10gb")));
}
#[test]
fn the_disk_binds_the_replication_machine_and_a_plan_does_not() {
let dir = tempfile::tempdir().expect("tempdir");
let key = mint_key();
let machine =
gunnar_core::Principal::new("primary", "primary", PrincipalKind::Machine).expect("legal");
let build = |gate: Arc<dyn PlanGate>| {
let plane = plane_with_key(&key, Access::Write);
plane
.auth()
.add_principal(machine.clone())
.expect("machine");
plane
.auth()
.grant(gunnar_core::Grant::repo(
"primary",
the_repo(),
gunnar_core::Level::Write,
))
.expect("grant");
Dispatch::new(plane.control.with_plan_gate(gate))
};
let attempt = |dispatch: &Dispatch| {
dispatch
.authorise_service(&machine, None, GitService::ReceivePack, the_repo())
.map(|_| ())
};
attempt(&build(Arc::new(LapsedPlan))).expect("replication is not a tenant growing data");
let err = attempt(&build(full_disk(dir.path()))).expect_err("a full standby refuses");
assert!(
matches!(&err, DispatchError::Plan { line, .. } if line.starts_with(gunnar_ssh::AT_FLOOR)),
"{err:?}"
);
attempt(&build(roomy_disk(dir.path()))).expect("room, so the copy lands");
}
#[test]
fn no_gate_wired_is_every_namespace_free_with_infinite_disk() {
let key = mint_key();
let plane = plane_with_key(&key, Access::Write);
let dispatch = Dispatch::new(plane.control);
assert!(dispatch.control().plan_gates().is_empty());
push(&dispatch).expect("a bare Control gates nothing");
}