use super::*; use dever_runtime::{ database::{Database, Options, Sql}, task::{Limits, Runtime}, }; // 验收只读取显式测试连接,并独占一个新 schema;失败也清理本次创建的对象。 pub(in crate::application) struct Schema { database: Database, runtime: Runtime, drop_sql: &'static str, pub connection: serde_json::Value, } impl Drop for Schema { fn drop(&mut self) { self.runtime .run(async |scope| { self.database .executor() .await .unwrap() .initialize(scope, Sql::same(self.drop_sql)) .await .unwrap(); }) .unwrap(); self.database.close(); } } impl Schema { pub fn new( directory: &Path, name: &'static str, create_sql: &'static str, drop_sql: &'static str, ) -> Self { let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(); let settings: serde_json::Value = serde_json::from_slice(&std::fs::read(root.join("config/setting.json")).unwrap()) .unwrap(); let mut connection = settings["database"]["postgres_test"].clone(); let options: Options = serde_json::from_value(connection.clone()).unwrap(); assert_eq!(options.driver, "postgres"); let runtime = Runtime::new(Limits::default()).unwrap(); let database = runtime .run(async |scope| { let database = Database::open(directory, options, scope).await.unwrap(); database .executor() .await .unwrap() .initialize(scope, Sql::same(create_sql)) .await .unwrap(); database }) .unwrap(); let mut schema = Schema { database, runtime, drop_sql, connection: serde_json::Value::Null, }; let mut url = url::Url::parse(connection["url"].as_str().unwrap()).unwrap(); assert!( !url.query_pairs().any(|(key, _)| key == "options"), "test connection must not override search_path" ); url.query_pairs_mut() .append_pair("options", &format!("-csearch_path={name}")); connection["url"] = serde_json::Value::String(url.into()); schema.connection = connection; schema } } #[test] #[ignore = "requires explicit isolated config/setting.json database.postgres_test"] fn postgres_dever_queries_roundtrip_and_rollback() { let directory = Directory::new(); let schema = Schema::new( &directory.0, "dever_native_queries", "CREATE SCHEMA dever_native_queries", "DROP SCHEMA dever_native_queries CASCADE", ); let project = Project::load(&fixture().with_file_name("database")).unwrap(); let program = project.check().unwrap(); let binary = build::build( &program, &project.sources, "main", &directory.0.join("postgres"), ) .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":schema.connection}})).unwrap(), ) .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 first = run("create", serde_json::json!({"name":"雪_%' OR 1=1 --"})); assert_eq!(&first["uid"].as_str().unwrap()[14..15], "7"); assert_eq!(run("get", serde_json::json!({"id":first["id"]})), first); assert_eq!( run("probe", serde_json::json!({"needle":"_%'"}))["contained"], 1 ); assert_eq!( run("probe", serde_json::json!({"needle":""}))["prefixed"], 1 ); assert_eq!( run("bulk", serde_json::json!({"first":"one","second":"two"})), 2 ); let recent = run("recent", serde_json::json!({})); let items = recent["items"].as_array().unwrap(); assert_eq!(items.len(), 3); assert_eq!(items[0]["rank"], 9); assert_eq!(items[1]["rank"], 7); let unique: std::collections::HashSet<_> = items .iter() .map(|row| row["uid"].as_str().unwrap()) .collect(); assert_eq!(unique.len(), 3); let created = run( "save", serde_json::json!({"name":"upsert","initial":"first","replacement":"ignored"}), ); let updated = run( "save", serde_json::json!({"name":"upsert","initial":"ignored","replacement":"changed"}), ); assert_eq!(updated["id"], created["id"]); assert_eq!(updated["uid"], created["uid"]); assert_eq!(updated["created_at"], created["created_at"]); assert_eq!(updated["note"], "changed"); assert_eq!(run("retain", serde_json::json!({"name":"upsert"})), updated); let page = run("scan", serde_json::json!({"after":null,"size":2})); assert_eq!(page["has_more"], true); let next = run("scan", serde_json::json!({"after":page["next"],"size":2})); assert_eq!(next["has_more"], false); assert_eq!(next["items"].as_array().unwrap().len(), 2); assert_ne!(page["items"][0]["id"], next["items"][0]["id"]); let count = run("count", serde_json::json!({})); for (command, input) in [ ( "bulk", serde_json::json!({"first":"rollback","second":"one"}), ), ( "failed_bulk", serde_json::json!({"first":"rollback1","second":"rollback2","value":i64::MAX}), ), ("create", serde_json::json!({"name":"embedded\0nul"})), ] { let failed = execute( &binary, &format!("demo.account.{command}"), &input.to_string(), ); assert!(!failed.status.success(), "{command}"); assert_eq!(run("count", serde_json::json!({})), count); } }