#![cfg(feature = "sqlite")]
use std::path::Path;
use std::process::Command;
use diesel::prelude::*;
use diesel::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(),
)
}
#[derive(QueryableByName)]
struct TableName {
#[diesel(sql_type = diesel::sql_types::Text)]
#[allow(dead_code)]
name: String,
}
fn posts_table_exists(db_path: &Path) -> bool {
let mut conn = SqliteConnection::establish(db_path.to_str().expect("utf-8 path"))
.expect("open sqlite file");
let rows: Vec<TableName> =
sql_query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'posts'")
.load(&mut conn)
.expect("query sqlite_master");
!rows.is_empty()
}
#[test]
fn migrate_up_then_down_against_a_sqlite_file() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
let migration = root.join("migrations/20990101000000_create_posts");
std::fs::create_dir_all(&migration).expect("mkdir migrations");
std::fs::write(
migration.join("up.sql"),
"CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT NOT NULL);\n",
)
.expect("write up.sql");
std::fs::write(migration.join("down.sql"), "DROP TABLE posts;\n").expect("write down.sql");
let db_path = root.join("app.db");
let url = format!("sqlite://{}", db_path.display());
let envs = [("AUTUMN_DATABASE__URL", url.as_str())];
let (_out, err, code) = run_autumn(root, &["migrate"], &envs);
assert_eq!(code, Some(0), "migrate up failed (exit={code:?})\n{err}");
assert!(
err.contains("Migrations applied successfully"),
"up reports success: {err}"
);
assert!(db_path.is_file(), "sqlite db file created");
assert!(
posts_table_exists(&db_path),
"posts table exists after migrate up"
);
let (_out2, err2, code2) = run_autumn(root, &["migrate"], &envs);
assert_eq!(code2, Some(0), "second migrate up failed: {err2}");
assert!(
err2.contains("already up to date"),
"second up is a no-op: {err2}"
);
let (_out3, err3, code3) = run_autumn(root, &["migrate", "down"], &envs);
assert_eq!(
code3,
Some(0),
"migrate down failed (exit={code3:?})\n{err3}"
);
assert!(
err3.contains("rolled back"),
"down reports rollback: {err3}"
);
assert!(
!posts_table_exists(&db_path),
"posts table is gone after migrate down"
);
let (_out4, err4, code4) = run_autumn(root, &["migrate"], &envs);
assert_eq!(code4, Some(0), "re-apply after down failed: {err4}");
assert!(
err4.contains("Migrations applied successfully"),
"re-apply after down: {err4}"
);
assert!(
posts_table_exists(&db_path),
"posts table exists again after re-apply"
);
}
#[test]
fn migrate_status_under_sqlite_is_a_clear_provider_error() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
let migration = root.join("migrations/20990101000000_create_posts");
std::fs::create_dir_all(&migration).expect("mkdir migrations");
std::fs::write(
migration.join("up.sql"),
"CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT NOT NULL);\n",
)
.expect("write up.sql");
std::fs::write(migration.join("down.sql"), "DROP TABLE posts;\n").expect("write down.sql");
let db_path = root.join("app.db");
let url = format!("sqlite://{}", db_path.display());
let envs = [("AUTUMN_DATABASE__URL", url.as_str())];
let (_out, err, code) = run_autumn(root, &["migrate", "status"], &envs);
assert_eq!(code, Some(1), "status under sqlite exits non-zero: {err}");
assert!(
err.contains("not supported under the SQLite backend"),
"status under sqlite gives a provider-appropriate message: {err}"
);
assert!(
!err.contains("diesel migration list") && !err.contains("os error 2"),
"status under sqlite must not invoke the diesel subprocess: {err}"
);
}
fn write_posts_migration(root: &Path) {
let migration = root.join("migrations/20990101000000_create_posts");
std::fs::create_dir_all(&migration).expect("mkdir migrations");
std::fs::write(
migration.join("up.sql"),
"CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT NOT NULL);\n",
)
.expect("write up.sql");
std::fs::write(migration.join("down.sql"), "DROP TABLE posts;\n").expect("write down.sql");
}
#[test]
fn migrate_sqlite_control_with_shards_is_rejected_before_applying() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
write_posts_migration(root);
let control_db = root.join("app.db");
let shard_db = root.join("shard0.db");
let control_url = format!("sqlite://{}", control_db.display());
let shard_url = format!("sqlite://{}", shard_db.display());
let envs = [
("AUTUMN_DATABASE__URL", control_url.as_str()),
("AUTUMN_DATABASE__SHARDS__0__NAME", "shard0"),
(
"AUTUMN_DATABASE__SHARDS__0__PRIMARY_URL",
shard_url.as_str(),
),
];
let (_out, err, code) = run_autumn(root, &["migrate"], &envs);
assert_eq!(
code,
Some(1),
"sqlite control + shards exits non-zero: {err}"
);
assert!(
err.contains("do not support sharding") && err.contains("#1614"),
"clear provider-appropriate rejection: {err}"
);
assert!(
!control_db.exists(),
"control sqlite file must not be created/migrated"
);
assert!(
!shard_db.exists(),
"shard sqlite file must not be created/migrated"
);
}
#[test]
fn migrate_postgres_control_with_sqlite_shard_is_rejected_before_applying() {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
write_posts_migration(root);
let shard_db = root.join("shard0.db");
let shard_url = format!("sqlite://{}", shard_db.display());
let envs = [
(
"AUTUMN_DATABASE__URL",
"postgres://user@127.0.0.1:1/does_not_exist",
),
("AUTUMN_DATABASE__SHARDS__0__NAME", "shard0"),
(
"AUTUMN_DATABASE__SHARDS__0__PRIMARY_URL",
shard_url.as_str(),
),
];
let (_out, err, code) = run_autumn(root, &["migrate"], &envs);
assert_eq!(
code,
Some(1),
"postgres control + sqlite shard exits non-zero: {err}"
);
assert!(
err.contains("do not support sharding") && err.contains("shard") && err.contains("#1614"),
"clear provider-appropriate rejection naming the sqlite shard: {err}"
);
assert!(
!shard_db.exists(),
"sqlite shard file must not be created/migrated"
);
}