use pgevolve::executor::ApplyError;
use pgevolve::executor::env_interp;
use pgevolve::executor::preflight::check_env_vars_resolvable;
use pgevolve_core::identifier::{Identifier, QualifiedName};
use pgevolve_core::ir::catalog::Catalog;
use pgevolve_core::plan::raw_step::{RawStep, StepKind, TransactionConstraint};
use pgevolve_core::plan::{Plan, PlanId, PlanMetadata, TransactionGroup};
use time::OffsetDateTime;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn qn(schema: &str, name: &str) -> QualifiedName {
QualifiedName::new(id(schema), id(name))
}
fn plan_with_sql(sql: &str) -> Plan {
let step = RawStep {
step_no: 1,
kind: StepKind::CreatePublication,
destructive: false,
destructive_reason: None,
intent_id: None,
targets: vec![qn("public", "my_sub")],
sql: sql.to_string(),
transactional: TransactionConstraint::OutsideTransaction,
};
let group = TransactionGroup {
id: 1,
transactional: false,
steps: vec![step],
};
Plan {
id: PlanId([0u8; 32]),
groups: vec![group],
intents: vec![],
lint_waivers: vec![],
step_overrides: vec![],
metadata: PlanMetadata {
pgevolve_version: "0.0.0-test".into(),
planner_ruleset_version: 1,
source_rev: None,
target_identity: "test".into(),
target_snapshot: Catalog::empty(),
created_at: OffsetDateTime::now_utc(),
lint_at_plan_findings: vec![],
},
advisory_findings: vec![],
}
}
const ABSENT_VAR: &str = "PGEVOLVE_STAGE4_INTEGRATION_SENTINEL_VAR_DEFINITELY_NOT_SET_XQZW9B";
#[test]
fn missing_env_var_fails_preflight() {
assert!(
std::env::var(ABSENT_VAR).is_err(),
"sentinel env var {ABSENT_VAR} is unexpectedly set; choose a different name"
);
let sql = format!(
"CREATE SUBSCRIPTION my_sub CONNECTION 'host=db password=${{{ABSENT_VAR}}}' PUBLICATION pub;"
);
let plan = plan_with_sql(&sql);
let result = check_env_vars_resolvable(&plan);
match result {
Err(ApplyError::MissingEnvVar(name, step_no)) => {
assert_eq!(name, ABSENT_VAR, "wrong var name in error");
assert_eq!(step_no, 1, "wrong step number in error");
}
other => panic!("expected MissingEnvVar, got: {other:?}"),
}
}
#[test]
fn no_refs_always_passes() {
let plan = plan_with_sql("CREATE SUBSCRIPTION my_sub CONNECTION 'host=db' PUBLICATION pub;");
assert!(
check_env_vars_resolvable(&plan).is_ok(),
"expected Ok for SQL with no env-var references"
);
}
#[test]
fn references_detects_subscription_connection_vars() {
let sql = "CREATE SUBSCRIPTION my_sub CONNECTION \
'host=db password=${DB_PASSWORD} user=${DB_USER}' PUBLICATION pub;";
let refs = env_interp::references(sql);
assert_eq!(refs, vec!["DB_PASSWORD", "DB_USER"]);
}
#[test]
fn references_ignores_invalid_syntax() {
let sql = "SELECT $1, ${lowercase}, ${} FROM t;";
let refs = env_interp::references(sql);
assert!(
refs.is_empty(),
"expected no references from invalid syntax, got: {refs:?}"
);
}