use duct::cmd; use insta::assert_snapshot; use rstest::rstest; use serial_test::serial; use std::{collections::HashMap, env::current_dir, fs::read_to_string}; #[rstest] #[serial] fn test_migrations_flow(#[values("postgres", "sqlite")] db_kind: &str) { if db_kind == "postgres" && std::env::var("DATABASE_URL").is_err() { return; } let tree_fs = tree_fs::TreeBuilder::default() .drop(true) .create() .expect("Should create temp folder"); let loco_dev_path = current_dir().unwrap(); let loco_dev_path = loco_dev_path.parent().unwrap(); // 1. install most recent dev cli: cd loco-new; cargo install --path . --force // 2. when running locally set LOCO_DEV_MODE_PATH= // LOCO_DEV_MODE_PATH=../../ cargo run -- new let mut env_map: HashMap = std::env::vars().collect(); env_map.insert( "LOCO_DEV_MODE_PATH".into(), loco_dev_path.to_str().unwrap().to_string(), ); if db_kind == "sqlite" { env_map.remove("DATABASE_URL"); } cmd!( "loco", "new", "-n", "myapp", "--db", db_kind, "--bg", "async", "--assets", "serverside", "-a" ) .full_env(&env_map) .dir(&tree_fs.root) .run() .expect("new"); // build a mega long all-types "title:string ..." pairs for all types from // `column::scalar_from_base_name`'s recognized base names (the successor to // the now-removed `mappings.json`); name of column is name of type // adjusted with unique, or nonnull, etc. arity arguments get manual // treatment. `bool`/`tstz` have no `^` (unique) variant -- see // `column::parse_column`'s rejection of `bool^`/`tstz^`. let base_names = [ "string", "text", "uuid", "bool", "date", "time", "date_time", "tstz", "json", "jsonb", "blob", "money", "decimal", "float", "double", "small_int", "small_unsigned", "unsigned", "int", "big_int", ]; let mut type_names = Vec::new(); for base in base_names { type_names.push(format!("{base}:{base}")); type_names.push(format!("{base}_nonull:{base}!")); // bool/tstz have no unique column type; `json` has no btree operator // class on Postgres (use `jsonb`). See `column::parse_column`'s rejects. if base != "bool" && base != "tstz" && base != "json" { type_names.push(format!("{base}_uniq:{base}^")); } } // push arity arguments manually type_names.push("age:decimal_len:8:24".to_string()); type_names.push("age_nonull:decimal_len!:8:24".to_string()); if db_kind == "postgres" { type_names.push("array_string:array:string".to_string()); type_names.push("array_float:array:float".to_string()); type_names.push("array_int:array:int".to_string()); type_names.push("array_double:array:double".to_string()); type_names.push("array_bool:array:bool".to_string()); } let types_line = type_names.join(" "); let script = [ "loco db reset", "loco g model user name:string", "loco g model user_without_tz name:string --without-tz", &format!("loco g scaffold playlists {types_line}"), &format!("loco g scaffold playlists_without_tz {types_line} --without-tz"), &format!("loco g model movies {types_line} playlist:references user:references?"), "loco g migration AddContentToMovies content:string", "loco g migration CreateActors foobar:string", // TBD this errors under sqlite because they don`t support alter and uniq // &format!("loco g migration AddAllToActors {types_line}"), "loco g migration CreateJoinTableActorsAndMovies minutes:int", "loco g migration CreateJoinTableUser_without_tzAndMovies minutes:int --without-tz", "loco g migration CreateAwards name:string actor:references", "loco g migration RemoveContentFromMovies content:string", "loco g migration AddRatingToMovies rating:int", "loco db migrate", "loco db entities", "loco db schema", ]; for line in script { cmd("cargo", line.split(' ')) .full_env(&env_map) .dir(tree_fs.root.join("myapp")) .run() .unwrap_or_else(|_| panic!("command {line} should run successfully")); } // cargo loco build assert_snapshot!( format!("migrations_flow_{db_kind}"), read_to_string(tree_fs.root.join("myapp").join("schema_dump.json")).unwrap() ); }