use std::io::Write;
use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::NamedTempFile;
fn write_temp_manifest(content: &str) -> NamedTempFile {
let mut file = tempfile::Builder::new()
.suffix(".yml")
.tempfile()
.expect("temp file");
file.write_all(content.as_bytes()).expect("write manifest");
file
}
fn write_temp_env(content: &str) -> NamedTempFile {
let mut file = tempfile::Builder::new()
.suffix(".env")
.tempfile()
.expect("temp env file");
file.write_all(content.as_bytes()).expect("write env file");
file
}
const MANIFEST_NO_REFS: &str = r#"
project:
name: app
resources:
db:
postgres:
version: "16"
"#;
const MANIFEST_REQUIRED_VAR: &str = r#"
project:
name: app
resources:
app:
container:
image: myapp:latest
env:
API_TOKEN: "${env.API_TOKEN}"
"#;
const MANIFEST_OPTIONAL_VAR: &str = r#"
project:
name: app
resources:
app:
container:
image: myapp:latest
env:
LOG_LEVEL: "${env.LOG_LEVEL:-info}"
"#;
const MANIFEST_MIXED_VARS: &str = r#"
project:
name: app
resources:
app:
container:
image: myapp:latest
env:
API_TOKEN: "${env.API_TOKEN}"
LOG_LEVEL: "${env.LOG_LEVEL:-info}"
"#;
const MANIFEST_IMAGE_REF: &str = r#"
project:
name: app
resources:
app:
container:
image: "myapp:${env.APP_VERSION}"
"#;
#[test]
fn secrets_check_no_refs_succeeds() {
let manifest = write_temp_manifest(MANIFEST_NO_REFS);
let empty = write_temp_env("");
Command::cargo_bin("lightshuttle")
.unwrap()
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(empty.path())
.assert()
.success()
.stdout(predicate::str::contains("no `${env.*}` references found"));
}
#[test]
fn secrets_check_ignores_references_in_image_tag() {
let manifest = write_temp_manifest(MANIFEST_IMAGE_REF);
let empty = write_temp_env("");
Command::cargo_bin("lightshuttle")
.unwrap()
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(empty.path())
.assert()
.success()
.stdout(predicate::str::contains("no `${env.*}` references found"));
}
#[test]
fn secrets_check_required_var_missing_fails() {
let manifest = write_temp_manifest(MANIFEST_REQUIRED_VAR);
let empty = write_temp_env("");
Command::cargo_bin("lightshuttle")
.unwrap()
.env_remove("API_TOKEN")
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(empty.path())
.assert()
.failure()
.code(1)
.stdout(predicate::str::contains("missing"));
}
#[test]
fn secrets_check_required_var_set_via_env_file_succeeds() {
let manifest = write_temp_manifest(MANIFEST_REQUIRED_VAR);
let env_file = write_temp_env("API_TOKEN=s3cr3t\n");
Command::cargo_bin("lightshuttle")
.unwrap()
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(env_file.path())
.assert()
.success()
.stdout(predicate::str::contains("set (.env)"))
.stdout(predicate::str::contains("all required secrets are set"));
}
#[test]
fn secrets_check_resolves_required_var_from_process_env() {
let manifest = write_temp_manifest(MANIFEST_REQUIRED_VAR);
let empty = write_temp_env("");
Command::cargo_bin("lightshuttle")
.unwrap()
.env("API_TOKEN", "from-shell")
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(empty.path())
.assert()
.success()
.stdout(predicate::str::contains("set (env)"));
}
#[test]
fn secrets_check_empty_value_is_missing() {
let manifest = write_temp_manifest(MANIFEST_REQUIRED_VAR);
let env_file = write_temp_env("API_TOKEN=\n");
Command::cargo_bin("lightshuttle")
.unwrap()
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(env_file.path())
.assert()
.failure()
.code(1)
.stdout(predicate::str::contains("missing"));
}
#[test]
fn secrets_check_optional_var_shows_default_without_env() {
let manifest = write_temp_manifest(MANIFEST_OPTIONAL_VAR);
let empty = write_temp_env("");
Command::cargo_bin("lightshuttle")
.unwrap()
.env_remove("LOG_LEVEL")
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(empty.path())
.assert()
.success()
.stdout(predicate::str::contains("default"));
}
#[test]
fn secrets_check_optional_var_set_shows_set() {
let manifest = write_temp_manifest(MANIFEST_OPTIONAL_VAR);
let env_file = write_temp_env("LOG_LEVEL=debug\n");
Command::cargo_bin("lightshuttle")
.unwrap()
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(env_file.path())
.assert()
.success()
.stdout(predicate::str::contains("set (.env)"));
}
#[test]
fn secrets_check_mixed_fails_when_required_var_absent() {
let manifest = write_temp_manifest(MANIFEST_MIXED_VARS);
let env_file = write_temp_env("LOG_LEVEL=debug\n");
Command::cargo_bin("lightshuttle")
.unwrap()
.env_remove("API_TOKEN")
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(env_file.path())
.assert()
.failure()
.code(1)
.stdout(predicate::str::contains("missing"))
.stdout(predicate::str::contains("set (.env)"));
}
#[test]
fn secrets_check_mixed_succeeds_when_all_vars_provided() {
let manifest = write_temp_manifest(MANIFEST_MIXED_VARS);
let env_file = write_temp_env("API_TOKEN=s3cr3t\nLOG_LEVEL=debug\n");
Command::cargo_bin("lightshuttle")
.unwrap()
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file"])
.arg(env_file.path())
.assert()
.success()
.stdout(predicate::str::contains("all required secrets are set"));
}
#[test]
fn secrets_check_explicit_env_file_not_found_fails() {
let manifest = write_temp_manifest(MANIFEST_REQUIRED_VAR);
Command::cargo_bin("lightshuttle")
.unwrap()
.arg("--file")
.arg(manifest.path())
.args(["secrets", "check", "--env-file", "/nonexistent/path.env"])
.assert()
.failure()
.code(1)
.stderr(predicate::str::contains("failed to load --env-file"));
}