use super::*;
use crate::owner_lock::{OwnerLock, try_acquire_owner_lock};
use crate::test_support::{fixture, remove};
use crate::transaction_owner::{current_transaction_owner, encode_transaction_owner};
fn pair(name: &str, value: &str) -> (OsString, OsString) {
return (OsString::from(name), OsString::from(value));
}
fn transaction(root: &Path, owner_bytes: &[u8], plan_lease: &str) -> PathBuf {
let directory: PathBuf = root.join("0b6c2c1e-6f5b-4d0e-9a55-3f5d8e2f6a10");
std::fs::create_dir_all(directory.join("hooks")).expect("hooks directory");
std::fs::write(directory.join(OWNER_FILENAME), owner_bytes).expect("owner record");
let mut plan: ObjectWriter = ObjectWriter::new();
plan.string("lease", plan_lease);
std::fs::write(directory.join("hooks").join("plan.json"), plan.finish()).expect("plan");
return directory;
}
fn live_owner() -> Vec<u8> {
let owner: crate::transaction_owner::TransactionOwner = current_transaction_owner(
"0b6c2c1e-6f5b-4d0e-9a55-3f5d8e2f6a10",
"2026-10-06T00:00:00.000Z",
)
.expect("current owner");
return encode_transaction_owner(&owner).into_bytes();
}
#[test]
fn leases_have_the_incumbent_spelling() {
assert_eq!(
format_preparation_lease("/repo/.git/cli-git-transactions/x", "t\"1"),
"{\"directory\":\"/repo/.git/cli-git-transactions/x\",\"token\":\"t\\\"1\"}"
);
assert_eq!(
format_landing_lease("/repo/.git/cli-git-transactions/landing.lock", "t"),
"{\"lockDirectory\":\"/repo/.git/cli-git-transactions/landing.lock\",\"token\":\"t\"}"
);
}
#[test]
fn preparation_leases_need_the_plan_and_a_live_owner() {
let root: PathBuf = fixture("lease-preparation");
let directory_text: String = root
.join("0b6c2c1e-6f5b-4d0e-9a55-3f5d8e2f6a10")
.to_str()
.expect("UTF-8 fixture")
.to_string();
let lease: String = format_preparation_lease(directory_text.as_str(), "token");
let directory: PathBuf = transaction(root.as_path(), live_owner().as_slice(), lease.as_str());
let valid: Vec<(OsString, OsString)> = vec![pair(PREPARATION_LEASE_VARIABLE, lease.as_str())];
assert!(has_valid_preparation_lease(valid.as_slice()));
let overridden: Vec<(OsString, OsString)> = vec![
pair(PREPARATION_LEASE_VARIABLE, "{}"),
pair(PREPARATION_LEASE_VARIABLE, lease.as_str()),
];
assert!(has_valid_preparation_lease(overridden.as_slice()));
let shadowed: Vec<(OsString, OsString)> = vec![
pair(PREPARATION_LEASE_VARIABLE, lease.as_str()),
pair(PREPARATION_LEASE_VARIABLE, ""),
];
assert!(!has_valid_preparation_lease(shadowed.as_slice()));
assert!(!has_valid_preparation_lease(&[]));
assert!(!has_valid_preparation_lease(&[pair(
"OTHER",
lease.as_str()
)]));
assert!(!has_valid_preparation_lease(&[pair(
PREPARATION_LEASE_VARIABLE,
"not json"
)]));
assert!(!has_valid_preparation_lease(&[pair(
PREPARATION_LEASE_VARIABLE,
"{\"token\":\"t\"}"
)]));
let other: String = format_preparation_lease(directory_text.as_str(), "other");
assert!(!has_valid_preparation_lease(&[pair(
PREPARATION_LEASE_VARIABLE,
other.as_str()
)]));
std::fs::write(directory.join("hooks").join("plan.json"), "[]").expect("array plan");
assert!(!has_valid_preparation_lease(valid.as_slice()));
std::fs::write(directory.join("hooks").join("plan.json"), "{}").expect("empty plan");
assert!(!has_valid_preparation_lease(valid.as_slice()));
std::fs::write(directory.join("hooks").join("plan.json"), [0xff, 0xfe]).expect("binary plan");
assert!(!has_valid_preparation_lease(valid.as_slice()));
std::fs::remove_file(directory.join("hooks").join("plan.json")).expect("remove plan");
assert!(!has_valid_preparation_lease(valid.as_slice()));
remove(root.as_path());
}
#[test]
fn preparation_leases_die_with_their_owner() {
let root: PathBuf = fixture("lease-owner");
let directory_text: String = root
.join("0b6c2c1e-6f5b-4d0e-9a55-3f5d8e2f6a10")
.to_str()
.expect("UTF-8 fixture")
.to_string();
let lease: String = format_preparation_lease(directory_text.as_str(), "token");
let environment: Vec<(OsString, OsString)> =
vec![pair(PREPARATION_LEASE_VARIABLE, lease.as_str())];
let mut dead: crate::transaction_owner::TransactionOwner = current_transaction_owner(
"0b6c2c1e-6f5b-4d0e-9a55-3f5d8e2f6a10",
"2026-10-06T00:00:00.000Z",
)
.expect("current owner");
dead.owner_identity = String::from("linux:1");
let directory: PathBuf = transaction(
root.as_path(),
encode_transaction_owner(&dead).as_bytes(),
lease.as_str(),
);
assert!(!has_valid_preparation_lease(environment.as_slice()));
std::fs::write(directory.join(OWNER_FILENAME), "{}").expect("malformed owner");
assert!(!has_valid_preparation_lease(environment.as_slice()));
std::fs::remove_file(directory.join(OWNER_FILENAME)).expect("remove owner");
assert!(!has_valid_preparation_lease(environment.as_slice()));
std::fs::write(directory.join(OWNER_FILENAME), live_owner()).expect("live owner");
assert!(has_valid_preparation_lease(environment.as_slice()));
remove(root.as_path());
}
#[test]
fn landing_leases_name_their_lock_and_token() {
let root: PathBuf = fixture("lease-landing");
let lock_directory: PathBuf = root.join("landing.lock");
let lock: OwnerLock = try_acquire_owner_lock(lock_directory.as_path(), None)
.expect("acquire")
.expect("free lock");
let text: &str = lock_directory.to_str().expect("UTF-8 fixture");
let lease: String = format_landing_lease(text, lock.token());
let environment: Vec<(OsString, OsString)> = vec![pair(LANDING_LEASE_VARIABLE, lease.as_str())];
assert!(has_valid_landing_lease(
environment.as_slice(),
lock_directory.as_path()
));
assert!(!has_valid_landing_lease(
environment.as_slice(),
root.join("other.lock").as_path()
));
let spelled: PathBuf = root.join(".").join("landing.lock");
assert!(!has_valid_landing_lease(
environment.as_slice(),
spelled.as_path()
));
let stale: String = format_landing_lease(text, "another-token");
assert!(!has_valid_landing_lease(
&[pair(LANDING_LEASE_VARIABLE, stale.as_str())],
lock_directory.as_path()
));
assert!(!has_valid_landing_lease(&[], lock_directory.as_path()));
assert!(!has_valid_landing_lease(
&[pair(LANDING_LEASE_VARIABLE, "[]")],
lock_directory.as_path()
));
let without_token: String =
format!("{{\"lockDirectory\":{}}}", crate::json_record::quote(text));
assert!(!has_valid_landing_lease(
&[pair(LANDING_LEASE_VARIABLE, without_token.as_str())],
lock_directory.as_path()
));
lock.release().expect("release");
assert!(!has_valid_landing_lease(
environment.as_slice(),
lock_directory.as_path()
));
std::fs::create_dir(&lock_directory).expect("ownerless lock");
assert!(!has_valid_landing_lease(
environment.as_slice(),
lock_directory.as_path()
));
std::fs::write(lock_directory.join("owner.json"), "{").expect("malformed owner");
assert!(!has_valid_landing_lease(
environment.as_slice(),
lock_directory.as_path()
));
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn non_unicode_leases_are_ignored() {
use std::os::unix::ffi::OsStringExt;
let environment: Vec<(OsString, OsString)> = vec![(
OsString::from(PREPARATION_LEASE_VARIABLE),
OsString::from_vec(vec![0xff, b'{', b'}']),
)];
assert!(!has_valid_preparation_lease(environment.as_slice()));
let landing: Vec<(OsString, OsString)> = vec![(
OsString::from(LANDING_LEASE_VARIABLE),
OsString::from_vec(vec![0xff]),
)];
assert!(!has_valid_landing_lease(
landing.as_slice(),
Path::new("/x")
));
}
#[test]
fn the_landing_lease_token_is_its_own_field() {
let lease: String = format_landing_lease("/r/landing.lock", "token-1");
let named = |value: &str| { return vec![(OsString::from(LANDING_LEASE_VARIABLE), OsString::from(value))]; };
assert_eq!(landing_lease_token(named(lease.as_str()).as_slice()), Some(String::from("token-1")));
assert_eq!(landing_lease_token(&[]), None);
assert_eq!(landing_lease_token(named("").as_slice()), None);
assert_eq!(landing_lease_token(named("not json").as_slice()), None);
assert_eq!(landing_lease_token(named("{\"lockDirectory\":\"/r\"}").as_slice()), None);
}