Files
core/test/rust/native/application/database/defaults.rs
T
2026-11-03 03:32:07 +00:00

209 lines
7.2 KiB
Rust

use super::*;
#[test]
fn generated_uuid_is_available_when_entries_only_return_counts() {
let directory = Directory::new();
fixture_defaults(&directory.0);
let source = directory.0.join("module/demo/account");
std::fs::write(
source.join("app.dever"),
r#"seed() (total: Int) {
total = model.createMany([{}, {}])
}
"#,
)
.unwrap();
std::fs::write(
source.join("api.dever"),
r#"cmd seed = app.seed
"#,
)
.unwrap();
let project = Project::load(&directory.0).unwrap();
let program = project.check().unwrap();
let binary = build::build(
&program,
&project.sources,
"main",
&directory.0.join("counts"),
)
.unwrap();
std::fs::create_dir(directory.0.join("config")).unwrap();
std::fs::write(
directory.0.join("config/setting.json"),
r#"{"database":{"default":{"type":"sqlite","path":"counts.db"}}}"#,
)
.unwrap();
let output = execute(&binary, "demo.account.seed", "{}");
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&output.stdout).unwrap()["data"],
2
);
}
#[test]
fn database_defaults_choice_and_generated_uuid_survive_native_roundtrips() {
let directory = Directory::new();
verify_defaults(
&directory,
serde_json::json!({"type":"sqlite","path":"data/defaults.db","max_connections":1,"max_page_size":20}),
);
}
#[cfg(feature = "postgres")]
#[test]
#[ignore = "requires explicit isolated config/setting.json database.postgres_test"]
fn postgres_dever_defaults_choice_and_generated_uuid_survive_native_roundtrips() {
let directory = Directory::new();
let schema = super::postgres::Schema::new(
&directory.0,
"dever_native_defaults",
"CREATE SCHEMA dever_native_defaults",
"DROP SCHEMA dever_native_defaults CASCADE",
);
verify_defaults(&directory, schema.connection.clone());
}
fn verify_defaults(directory: &Directory, connection: serde_json::Value) {
fixture_defaults(&directory.0);
let project = Project::load(&directory.0).unwrap();
let program = project.check().unwrap();
let binary = build::build(
&program,
&project.sources,
"main",
&directory.0.join("defaults"),
)
.unwrap();
std::fs::create_dir(directory.0.join("config")).unwrap();
std::fs::write(
directory.0.join("config/setting.json"),
serde_json::to_vec(&serde_json::json!({"database":{"default":connection}})).unwrap(),
)
.unwrap();
let run = |name: &str, input: &str| {
let output = execute(&binary, &format!("demo.account.{name}"), input);
assert!(
output.status.success(),
"{name}: {}",
String::from_utf8_lossy(&output.stderr)
);
serde_json::from_slice::<serde_json::Value>(&output.stdout).unwrap()["data"].clone()
};
let first = run("create", "{}");
assert_eq!(first["name"], "O'Reilly");
assert_eq!(
dever_runtime::value::decimal::Decimal::parse(first["amount"].as_str().unwrap()).unwrap(),
dever_runtime::value::decimal::Decimal::parse("12.30").unwrap()
);
assert_eq!(first["active"], true);
assert_eq!(first["enabled"], true);
assert_eq!(first["note"], "default note");
assert_eq!(first["day"], "2000-02-29");
assert_eq!(first["time"], "23:59:59.999");
assert_eq!(first["elapsed"], -1);
assert_eq!(first["document"], serde_json::json!({"enabled":true}));
assert_eq!(first["empty"], serde_json::Value::Null);
let uuid = first["uid"].as_str().unwrap();
assert_eq!(&uuid[14..15], "7");
assert!(matches!(&uuid[19..20], "8" | "9" | "a" | "b"));
assert_eq!(run("get", r#"{"key":1}"#), first);
let second = run("override", r#"{"value":null}"#);
assert!(second["note"].is_null());
assert_eq!(second["active"], false);
assert_ne!(first["uid"], second["uid"]);
assert_eq!(run("get", r#"{"key":2}"#), second);
let failed = execute(
&binary,
"demo.account.explode",
r#"{"value":9223372036854775807}"#,
);
assert!(!failed.status.success());
assert!(String::from_utf8_lossy(&failed.stderr).contains("integer arithmetic fault"));
assert_eq!(run("count", "{}"), 2);
assert_eq!(run("option", "{}"), "启用");
}
pub(super) fn fixture_defaults(root: &Path) {
let root = root.join("module/demo/account");
std::fs::create_dir_all(&root).unwrap();
let fields = [
("uid", "Uuid", "generated unique"),
("name", "Text(1, 20)", "default \"O'Reilly\""),
("amount", "Decimal(5, 2)", "default 12.30"),
("status", "Status", "default Status.Active"),
("enabled", "Bool", "default true"),
("note", "Text?", "default \"default note\""),
("day", "Date", "default \"2000-02-29\""),
("time", "Time", "default \"23:59:59.999\""),
("elapsed", "Duration", "default -1"),
("document", "Json", "default \"{\\\"enabled\\\":true}\""),
("empty", "Int?", "default null"),
];
let fields = fields
.iter()
.map(|(name, kind, metadata)| format!("{name}: {kind} {metadata}\n"))
.collect::<String>();
std::fs::write(root.join("model.dever"), format!("type Status {{ Active = \"启用\"\nDisabled = \"禁用\" }}\n\ntype Account {{\n{fields}}}\n")).unwrap();
let outputs = "id: model.id, uid: Uuid, name: Text, amount: Decimal, active: Bool, enabled: Bool, note: Text?, day: Date, time: Time, elapsed: Duration, document: Json, empty: Int?";
let view = outputs
.split(", ")
.map(|output| {
let field = output.split_once(':').unwrap().0;
if field == "active" {
"active = row.status == model.Status.Active\n".into()
} else {
format!("{field} = row.{field}\n")
}
})
.collect::<String>();
let methods = [
(
"create",
"",
outputs,
format!("row = model.create({{}})\n{view}"),
),
(
"get",
"key: model.id",
outputs,
format!("row = model.get(key)\n{view}"),
),
(
"override",
"value: Text?",
outputs,
format!(
"row = model.create({{ note = value, status = model.Status.Disabled }})\n{view}"
),
),
(
"explode",
"value: Int",
"id: model.id, result: Int",
"row = model.create({})\nid = row.id\nresult = value + 1".into(),
),
("count", "", "total: Int", "total = model.count()".into()),
(
"option",
"",
"label: Text?",
"choices = model.Status.options\nlabel = get(choices, model.Status.Active)".into(),
),
];
let mut app = String::new();
let mut api = String::new();
for (name, inputs, outputs, body) in methods {
app.push_str(&format!("{name}({inputs}) ({outputs}) {{\n{body}\n}}\n"));
api.push_str(&format!("cmd {name} = app.{name}\n"));
}
std::fs::write(root.join("app.dever"), app).unwrap();
std::fs::write(root.join("api.dever"), api).unwrap();
}