Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

@@ -0,0 +1,177 @@
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::<serde_json::Value>(&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);
}
}