use std::env;
use serial_test::serial;
const CLEAR_ALL_DIMENSION_VARS: &[&str] = &[
"DOTENVAGE_ENV",
"EKG_ENV",
"VERCEL_ENV",
"NODE_ENV",
"DOTENVAGE_OS",
"EKG_OS",
"DOTENVAGE_ARCH",
"EKG_ARCH",
"CARGO_CFG_TARGET_ARCH",
"TARGET",
"TARGETARCH",
"TARGETPLATFORM",
"RUNNER_ARCH",
"DOTENVAGE_USER",
"EKG_USER",
"GITHUB_ACTOR",
"GITHUB_TRIGGERING_ACTOR",
"GITHUB_REPOSITORY_OWNER",
"USER",
"USERNAME",
"DOTENVAGE_VARIANT",
"EKG_VARIANT",
"VARIANT",
"GITHUB_EVENT_NAME",
"GITHUB_REF",
"PR_NUMBER",
];
fn clear_env(keys: &[&str]) {
for k in keys {
unsafe { env::remove_var(k) };
}
}
#[test]
#[serial]
fn dynamic_env_discovery_from_base_file() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "NODE_ENV=production\nBASE=1\n").unwrap();
std::fs::write(tmp.path().join(".env.production"), "PROD=1\n").unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
loader.load_from_dir(tmp.path()).unwrap();
assert_eq!(env::var("BASE").unwrap(), "1");
assert_eq!(env::var("PROD").unwrap(), "1");
assert_eq!(env::var("NODE_ENV").unwrap(), "production");
}
#[test]
#[serial]
fn dynamic_variant_discovery_triggers_reload() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "VARIANT=docker\nBASE=1\n").unwrap();
std::fs::write(tmp.path().join(".env.docker"), "DOCKER_CONFIG=enabled\n").unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
loader.load_from_dir(tmp.path()).unwrap();
assert_eq!(env::var("BASE").unwrap(), "1");
assert_eq!(env::var("DOCKER_CONFIG").unwrap(), "enabled");
assert_eq!(env::var("VARIANT").unwrap(), "docker");
}
#[test]
#[serial]
fn dynamic_arch_discovery_from_env_file() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "DOTENVAGE_ARCH=arm64\nBASE=1\n").unwrap();
std::fs::write(tmp.path().join(".env.local"), "LOCAL=1\n").unwrap();
std::fs::write(
tmp.path().join(".env.local.arm64"),
"ARM64_CONFIG=enabled\n",
)
.unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
loader.load_from_dir(tmp.path()).unwrap();
assert_eq!(env::var("BASE").unwrap(), "1");
assert_eq!(env::var("LOCAL").unwrap(), "1");
assert_eq!(env::var("ARM64_CONFIG").unwrap(), "enabled");
}
#[test]
#[serial]
fn no_double_loading() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "NODE_ENV=prod\nCOUNTER=0\n").unwrap();
std::fs::write(tmp.path().join(".env.prod"), "COUNTER=1\nPROD_LOADED=yes\n").unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
loader.load_from_dir(tmp.path()).unwrap();
assert_eq!(env::var("COUNTER").unwrap(), "1");
assert_eq!(env::var("PROD_LOADED").unwrap(), "yes");
}
#[test]
#[serial]
fn chained_discovery() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "NODE_ENV=staging\nSTEP=1\n").unwrap();
std::fs::write(tmp.path().join(".env.staging"), "VARIANT=canary\nSTEP=2\n").unwrap();
std::fs::write(
tmp.path().join(".env.staging.canary"),
"STEP=3\nCANARY=yes\n",
)
.unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
loader.load_from_dir(tmp.path()).unwrap();
assert_eq!(env::var("NODE_ENV").unwrap(), "staging");
assert_eq!(env::var("VARIANT").unwrap(), "canary");
assert_eq!(env::var("STEP").unwrap(), "3"); assert_eq!(env::var("CANARY").unwrap(), "yes");
}
#[test]
#[serial]
fn encrypted_dimension_config_ignored_in_discovery() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "VARIANT=docker\nBASE=1\n").unwrap();
std::fs::write(tmp.path().join(".env.local"), "LOCAL=1\n").unwrap();
std::fs::write(tmp.path().join(".env.docker"), "DOCKER=1\n").unwrap();
std::fs::write(tmp.path().join(".env.local.docker"), "LOCAL_DOCKER=1\n").unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
loader.load_from_dir(tmp.path()).unwrap();
assert_eq!(env::var("BASE").unwrap(), "1");
assert_eq!(env::var("LOCAL").unwrap(), "1");
assert_eq!(env::var("DOCKER").unwrap(), "1");
assert_eq!(env::var("LOCAL_DOCKER").unwrap(), "1");
}
#[test]
#[serial]
fn env_var_determines_file_selection() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
unsafe {
env::set_var("NODE_ENV", "production");
}
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "NODE_ENV=staging\nBASE=1\n").unwrap();
std::fs::write(tmp.path().join(".env.production"), "PROD=1\n").unwrap();
std::fs::write(tmp.path().join(".env.staging"), "STAGING=1\n").unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
loader.load_from_dir(tmp.path()).unwrap();
assert_eq!(env::var("BASE").unwrap(), "1");
assert_eq!(env::var("PROD").unwrap(), "1");
assert!(
env::var("STAGING").is_err(),
"Env var should determine file selection, staging should not load"
);
}
#[test]
#[serial]
fn collect_vars_discovers_variant_from_intermediate_file() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "BASE=from_env\n").unwrap();
std::fs::write(
tmp.path().join(".env.local"),
"LOCAL=from_local\nEKG_VARIANT=oxigraph\n",
)
.unwrap();
std::fs::write(
tmp.path().join(".env.local.oxigraph"),
"OXIGRAPH_CONFIG=from_oxigraph\n",
)
.unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
let (vars, paths) = loader.collect_all_vars_from_dir(tmp.path()).unwrap();
assert_eq!(vars.get("BASE").unwrap(), "from_env");
assert_eq!(vars.get("LOCAL").unwrap(), "from_local");
assert_eq!(vars.get("EKG_VARIANT").unwrap(), "oxigraph");
assert_eq!(
vars.get("OXIGRAPH_CONFIG").unwrap(),
"from_oxigraph",
"VARIANT discovered in .env.local should trigger loading .env.local.oxigraph"
);
assert_eq!(paths.len(), 3, "Should load exactly 3 files");
assert!(paths[0].ends_with(".env"));
assert!(paths[1].ends_with(".env.local"));
assert!(paths[2].ends_with(".env.local.oxigraph"));
}
#[test]
#[serial]
fn collect_vars_chained_discovery() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join(".env"), "NODE_ENV=staging\nSTEP=1\n").unwrap();
std::fs::write(tmp.path().join(".env.staging"), "VARIANT=canary\nSTEP=2\n").unwrap();
std::fs::write(
tmp.path().join(".env.staging.canary"),
"STEP=3\nCANARY_FLAG=yes\n",
)
.unwrap();
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
let (vars, paths) = loader.collect_all_vars_from_dir(tmp.path()).unwrap();
assert_eq!(vars.get("NODE_ENV").unwrap(), "staging");
assert_eq!(vars.get("VARIANT").unwrap(), "canary");
assert_eq!(vars.get("STEP").unwrap(), "3"); assert_eq!(
vars.get("CANARY_FLAG").unwrap(),
"yes",
"Chained discovery should load .env.staging.canary"
);
assert_eq!(paths.len(), 3, "Should load exactly 3 files");
}
#[test]
#[serial]
fn collect_vars_does_not_modify_environment() {
clear_env(CLEAR_ALL_DIMENSION_VARS);
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join(".env"),
"NODE_ENV=production\nTEST_VAR=test_value\n",
)
.unwrap();
std::fs::write(tmp.path().join(".env.production"), "PROD_VAR=prod_value\n").unwrap();
unsafe {
env::set_var("UNRELATED_VAR", "original_value");
}
let loader = dotenvage::EnvLoader::with_manager(dotenvage::SecretManager::generate().unwrap());
let (vars, _paths) = loader.collect_all_vars_from_dir(tmp.path()).unwrap();
assert_eq!(vars.get("PROD_VAR").unwrap(), "prod_value");
assert_eq!(vars.get("TEST_VAR").unwrap(), "test_value");
assert_eq!(
env::var("UNRELATED_VAR").unwrap(),
"original_value",
"collect_all_vars_from_dir should not touch unrelated vars"
);
assert!(
env::var("TEST_VAR").is_err(),
"collect_all_vars_from_dir should not set vars in process environment"
);
assert!(
env::var("PROD_VAR").is_err(),
"collect_all_vars_from_dir should not set vars in process environment"
);
assert!(
env::var("NODE_ENV").is_err(),
"collect_all_vars_from_dir should restore dimension vars to original state"
);
}