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::(&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::(&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::(); 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::(); 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(); }