use std::path::Path;
use std::process::Command;
const fn autumn_bin() -> &'static str {
env!("CARGO_BIN_EXE_autumn")
}
const SECRET_PW: &str = "s3cr3t_pw_do_not_leak";
fn run_autumn(dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> (String, String, Option<i32>) {
let output = Command::new(autumn_bin())
.args(args)
.current_dir(dir)
.env_remove("DATABASE_URL")
.env_remove("AUTUMN_DATABASE__URL")
.env_remove("AUTUMN_DATABASE__PRIMARY_URL")
.envs(envs.iter().copied())
.output()
.expect("failed to run autumn");
(
String::from_utf8_lossy(&output.stdout).into_owned(),
String::from_utf8_lossy(&output.stderr).into_owned(),
output.status.code(),
)
}
fn run_autumn_ok(dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> (String, String) {
let (stdout, stderr, code) = run_autumn(dir, args, envs);
assert_eq!(
code,
Some(0),
"autumn {args:?} failed (exit={code:?})\nstdout: {stdout}\nstderr: {stderr}",
);
(stdout, stderr)
}
fn run_autumn_fail(dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> (String, String) {
let (stdout, stderr, code) = run_autumn(dir, args, envs);
assert_ne!(
code,
Some(0),
"autumn {args:?} should have failed but exited 0\nstdout: {stdout}\nstderr: {stderr}",
);
(stdout, stderr)
}
fn write_migration(migrations_dir: &Path, name: &str, up_sql: &str, down_sql: &str) {
let dir = migrations_dir.join(name);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("up.sql"), up_sql).unwrap();
std::fs::write(dir.join("down.sql"), down_sql).unwrap();
}
fn write_cargo_fixture(dir: &Path, test_passes: bool) {
std::fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"autumn_test_fixture\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\n[lib]\npath = \"src/lib.rs\"\n",
)
.unwrap();
std::fs::create_dir_all(dir.join("src")).unwrap();
let body = if test_passes {
"#[test]\nfn fixture_passes() { assert_eq!(2 + 2, 4); }\n"
} else {
"#[test]\nfn fixture_fails() { panic!(\"deliberate failure\"); }\n"
};
std::fs::write(dir.join("src/lib.rs"), body).unwrap();
let migrations = dir.join("migrations");
std::fs::create_dir_all(&migrations).unwrap();
write_migration(
&migrations,
"20260101000000_create_widgets",
"CREATE TABLE widgets (id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL);",
"DROP TABLE widgets;",
);
}
async fn start_postgres() -> (
testcontainers::ContainerAsync<testcontainers_modules::postgres::Postgres>,
String,
u16,
) {
use testcontainers::runners::AsyncRunner as _;
use testcontainers_modules::postgres::Postgres;
let container = Postgres::default()
.with_password(SECRET_PW)
.start()
.await
.expect("failed to start Postgres testcontainer — is Docker running?");
let host = container.get_host().await.unwrap().to_string();
let port = container.get_host_port_ipv4(5432).await.unwrap();
(container, host, port)
}
#[test]
fn test_help_documents_env_vars_and_lifecycle() {
let tmp = tempfile::tempdir().unwrap();
let (stdout, _stderr) = run_autumn_ok(tmp.path(), &["test", "--help"], &[]);
assert!(stdout.contains("AUTUMN_ENV=test"), "help: {stdout}");
assert!(stdout.contains("DATABASE_URL"), "help: {stdout}");
assert!(stdout.contains("create"), "help: {stdout}");
assert!(stdout.contains("migrate"), "help: {stdout}");
assert!(stdout.contains("cargo test"), "help: {stdout}");
assert!(stdout.contains("--reset"), "help: {stdout}");
}
#[test]
fn test_refuses_url_that_cannot_be_made_test_safe() {
let tmp = tempfile::tempdir().unwrap();
let envs = [("AUTUMN_DATABASE__PRIMARY_URL", "postgres://localhost/")];
let (stdout, stderr) = run_autumn_fail(tmp.path(), &["test"], &envs);
assert!(
stderr.contains("does not name a database"),
"stdout: {stdout}\nstderr: {stderr}",
);
}
#[test]
fn test_refuses_when_no_url_configured() {
let tmp = tempfile::tempdir().unwrap();
let (_stdout, stderr) = run_autumn_fail(tmp.path(), &["test"], &[]);
assert!(
stderr.contains("No test database URL found"),
"stderr: {stderr}",
);
}
#[tokio::test]
#[ignore = "requires Docker (testcontainers); run with -- --ignored"]
async fn test_derives_test_suffix_creates_migrates_and_runs() {
let (_container, host, port) = start_postgres().await;
let url = format!("postgres://postgres:{SECRET_PW}@{host}:{port}/myapp");
let envs = [("AUTUMN_DATABASE__PRIMARY_URL", url.as_str())];
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_cargo_fixture(dir, true);
let (stdout, stderr) = run_autumn_ok(dir, &["test"], &envs);
let combined = format!("{stdout}{stderr}");
assert!(combined.contains("myapp_test"), "output: {combined}");
assert!(combined.contains("Created database"), "output: {combined}");
assert!(
combined.contains("Migrations applied") || combined.contains("up to date"),
"output: {combined}",
);
assert!(
!combined.contains(SECRET_PW),
"credentials leaked!\n{combined}",
);
}
#[tokio::test]
#[ignore = "requires Docker (testcontainers); run with -- --ignored"]
async fn test_create_if_missing_is_idempotent_and_preserves_data() {
let (_container, host, port) = start_postgres().await;
let url = format!("postgres://postgres:{SECRET_PW}@{host}:{port}/keepdata_test");
let envs = [("AUTUMN_DATABASE__PRIMARY_URL", url.as_str())];
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_cargo_fixture(dir, true);
run_autumn_ok(dir, &["test"], &envs);
let sentinel = "sentinel-preserve-me";
crate::common::execute_params(&url, "INSERT INTO widgets (name) VALUES ($1)", &[&sentinel])
.await;
let (stdout, stderr) = run_autumn_ok(dir, &["test"], &envs);
let combined = format!("{stdout}{stderr}");
assert!(combined.contains("already exists"), "output: {combined}");
let survived = crate::common::query_one_text(
&url,
"SELECT name FROM widgets WHERE name = $1",
&[&sentinel],
)
.await;
assert_eq!(
survived.as_deref(),
Some(sentinel),
"sentinel row was lost — the database was not preserved",
);
}
#[tokio::test]
#[ignore = "requires Docker (testcontainers); run with -- --ignored"]
async fn test_reset_drops_and_recreates() {
let (_container, host, port) = start_postgres().await;
let url = format!("postgres://postgres:{SECRET_PW}@{host}:{port}/resetme_test");
let envs = [("AUTUMN_DATABASE__PRIMARY_URL", url.as_str())];
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_cargo_fixture(dir, true);
run_autumn_ok(dir, &["test"], &envs);
let (stdout, stderr) = run_autumn_ok(dir, &["test", "--reset"], &envs);
let combined = format!("{stdout}{stderr}");
assert!(combined.contains("Dropped database"), "output: {combined}");
assert!(combined.contains("Created database"), "output: {combined}");
}
#[tokio::test]
#[ignore = "requires Docker (testcontainers); run with -- --ignored"]
async fn test_failing_suite_fails_the_command() {
let (_container, host, port) = start_postgres().await;
let url = format!("postgres://postgres:{SECRET_PW}@{host}:{port}/exitcode_test");
let envs = [("AUTUMN_DATABASE__PRIMARY_URL", url.as_str())];
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_cargo_fixture(dir, false);
let (stdout, stderr) = run_autumn_fail(dir, &["test"], &envs);
let combined = format!("{stdout}{stderr}");
assert!(combined.contains("Created database"), "output: {combined}");
}