use std::path::Path;
use std::process::Command;
use diesel::{Connection as _, PgConnection, QueryableByName, RunQueryDsl as _, sql_query};
const fn autumn_bin() -> &'static str {
env!("CARGO_BIN_EXE_autumn")
}
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)
.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)
}
#[derive(QueryableByName)]
struct CountRow {
#[diesel(sql_type = diesel::sql_types::BigInt)]
count: i64,
}
fn count(conn: &mut PgConnection, sql: &str) -> i64 {
sql_query(sql)
.get_result::<CountRow>(conn)
.unwrap_or_else(|e| panic!("query failed: {sql}\n{e}"))
.count
}
#[tokio::test]
#[ignore = "requires Docker (testcontainers); run with -- --ignored"]
async fn references_field_creates_fk_and_index_enforces_integrity_and_reverts_cleanly() {
use testcontainers::runners::AsyncRunner as _;
use testcontainers_modules::postgres::Postgres;
let container = Postgres::default()
.start()
.await
.expect("failed to start Postgres testcontainer — is Docker running?");
let host = container.get_host().await.unwrap();
let port = container.get_host_port_ipv4(5432).await.unwrap();
let db_url = format!("postgres://postgres:postgres@{host}:{port}/postgres");
let envs = [("AUTUMN_DATABASE__URL", db_url.as_str())];
let tmp = tempfile::tempdir().unwrap();
let project = tmp.path();
std::fs::write(
project.join("Cargo.toml"),
"[package]\nname = \"refs_app\"\n",
)
.unwrap();
run_autumn_ok(project, &["generate", "model", "Post", "title:String"], &[]);
std::thread::sleep(std::time::Duration::from_millis(1100));
run_autumn_ok(
project,
&[
"generate",
"model",
"Comment",
"body:Text",
"post:references",
],
&[],
);
run_autumn_ok(project, &["migrate"], &envs);
let mut conn = PgConnection::establish(&db_url).expect("connect to postgres");
let fk_count = count(
&mut conn,
"SELECT COUNT(*)::bigint AS count FROM information_schema.table_constraints \
WHERE table_name = 'comments' AND constraint_type = 'FOREIGN KEY'",
);
assert_eq!(
fk_count, 1,
"expected exactly one FK constraint on comments"
);
let index_count = count(
&mut conn,
"SELECT COUNT(*)::bigint AS count FROM pg_indexes \
WHERE tablename = 'comments' AND indexname = 'idx_comments_post_id'",
);
assert_eq!(index_count, 1, "expected idx_comments_post_id to exist");
let bad_insert = sql_query("INSERT INTO comments (body, post_id) VALUES ('orphan', 999999)")
.execute(&mut conn);
assert!(
bad_insert.is_err(),
"FK constraint should reject a comment referencing a non-existent post"
);
sql_query("INSERT INTO posts (title) VALUES ('hello')")
.execute(&mut conn)
.expect("insert post");
sql_query("INSERT INTO comments (body, post_id) VALUES ('hi', 1)")
.execute(&mut conn)
.expect("insert comment referencing a real post");
run_autumn_ok(project, &["migrate", "down", "--steps", "2"], &envs);
let remaining_tables = count(
&mut conn,
"SELECT COUNT(*)::bigint AS count FROM information_schema.tables \
WHERE table_name IN ('posts', 'comments')",
);
assert_eq!(
remaining_tables, 0,
"both tables (and their FK/index/columns) must be gone after migrate down"
);
}