use super::*;
mod embed_root_cases {
use camino::Utf8PathBuf;
use color_eyre::eyre::{Result, ensure, eyre};
use super::*;
pub(super) type Resolver = fn(&PgEnvCfg) -> BootstrapResult<SettingsPaths>;
fn scratch(name: &str) -> Result<Utf8PathBuf> {
Utf8PathBuf::from_path_buf(std::env::temp_dir().join(name))
.map_err(|path| eyre!("temp dir is not UTF-8: {}", path.display()))
}
fn leaf(paths: &SettingsPaths, runtime: bool) -> (Utf8PathBuf, bool) {
if runtime {
(paths.install_dir.clone(), paths.install_default)
} else {
(paths.data_dir.clone(), paths.data_default)
}
}
pub(super) fn assert_root_derives_leaf(resolve: Resolver, runtime: bool) -> Result<()> {
let root = scratch("pg-embed-root")?;
let cfg = PgEnvCfg {
embed_root: Some(root.clone()),
..PgEnvCfg::default()
};
let paths = resolve(&cfg)?;
let expected = root.join(if runtime { "install" } else { "data" });
let observed = leaf(&paths, runtime);
ensure!(
observed == (expected, true),
"leaf not derived from root: {observed:?}"
);
Ok(())
}
pub(super) fn assert_explicit_leaf_wins(resolve: Resolver, runtime: bool) -> Result<()> {
let root = scratch("pg-embed-root")?;
let explicit = scratch("elsewhere")?;
let cfg = PgEnvCfg {
embed_root: Some(root.clone()),
runtime_dir: runtime.then(|| explicit.clone()),
data_dir: (!runtime).then(|| explicit.clone()),
..PgEnvCfg::default()
};
let paths = resolve(&cfg)?;
let chosen = leaf(&paths, runtime);
ensure!(
chosen == (explicit, false),
"explicit leaf lost: {chosen:?}"
);
let other = root.join(if runtime { "data" } else { "install" });
let derived = leaf(&paths, !runtime);
ensure!(
derived == (other, true),
"other leaf not derived: {derived:?}"
);
Ok(())
}
}
#[cfg(not(all(unix, privileged_unix_platform)))]
mod portable_root_tests {
use rstest::rstest;
use super::*;
fn resolve(cfg: &PgEnvCfg) -> BootstrapResult<SettingsPaths> {
let mut settings = cfg.to_settings()?;
resolve_settings_paths_for_current_user(&mut settings, cfg)
}
#[test]
fn settings_defaults_are_kept_without_embed_root() {
let cfg = PgEnvCfg::default();
let mut settings = cfg.to_settings().expect("settings");
let expected_install = settings.installation_dir.clone();
let expected_data = settings.data_dir.clone();
let paths =
resolve_settings_paths_for_current_user(&mut settings, &cfg).expect("settings paths");
assert_eq!(paths.install_dir.as_std_path(), expected_install);
assert_eq!(paths.data_dir.as_std_path(), expected_data);
assert!(!paths.install_default && !paths.data_default);
}
#[rstest]
#[case::install(true)]
#[case::data(false)]
fn embed_root_derives_both_leaves(#[case] runtime: bool) {
embed_root_cases::assert_root_derives_leaf(resolve, runtime).expect("embed root contract");
}
#[rstest]
#[case::runtime_dir(true)]
#[case::data_dir(false)]
fn explicit_leaf_wins_over_embed_root(#[case] runtime: bool) {
embed_root_cases::assert_explicit_leaf_wins(resolve, runtime).expect("embed root contract");
}
}
#[cfg(all(unix, privileged_unix_platform))]
mod unix_tests {
use std::{
os::unix::fs::{MetadataExt, PermissionsExt},
sync::mpsc,
thread,
time::Duration,
};
use nix::{
sys::stat::Mode,
unistd::{Uid, User, geteuid},
};
use rstest::rstest;
use tempfile::tempdir;
use super::*;
use crate::{
bootstrap::prepare::unix_user::ensure_pgpass_for_user,
privileges::default_paths_for,
};
#[test]
fn ensure_settings_paths_applies_defaults() {
let cfg = PgEnvCfg::default();
let mut settings = cfg.to_settings().expect("default config should convert");
let uid = Uid::from_raw(9999);
let paths =
resolve_settings_paths_for_uid(&mut settings, &cfg, uid).expect("settings paths");
let (expected_install, expected_data) = default_paths_for(uid);
assert_eq!(paths.install_dir, expected_install);
assert_eq!(paths.data_dir, expected_data);
assert_eq!(paths.password_file, expected_install.join(".pgpass"));
assert!(paths.install_default);
assert!(paths.data_default);
}
#[test]
fn default_paths_under_derive_leaves_from_root() {
let (install, data) = default_paths_under(Utf8Path::new("/srv/project/pg"));
assert_eq!(install.as_str(), "/srv/project/pg/install");
assert_eq!(data.as_str(), "/srv/project/pg/data");
}
fn resolve(cfg: &PgEnvCfg) -> BootstrapResult<SettingsPaths> {
let mut settings = cfg.to_settings()?;
resolve_settings_paths_for_uid(&mut settings, cfg, Uid::from_raw(9999))
}
#[rstest]
#[case::install(true)]
#[case::data(false)]
fn ensure_settings_paths_honours_embed_root(#[case] runtime: bool) {
embed_root_cases::assert_root_derives_leaf(resolve, runtime).expect("embed root contract");
}
#[rstest]
#[case::runtime_dir(true)]
#[case::data_dir(false)]
fn explicit_leaf_wins_over_embed_root(#[case] runtime: bool) {
embed_root_cases::assert_explicit_leaf_wins(resolve, runtime).expect("embed root contract");
}
#[test]
fn ensure_settings_paths_respects_user_provided_dirs() {
let sandbox = tempdir().expect("settings sandbox");
let install_path = sandbox.path().join("install");
let data_path = sandbox.path().join("data");
let install_dir =
Utf8PathBuf::from_path_buf(install_path).expect("install dir should be utf8");
let data_dir = Utf8PathBuf::from_path_buf(data_path).expect("data dir should be utf8");
let cfg = PgEnvCfg {
runtime_dir: Some(install_dir.clone()),
data_dir: Some(data_dir.clone()),
..PgEnvCfg::default()
};
let mut settings = cfg.to_settings().expect("custom config should convert");
let uid = Uid::from_raw(4242);
let paths =
resolve_settings_paths_for_uid(&mut settings, &cfg, uid).expect("settings paths");
assert_eq!(paths.install_dir, install_dir);
assert_eq!(paths.data_dir, data_dir);
assert_eq!(paths.password_file, paths.install_dir.join(".pgpass"));
assert!(!paths.install_default);
assert!(!paths.data_default);
}
#[test]
fn ensure_pgpass_for_user_sets_permissions_and_owner() {
let sandbox = tempdir().expect("pgpass sandbox");
let path = sandbox.path().join(".pgpass");
std::fs::write(&path, b"test").expect("write pgpass");
let mut perms = std::fs::metadata(&path)
.expect("pgpass metadata")
.permissions();
perms.set_mode(0o644);
std::fs::set_permissions(&path, perms).expect("set initial pgpass permissions");
let user = User::from_uid(geteuid())
.expect("resolve current user")
.expect("current user should exist");
let utf8_path = Utf8PathBuf::from_path_buf(path).expect("pgpass path utf8");
ensure_pgpass_for_user(&utf8_path, &user).expect("ensure pgpass for user");
let metadata = std::fs::metadata(utf8_path.as_std_path()).expect("pgpass metadata");
let observed_mode = metadata.permissions().mode() & 0o777;
assert_eq!(observed_mode, PGPASS_MODE);
assert_eq!(metadata.uid(), user.uid.as_raw());
assert_eq!(metadata.gid(), user.gid.as_raw());
}
#[test]
fn ensure_pgpass_for_user_rejects_fifo_without_blocking() {
let sandbox = tempdir().expect("pgpass sandbox");
let path = sandbox.path().join(".pgpass");
nix::unistd::mkfifo(&path, Mode::S_IRUSR | Mode::S_IWUSR).expect("create pgpass FIFO");
let user = User::from_uid(geteuid())
.expect("resolve current user")
.expect("current user should exist");
let utf8_path = Utf8PathBuf::from_path_buf(path).expect("pgpass path utf8");
let (sender, receiver) = mpsc::channel();
let worker = thread::spawn(move || {
sender
.send(ensure_pgpass_for_user(&utf8_path, &user))
.expect("send pgpass preparation result");
});
let result = receiver
.recv_timeout(Duration::from_secs(2))
.expect("PGPASSFILE FIFO validation must not block without a writer");
worker.join().expect("pgpass preparation worker");
let err = result.expect_err("PGPASSFILE FIFO validation should fail");
assert!(
err.to_string().contains("must reference a regular file"),
"expected regular-file rejection, got: {err}"
);
}
}