Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

@@ -0,0 +1,116 @@
use super::*;
#[test]
fn database_migrations_preserve_data_and_reject_loss_or_changed_history() {
let directory = Directory::new();
defaults::fixture_defaults(&directory.0);
let source = directory.0.join("module/demo/account");
let original_model = std::fs::read_to_string(source.join("model.dever"))
.unwrap()
.replace(
"type Account {\n",
"type Account {\nlegacy: Text default \"old\"\n",
);
std::fs::write(source.join("model.dever"), &original_model).unwrap();
let original = compile(&directory.0, "original");
settings(&directory.0, "data/account.db");
let run = |binary: &Path, name: &str, input: &str| {
let result = execute(binary, &format!("demo.account.{name}"), input);
assert!(
result.status.success(),
"{name}: {}",
String::from_utf8_lossy(&result.stderr)
);
serde_json::from_slice::<serde_json::Value>(&result.stdout).unwrap()["data"].clone()
};
let first = run(&original, "create", "{}");
assert_eq!(first["name"], "O'Reilly");
let target = original_model
.replace("legacy: Text default \"old\"\n", "")
.replace("name: Text(1, 20)", "display_name: Text(1, 8)")
.replace(
"default \"O'Reilly\"",
"default \"O'Reilly\" from name unique",
)
.replace("Decimal(5, 2)", "Decimal(7, 3)")
+ UPGRADE;
std::fs::write(source.join("model.dever"), &target).unwrap();
let app = std::fs::read_to_string(source.join("app.dever"))
.unwrap()
.replace("row.name", "row.display_name");
std::fs::write(source.join("app.dever"), app).unwrap();
let upgraded = compile(&directory.0, "upgraded");
let next = run(&upgraded, "get", r#"{"key":1}"#);
assert_eq!(next["name"], "Final");
assert_eq!(next["uid"], first["uid"]);
assert_eq!(next["amount"], first["amount"]);
assert_eq!(run(&upgraded, "count", "{}"), 2);
assert_eq!(run(&upgraded, "get", r#"{"key":2}"#)["name"], "Seed");
assert_eq!(run(&upgraded, "count", "{}"), 2);
assert!(
!execute(&original, "demo.account.count", "{}")
.status
.success()
);
std::fs::write(
source.join("model.dever"),
target.replace("[\"Final\",\"Editor\"]", "[\"Changed\",\"Editor\"]"),
)
.unwrap();
let changed = compile(&directory.0, "changed");
let rejected = execute(&changed, "demo.account.count", "{}");
assert!(!rejected.status.success());
assert!(
String::from_utf8_lossy(&rejected.stderr)
.contains("applied migration content or order changed")
);
assert_eq!(run(&upgraded, "get", r#"{"key":1}"#), next);
let narrow = target
.replace("Text(1, 8)", "Text(1, 4)")
.replace("default \"O'Reilly\"", "default \"Ok\"");
std::fs::write(source.join("model.dever"), narrow).unwrap();
let narrowed = compile(&directory.0, "narrowed");
assert!(
!execute(&narrowed, "demo.account.count", "{}")
.status
.success()
);
assert_eq!(run(&upgraded, "get", r#"{"key":1}"#), next);
assert_eq!(run(&upgraded, "count", "{}"), 2);
settings(&directory.0, "data/fresh.db");
// 空库的最终表没有历史 name/legacy;执行旧数据 before 会失败,因此验证确实跳过。
assert_eq!(run(&upgraded, "count", "{}"), 1);
assert_eq!(run(&upgraded, "get", r#"{"key":1}"#)["name"], "Seed");
}
fn compile(root: &Path, name: &str) -> std::path::PathBuf {
let project = Project::load(root).unwrap();
let program = project.check().unwrap();
build::build(&program, &project.sources, "main", &root.join(name)).unwrap()
}
fn settings(root: &Path, path: &str) {
std::fs::create_dir_all(root.join("config")).unwrap();
std::fs::write(root.join("config/setting.json"),format!(r#"{{"database":{{"default":{{"type":"sqlite","path":"{path}","max_connections":1,"max_page_size":20}}}}}}"#)).unwrap();
}
const UPGRADE: &str = r#"index(display_name, created_at)
migrate normalize {
drop legacy
before {
sqlite = "UPDATE demo_account_model SET name = ?1 WHERE name = ?2"
postgres = "UPDATE demo_account_model SET name = $1 WHERE name = $2"
parameters = ["Editor","O'Reilly"]
}
after {
sqlite = "UPDATE demo_account_model SET display_name = ?1 WHERE display_name = ?2"
postgres = "UPDATE demo_account_model SET display_name = $1 WHERE display_name = $2"
parameters = ["Final","Editor"]
}
}
seed {
{ display_name = "Seed", amount = 1 }
}
"#;