use super::*; #[test] fn text_fields_normalize_writes_without_changing_queries_or_raw_content() { let directory = Directory::new(); let source = directory.0.join("module/demo/account"); std::fs::create_dir_all(&source).unwrap(); std::fs::write( source.join("model.dever"), r#"type Account { name: Text(1, 8) lower unique note: Text? upper body: Text raw label: Text default " 默认 " } "#, ) .unwrap(); std::fs::write(source.join("app.dever"), r#"create(name: Text, note: Text?, body: Text) (row: model.Account) { row = model.create({name = name, note = note, body = body}) } update(id: model.id, name: Text) (row: model.Account) { row = model.update(id, {name = name}) } replace(expected: Text, next: Text) (changed: Int) { changed = model.update({where = name == expected}, {name = next}) } lookup(name: Text) (row: model.Account?) { row = model.first({where = name == name}) } many() (total: Int) { total = model.createMany([{name = " B ", note = " ß ", body = " b "}, {name = " C ", note = null, body = " c "}]) } merge(name: Text, body: Text) (row: model.Account) { row = model.upsert({name = name}, {note = null, body = body}, {body = body}) } guard(value: Bool) (ok: Bool) { dever.api.require(value, dever.api.Error.Forbidden) ok = contains(["a", "b"], "a") and not contains([], "c") } decode(value: Json) (items: List) { items = dever.json.decode(value) } type Entry { label: Text values: List } type Envelope { entries: List optional: Text? } decodeRecord(value: Json) (row: Envelope) { row = dever.json.decode(value) } "#).unwrap(); std::fs::write(source.join("api.dever"), "cmd create = app.create\ncmd update = app.update\ncmd replace = app.replace\ncmd lookup = app.lookup\ncmd many = app.many\ncmd merge = app.merge\ncmd guard = app.guard\ncmd decode = app.decode\ncmd decode_record = app.decodeRecord\n").unwrap(); let project = Project::load(&directory.0).unwrap(); let program = project.check().unwrap(); let binary = build::build( &program, &project.sources, "main", &directory.0.join("text"), ) .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":"text.db"}}}"#, ) .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::(&output.stdout).unwrap()["data"].clone() }; let row = run( "create", serde_json::json!({"name":"\u{2003}Ä\t", "note":" ß ", "body":" \n code\n "}), ); assert_eq!(row["name"], "ä"); assert_eq!(row["note"], "SS"); assert_eq!(row["body"], " \n code\n "); assert_eq!(row["label"], "默认"); assert!(run("lookup", serde_json::json!({"name":" ä "})).is_null()); assert_eq!(run("lookup", serde_json::json!({"name":"ä"})), row); assert_eq!( run( "replace", serde_json::json!({"expected":" ä ","next":" D "}) ), 0 ); assert_eq!( run("replace", serde_json::json!({"expected":"ä","next":" D "})), 1 ); assert_eq!( run("update", serde_json::json!({"id":row["id"],"name":" E "}))["name"], "e" ); assert_eq!(run("many", serde_json::json!({})), 2); let merged = run("merge", serde_json::json!({"name":" B ","body":" exact "})); assert_eq!(merged["name"], "b"); assert_eq!(merged["body"], " exact "); assert!(run("lookup", serde_json::json!({"name":"c"}))["note"].is_null()); for name in [" ", "ABCDEFGHI", " E "] { assert!( !execute( &binary, "demo.account.create", &serde_json::json!({"name":name,"note":null,"body":""}).to_string() ) .status .success() ); } assert_eq!(run("guard", serde_json::json!({"value":true})), true); assert_eq!( run("decode", serde_json::json!({"value":["雪", " x "]})), serde_json::json!(["雪", " x "]) ); assert!( !execute(&binary, "demo.account.decode", r#"{"value":[1]}"#) .status .success() ); assert!( !execute(&binary, "demo.account.guard", r#"{"value":false}"#) .status .success() ); let record = serde_json::json!({"entries":[{"label":" x ","values":[1,2]}],"optional":null}); assert_eq!( run("decode_record", serde_json::json!({"value":record})), record ); for record in [ r#"{"entries":[],"extra":1}"#, r#"{"entries":[{"label":"x","values":[true]}]}"#, r#"{"optional":null}"#, ] { let input = format!("{{\"value\":{record}}}"); assert!( !execute(&binary, "demo.account.decode_record", &input) .status .success(), "{record}" ); } } #[test] fn owned_trim_preserves_allocation_and_unicode_boundaries() { for input in ["", "abc", " ", "\u{2003}雪\t", " a\n b ", "\0 x \0"] { let value = String::from(input); let allocation = value.as_ptr(); let trimmed = dever_runtime::value::text::trim_owned(value); assert_eq!(trimmed, input.trim()); assert_eq!(trimmed.as_ptr(), allocation); } }