Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,258 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn database_scalar_source_preserves_exact_values_and_nullable_updates() {
|
||||
use dever_runtime::value::decimal::Decimal;
|
||||
let directory = Directory::new();
|
||||
scalar_fixture(&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("scalars"),
|
||||
)
|
||||
.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":"data/scalars.db","max_connections":1,"max_page_size":20}}}"#).unwrap();
|
||||
let run = |name: &str, input: serde_json::Value| {
|
||||
let output = execute(&binary, &format!("demo.account.{name}"), &input.to_string());
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{name}: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
serde_json::from_slice::<serde_json::Value>(&output.stdout).unwrap()["data"].clone()
|
||||
};
|
||||
let base = serde_json::json!({
|
||||
"amount":"1.0000", "price":"999.99", "uid":"00112233-4455-6677-8899-aabbccddeeff",
|
||||
"day":"0000-01-01", "time":"23:59:59.999", "elapsed":i64::MIN,
|
||||
"document":{"items":[true,null,{"name":"雪"}]},
|
||||
"optional_uid":"ffffffff-ffff-ffff-ffff-ffffffffffff", "optional_day":"9999-12-31",
|
||||
"optional_time":"00:00:00", "optional_elapsed":i64::MAX, "optional_document":{"value":42}
|
||||
});
|
||||
let created = run("create", base.clone());
|
||||
assert_eq!(created["id"], 1);
|
||||
assert_eq!(created["amount"], "1");
|
||||
for field in [
|
||||
"price",
|
||||
"uid",
|
||||
"day",
|
||||
"time",
|
||||
"elapsed",
|
||||
"document",
|
||||
"optional_uid",
|
||||
"optional_day",
|
||||
"optional_time",
|
||||
"optional_elapsed",
|
||||
"optional_document",
|
||||
] {
|
||||
assert_eq!(created[field], base[field], "{field}");
|
||||
}
|
||||
assert_eq!(run("get", serde_json::json!({"id":1})), created);
|
||||
for field in [
|
||||
"price",
|
||||
"optional_uid",
|
||||
"optional_day",
|
||||
"optional_time",
|
||||
"optional_elapsed",
|
||||
"optional_document",
|
||||
] {
|
||||
let mut changed = base.clone();
|
||||
changed["id"] = 1.into();
|
||||
changed[field] = serde_json::Value::Null;
|
||||
assert!(run("update", changed)[field].is_null(), "{field}");
|
||||
}
|
||||
for (field, value) in [
|
||||
("amount", "0.00001"),
|
||||
("amount", "1000000000000000000000000000000"),
|
||||
("price", "1000"),
|
||||
("price", "0.001"),
|
||||
("day", "2025-02-29"),
|
||||
("time", "24:00:00"),
|
||||
("uid", "00112233445566778899aabbccddeeff"),
|
||||
] {
|
||||
let mut invalid = base.clone();
|
||||
invalid[field] = value.into();
|
||||
invalid["uid"] = if field == "uid" {
|
||||
value
|
||||
} else {
|
||||
"11112233-4455-6677-8899-aabbccddeeff"
|
||||
}
|
||||
.into();
|
||||
let failed = execute(&binary, "demo.account.create", &invalid.to_string());
|
||||
assert!(!failed.status.success(), "accepted {field}: {value}");
|
||||
assert_eq!(run("count", serde_json::json!({})), 1);
|
||||
}
|
||||
let mut changed = base.clone();
|
||||
changed["id"] = 1.into();
|
||||
changed["amount"] = "-999999999999999999999999999999.9999".into();
|
||||
let minimum = run("update", changed.clone());
|
||||
assert_eq!(minimum["amount"], changed["amount"]);
|
||||
for (index, amount) in [
|
||||
"-10",
|
||||
"-0.0001",
|
||||
"0",
|
||||
"0.0001",
|
||||
"10",
|
||||
"999999999999999999999999999999.9999",
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let mut value = base.clone();
|
||||
value["amount"] = (*amount).into();
|
||||
value["uid"] = format!("00000000-0000-0000-0000-{:012x}", index).into();
|
||||
value["price"] = serde_json::Value::Null;
|
||||
let row = run("create", value);
|
||||
assert_eq!(
|
||||
Decimal::parse(row["amount"].as_str().unwrap()).unwrap(),
|
||||
Decimal::parse(amount).unwrap()
|
||||
);
|
||||
assert!(row["price"].is_null());
|
||||
}
|
||||
let page = run("search", serde_json::json!({"low":"-10"}));
|
||||
let actual: Vec<_> = page["items"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|row| Decimal::parse(row["amount"].as_str().unwrap()).unwrap())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
actual,
|
||||
[
|
||||
"-10",
|
||||
"-0.0001",
|
||||
"0",
|
||||
"0.0001",
|
||||
"10",
|
||||
"999999999999999999999999999999.9999"
|
||||
]
|
||||
.map(|text| Decimal::parse(text).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
run("price", serde_json::json!({"price":"999.990"}))["total"],
|
||||
1
|
||||
);
|
||||
assert_eq!(run("price", serde_json::json!({}))["total"], 6);
|
||||
let reused = run(
|
||||
"document",
|
||||
serde_json::json!({"id":1,"document":base["document"]}),
|
||||
);
|
||||
assert_eq!(reused["document"], base["document"]);
|
||||
assert_eq!(reused["optional_document"], base["document"]);
|
||||
assert_eq!(run("get", serde_json::json!({"id":1})), reused);
|
||||
let raw =
|
||||
r#"{"id":1,"document":{"precise":123456789012345678901234567890.0010,"tiny":1e-1000}}"#;
|
||||
let output = execute(&binary, "demo.account.document", raw);
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let output = String::from_utf8(output.stdout).unwrap();
|
||||
assert!(output.contains("123456789012345678901234567890.0010"));
|
||||
assert!(output.contains("1e-1000"));
|
||||
let output = execute(&binary, "demo.account.get", r#"{"id":1}"#);
|
||||
assert!(
|
||||
String::from_utf8(output.stdout)
|
||||
.unwrap()
|
||||
.contains("123456789012345678901234567890.0010")
|
||||
);
|
||||
}
|
||||
|
||||
fn scalar_fixture(root: &Path) {
|
||||
let fields = [
|
||||
("amount", "Decimal(34, 4)", "Decimal"),
|
||||
("price", "Decimal(5, 2)?", "Decimal?"),
|
||||
("uid", "Uuid", "Uuid"),
|
||||
("day", "Date", "Date"),
|
||||
("time", "Time", "Time"),
|
||||
("elapsed", "Duration", "Duration"),
|
||||
("document", "Json", "Json"),
|
||||
("optional_uid", "Uuid?", "Uuid?"),
|
||||
("optional_day", "Date?", "Date?"),
|
||||
("optional_time", "Time?", "Time?"),
|
||||
("optional_elapsed", "Duration?", "Duration?"),
|
||||
("optional_document", "Json?", "Json?"),
|
||||
];
|
||||
let root = root.join("module/demo/account");
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
let columns = fields
|
||||
.iter()
|
||||
.map(|(name, kind, _)| {
|
||||
format!(
|
||||
"{name}: {kind}{}\n",
|
||||
if *name == "uid" { " unique" } else { "" }
|
||||
)
|
||||
})
|
||||
.collect::<String>();
|
||||
std::fs::write(
|
||||
root.join("model.dever"),
|
||||
format!("type Account {{\n{columns}}}\n"),
|
||||
)
|
||||
.unwrap();
|
||||
let input = fields
|
||||
.iter()
|
||||
.map(|(name, _, kind)| format!("{name}: {kind}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let values = fields
|
||||
.iter()
|
||||
.map(|(name, _, _)| format!("{name} = {name}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let methods = [
|
||||
(
|
||||
"create",
|
||||
input.clone(),
|
||||
"record: model.Account",
|
||||
format!("record = model.create({{ {values} }})"),
|
||||
),
|
||||
(
|
||||
"update",
|
||||
format!("id: model.id, {input}"),
|
||||
"record: model.Account",
|
||||
format!("record = model.update(id, {{ {values} }})"),
|
||||
),
|
||||
(
|
||||
"get",
|
||||
"id: model.id".into(),
|
||||
"record: model.Account",
|
||||
"record = model.get(id)".into(),
|
||||
),
|
||||
(
|
||||
"count",
|
||||
"".into(),
|
||||
"total: Int",
|
||||
"total = model.count()".into(),
|
||||
),
|
||||
(
|
||||
"search",
|
||||
"low: Decimal".into(),
|
||||
"page: model.Page",
|
||||
"page = model.list({ where = amount >= low, order = [amount.asc] })".into(),
|
||||
),
|
||||
(
|
||||
"price",
|
||||
"price: Decimal?".into(),
|
||||
"page: model.Page",
|
||||
"page = model.list({ where = price == price })".into(),
|
||||
),
|
||||
(
|
||||
"document",
|
||||
"id: model.id, document: Json".into(),
|
||||
"record: model.Account",
|
||||
"record = model.update(id, { document = document, optional_document = document })\nrecord.document = document".into(),
|
||||
),
|
||||
];
|
||||
let mut app = String::new();
|
||||
let mut api = String::new();
|
||||
for (name, inputs, output, body) in methods {
|
||||
app.push_str(&format!("{name}({inputs}) ({output}) {{\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();
|
||||
}
|
||||
Reference in new issue
Block a user