use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
const MODE: &str = "SECRETSPEC_TYPED_SCOPE_TEST_MODE";
const OUT: &str = "SECRETSPEC_TYPED_SCOPE_TEST_OUT";
const MANIFEST: &str = r#"
[project]
name = "typed-scope-env"
revision = "1.0"
require_reason = false
[profiles.default]
DATABASE_URL = { description = "DB", required = true }
API_KEY = { description = "API key", required = true }
QUEUE_TOKEN = { description = "Queue token", required = true }
[scopes.api]
secrets = ["DATABASE_URL", "API_KEY"]
"#;
const ENV_FILE: &str = "DATABASE_URL=db\nAPI_KEY=key\nQUEUE_TOKEN=tok\n";
#[test]
fn typed_scope_env_child() {
let Ok(mode) = std::env::var(MODE) else {
return;
};
let out = std::env::var(OUT).expect("child needs an output path");
let mut spec = secretspec::Secrets::load().expect("load secretspec.toml from cwd");
if mode == "typed" {
spec.set_ignore_ambient_scope(true);
}
let response = spec.resolve().expect("resolve secrets");
let mut names: Vec<String> = response.secrets.keys().cloned().collect();
names.sort();
fs::write(out, names.join(",")).expect("write resolved names");
}
fn resolved_names(exe: &Path, project: &Path, mode: &str) -> String {
let out = project.join(format!("out-{mode}"));
let status = Command::new(exe)
.args(["typed_scope_env_child", "--exact", "--nocapture"])
.current_dir(project)
.env(MODE, mode)
.env(OUT, &out)
.env(
"SECRETSPEC_PROVIDER",
format!("dotenv://{}", project.join(".env").display()),
)
.env("SECRETSPEC_SCOPE", "api")
.env_remove("SECRETSPEC_PROFILE")
.env("HOME", project)
.env("XDG_CONFIG_HOME", project)
.status()
.expect("spawn child test binary");
assert!(status.success(), "child ({mode}) exited with failure");
fs::read_to_string(&out).expect("child wrote resolved names")
}
#[test]
fn typed_load_ignores_ambient_scope_while_untyped_honors_it() {
if std::env::var(MODE).is_ok() {
return;
}
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("secretspec.toml"), MANIFEST).unwrap();
fs::write(project.join(".env"), ENV_FILE).unwrap();
let exe = std::env::current_exe().expect("current test binary");
let untyped = resolved_names(&exe, project, "untyped");
let typed = resolved_names(&exe, project, "typed");
assert_eq!(
untyped, "API_KEY,DATABASE_URL",
"untyped resolution honors SECRETSPEC_SCOPE"
);
assert_eq!(
typed, "API_KEY,DATABASE_URL,QUEUE_TOKEN",
"a typed loader ignores an ambient SECRETSPEC_SCOPE and sees the full profile"
);
}