#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use agent_first_psql::conn::{libpq_env_fallbacks_in_use, resolve_pg_config};
use agent_first_psql::types::SessionConfig;
use tokio_postgres::config::{Host, SslMode};
#[path = "support/env.rs"]
mod test_env;
const CONNECTION_VARS: [&str; 13] = [
"PGHOST",
"PGPORT",
"PGUSER",
"PGDATABASE",
"PGPASSWORD",
"PGSSLMODE",
"AFPSQL_HOST",
"AFPSQL_PORT",
"AFPSQL_USER",
"AFPSQL_DBNAME",
"AFPSQL_PASSWORD_SECRET",
"AFPSQL_DSN_SECRET",
"AFPSQL_CONNINFO_SECRET",
];
fn with_connection_env<T>(values: &[(&str, &str)], body: impl FnOnce() -> T) -> T {
let _guard = test_env::env_lock();
let saved: Vec<_> = CONNECTION_VARS
.iter()
.map(|name| (*name, std::env::var_os(name)))
.collect();
for name in CONNECTION_VARS {
unsafe { std::env::remove_var(name) };
}
for (name, value) in values {
unsafe { std::env::set_var(name, value) };
}
let result = body();
for (name, value) in saved {
match value {
Some(value) => unsafe { std::env::set_var(name, value) },
None => unsafe { std::env::remove_var(name) },
}
}
result
}
#[test]
fn locked_profile_endpoint_is_not_redirected_by_environment() {
let hostile = [
(
"AFPSQL_DSN_SECRET",
"postgresql://evil:pw@attacker.example/loot",
),
("PGHOST", "attacker.example"),
("PGPORT", "6543"),
("PGUSER", "evil"),
("PGDATABASE", "loot"),
("PGSSLMODE", "disable"),
];
let (pinned, unpinned) = with_connection_env(&hostile, || {
let pinned = resolve_pg_config(&SessionConfig {
profile_pinned: true,
host: Some("db.internal".to_string()),
port: Some(5432),
user: Some("app_reader".to_string()),
dbname: Some("app".to_string()),
..Default::default()
})
.expect("pinned config resolves");
let unpinned = resolve_pg_config(&SessionConfig {
host: Some("db.internal".to_string()),
..Default::default()
})
.expect("unpinned config resolves");
(pinned, unpinned)
});
assert_eq!(
pinned.get_hosts(),
&[Host::Tcp("db.internal".to_string())],
"an environment DSN redirected a locked profile"
);
assert_eq!(pinned.get_ports(), &[5432]);
assert_eq!(pinned.get_user(), Some("app_reader"));
assert_eq!(pinned.get_dbname(), Some("app"));
assert_eq!(
pinned.get_ssl_mode(),
SslMode::Prefer,
"PGSSLMODE downgraded TLS on a locked profile"
);
assert_eq!(
unpinned.get_dbname(),
Some("loot"),
"the environment was not actually in effect, so the pinned case proves nothing"
);
}
#[test]
fn resolve_conn_uses_pgpassword_fallback() {
let out = with_connection_env(&[("PGPASSWORD", "pgpass-test")], || {
resolve_pg_config(&SessionConfig {
host: Some("localhost".to_string()),
user: Some("roger".to_string()),
dbname: Some("postgres".to_string()),
..Default::default()
})
})
.expect("config resolves");
assert_eq!(out.get_password(), Some("pgpass-test".as_bytes()));
}
#[test]
fn libpq_env_fallbacks_lists_only_unfilled_pg_vars() {
let (only_explicit, with_defaults, with_dsn) =
with_connection_env(&[("PGHOST", "envhost"), ("PGPASSWORD", "envpw")], || {
let only_explicit = libpq_env_fallbacks_in_use(&SessionConfig {
host: Some("explicit".to_string()),
password_secret: Some("explicit-secret".to_string()),
..Default::default()
});
let with_defaults = libpq_env_fallbacks_in_use(&SessionConfig::default());
let with_dsn = libpq_env_fallbacks_in_use(&SessionConfig {
dsn_secret: Some("postgresql://u:p@h/db".to_string()),
..Default::default()
});
(only_explicit, with_defaults, with_dsn)
});
assert!(
only_explicit.is_empty(),
"explicit fields must suppress fallback report, got {only_explicit:?}"
);
assert!(with_defaults.contains(&"PGHOST"));
assert!(with_defaults.contains(&"PGPASSWORD"));
assert!(
with_dsn.is_empty(),
"dsn_secret short-circuits libpq fallback reporting, got {with_dsn:?}"
);
}