use super::*;
#[test]
fn the_package_name_is_found() {
let manifest = "[package]\nname = \"demo-app\"\nversion = \"0.1.0\"\n\n[dependencies]\n";
assert_eq!(package_name(manifest), Some("demo-app"));
}
#[test]
fn a_name_in_another_table_is_not_the_package() {
let manifest = "[dependencies]\nname = \"wrong\"\n\n[package]\nname = \"right\"\n";
assert_eq!(package_name(manifest), Some("right"));
}
#[test]
fn a_manifest_without_a_name_reads_as_none() {
assert_eq!(package_name("[package]\nversion = \"0.1.0\"\n"), None);
}
#[test]
fn a_local_checkout_is_recovered() {
let manifest = "[dependencies]\nrahti = { path = \"C:/src/rahti/crates/rahti\" }\n";
assert_eq!(local_checkout(manifest).as_deref(), Some("C:/src/rahti"));
}
#[test]
fn a_published_dependency_is_not_a_checkout() {
assert_eq!(local_checkout("[dependencies]\nrahti = \"0.0.7\"\n"), None);
}
#[test]
fn the_manifest_is_never_rewritten() {
assert!(NEVER_REWRITTEN.contains(&"Cargo.toml"));
}
#[test]
fn the_framework_files_are_rewritable() {
for path in [
"src/app/layout.rs",
"build.rs",
"public/js/pp-reactive-v2.min.js",
] {
assert!(
!NEVER_REWRITTEN.contains(&path),
"{path} must be upgradable"
);
}
}
fn config_dir(ledger: &Ledger) -> PathBuf {
static NEXT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
let root = std::env::temp_dir().join(format!(
"cargo-rahti-rewrite-{}-{}",
std::process::id(),
NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).expect("a temporary directory");
crate::new::write_config(&root, true, None, false, ledger).expect("a written config");
root
}
#[test]
fn an_edited_config_survives_the_rewrite() {
let mut ledger = Ledger::new();
ledger.insert("build.rs".to_string(), sha256::hex(b"old"));
let root = config_dir(&ledger);
let path = root.join("rahti.config.json");
let edited = fs::read_to_string(&path)
.expect("the written config")
.replace("\"port\": 3000", "\"port\": 8080")
.replace(
"\"download\": true",
"\"download\": true,\n \"minify\": true",
)
.replace(VERSION, "0.0.1");
fs::write(&path, &edited).expect("an edited config");
ledger.insert("build.rs".to_string(), sha256::hex(b"new"));
rewrite_config(&root, &ledger, None, false).expect("a rewritten config");
let raw = fs::read_to_string(&path).expect("the rewritten config");
let _ = fs::remove_dir_all(&root);
assert!(
raw.contains("\"port\": 8080"),
"the author's port was reset"
);
assert!(
raw.contains("\"minify\": true"),
"the author's css key was dropped"
);
assert!(
raw.contains(&format!("\"createdWith\": \"{VERSION}\"")),
"createdWith did not move with the tool"
);
assert!(raw.contains(&sha256::hex(b"new")));
assert!(!raw.contains(&sha256::hex(b"old")));
}
#[test]
fn an_untouched_config_round_trips_byte_identically() {
let mut ledger = Ledger::new();
ledger.insert("build.rs".to_string(), sha256::hex(b""));
let root = config_dir(&ledger);
let path = root.join("rahti.config.json");
let before = fs::read_to_string(&path).expect("the written config");
rewrite_config(&root, &ledger, None, false).expect("a rewritten config");
let after = fs::read_to_string(&path).expect("the rewritten config");
let _ = fs::remove_dir_all(&root);
assert_eq!(before, after);
}
#[test]
fn a_missing_key_is_left_missing() {
assert_eq!(
replace_string_value("{ \"schema\": 1 }", "createdWith", "9"),
None
);
}
#[test]
fn a_reformatted_scaffold_block_is_still_found() {
let raw = r#"{"scaffold":{"a":"1"},"schema":1}"#;
let mut ledger = Ledger::new();
ledger.insert("b".to_string(), "2".to_string());
let out = replace_scaffold(raw, &ledger).expect("a replaced block");
assert!(out.contains("\"b\": \"2\""));
assert!(out.contains("\"schema\":1"));
assert!(!out.contains("\"a\""));
}
#[test]
fn the_upgrade_flags_parse_the_way_new_parses_them() {
let options = Options::parse(&["--dry-run", "--ws"]).expect("parsed options");
assert!(options.dry_run);
assert!(options.ws);
assert_eq!(options.db, None);
assert_eq!(
Options::parse(&["--db"]).expect("parsed options").db,
Some(Backend::Sqlite)
);
assert_eq!(
Options::parse(&["--db", "postgres"])
.expect("parsed options")
.db,
Some(Backend::Postgres)
);
let both = Options::parse(&["--db", "--ws"]).expect("parsed options");
assert_eq!(both.db, Some(Backend::Sqlite));
assert!(both.ws);
assert!(Options::parse(&["--tailwind"]).is_err());
}
#[test]
fn an_added_feature_is_recorded_in_the_config() {
let mut ledger = Ledger::new();
ledger.insert("build.rs".to_string(), sha256::hex(b""));
let root = config_dir(&ledger);
let path = root.join("rahti.config.json");
let before = fs::read_to_string(&path).expect("the written config");
let raw = record_features(before.clone(), Some(Backend::Postgres), true);
let _ = fs::remove_dir_all(&root);
let value: serde_json::Value = serde_json::from_str(&raw).expect("still valid JSON");
assert_eq!(
value.get("db").and_then(|d| d.get("backend")),
Some(&serde_json::Value::String("postgres".into()))
);
assert_eq!(value.get("ws"), Some(&serde_json::Value::Bool(true)));
assert!(raw.contains("\"migrations\": \"src/migrations\""), "{raw}");
assert_eq!(record_features(before.clone(), None, false), before);
}
fn config_of(db: Option<Backend>) -> Result<Config, String> {
static NEXT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
let root = std::env::temp_dir().join(format!(
"cargo-rahti-upgrade-db-{}-{}",
std::process::id(),
NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).expect("a temporary directory");
let mut ledger = Ledger::new();
ledger.insert("build.rs".to_string(), sha256::hex(b""));
crate::new::write_config(&root, true, db, false, &ledger).expect("a written config");
let manifest = match db {
Some(_) => "[package]\nname = \"demo\"\n\n[dependencies]\nsea-orm = \"1\"\n",
None => "[package]\nname = \"demo\"\n",
};
fs::write(root.join("Cargo.toml"), manifest).expect("a written manifest");
let read = Config::read(&root);
let _ = fs::remove_dir_all(&root);
read
}
#[test]
fn the_manifest_and_env_are_wired_to_the_config() {
let root = std::env::temp_dir().join(format!("cargo-rahti-wire-{}", std::process::id()));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).expect("a temporary directory");
let mut ledger = Ledger::new();
ledger.insert("build.rs".to_string(), sha256::hex(b""));
crate::new::write_config(&root, true, Some(Backend::Sqlite), true, &ledger)
.expect("a written config");
fs::write(
root.join("Cargo.toml"),
"[package]\nname = \"demo\"\n\n[dependencies]\nrahti = \"0.0.7\"\n",
)
.expect("a written manifest");
fs::write(root.join(".env"), "AUTH_SECRET=\"abc\"\n").expect("a written env");
let config = Config::read(&root).expect("a readable project");
let previewed = wire(&root, &config, true).expect("a preview");
assert_eq!(previewed.dependencies.len(), 2);
assert!(previewed.ws_feature);
assert_eq!(previewed.env, vec![".env".to_string()]);
assert!(
!fs::read_to_string(root.join("Cargo.toml"))
.unwrap()
.contains("sea-orm")
);
assert!(previewed.manifest.is_some());
let wiring = wire(&root, &config, false).expect("a wired project");
assert!(wiring.manual.is_empty());
let manifest = fs::read_to_string(root.join("Cargo.toml")).expect("the manifest");
let env = fs::read_to_string(root.join(".env")).expect("the env file");
assert!(manifest.contains("sea-orm ="), "{manifest}");
assert!(manifest.contains("sea-orm-migration ="), "{manifest}");
assert!(manifest.contains("features = [\"ws\"]"), "{manifest}");
assert!(env.contains("DATABASE_URL=\"sqlite://"), "{env}");
assert!(env.contains("AUTH_SECRET=\"abc\""), "{env}");
let again = wire(&root, &config, false).expect("a second run");
assert!(again.is_empty(), "a second run changed something");
assert!(again.manifest.is_none());
assert_eq!(
fs::read_to_string(root.join("Cargo.toml")).expect("the manifest"),
manifest
);
assert!(!root.join(".env.example").exists());
let _ = fs::remove_dir_all(&root);
}
#[test]
fn the_backend_survives_the_round_trip() {
for backend in [Backend::Sqlite, Backend::Postgres, Backend::MySql] {
let config = config_of(Some(backend)).expect("a readable project");
assert_eq!(config.db, Some(backend));
}
assert_eq!(config_of(None).expect("a readable project").db, None);
}
#[test]
fn an_unnamed_backend_reads_as_sqlite() {
let value: serde_json::Value = serde_json::from_str(r#"{"db":{}}"#).unwrap();
assert!(value.get("db").is_some_and(|d| d.is_object()));
}
#[test]
fn a_project_without_a_database_is_not_wired_for_one() {
let config = config_of(None).expect("a readable project");
assert_eq!(config.db, None);
assert!(!config.ws);
}