Files
core/test/rust/native/application/database.rs
T
2026-11-03 03:32:07 +00:00

247 lines
7.7 KiB
Rust

use super::*;
mod defaults;
mod migration;
#[cfg(feature = "postgres")]
pub(super) mod postgres;
mod query;
mod read;
mod relation;
mod scalar;
mod text;
#[test]
fn database_source_compiles_and_persists_with_entry_rollback() {
let directory = Directory::new();
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("database"),
)
.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/account.db","max_connections":1,"max_page_size":20}}}"#).unwrap();
let run = |name: &str, input: &str| {
let result = execute(&binary, &format!("demo.account.{name}"), input);
assert!(
result.status.success(),
"{name}: {}",
String::from_utf8_lossy(&result.stderr)
);
serde_json::from_slice::<serde_json::Value>(&result.stdout).unwrap()["data"].clone()
};
let created = run("create", r#"{"name":"Dever 中文"}"#);
assert_eq!(created["id"], 1);
assert_eq!(created["name"], "Dever 中文");
assert!(created["note"].is_null());
assert!(created["created_at"].as_str().unwrap().ends_with('Z'));
assert_eq!(run("get", r#"{"id":1}"#), created);
for input in [
r#"{"name":"Dever 中文"}"#.to_owned(),
r#"{"name":""}"#.to_owned(),
serde_json::json!({"name": "雪".repeat(65)}).to_string(),
] {
assert!(
!execute(&binary, "demo.account.create", &input)
.status
.success()
);
}
assert_eq!(
run("rename", r#"{"id":1,"name":"updated"}"#)["name"],
"updated"
);
let page = run("search", r#"{"name":"updated","page":1,"size":2}"#);
assert_eq!(page["items"][0]["id"], 1);
assert_eq!(page["total"], 1);
assert_eq!(page["pages"], 1);
assert_eq!(
run("search", r#"{"name":"updated","page":2,"size":2}"#)["items"],
serde_json::json!([])
);
assert_eq!(run("find", "{}")["id"], 1);
assert!(run("find", r#"{"note":"missing"}"#).is_null());
assert!(
!execute(
&binary,
"demo.account.search",
r#"{"name":"updated","page":1,"size":21}"#
)
.status
.success()
);
let failed = execute(
&binary,
"demo.account.explode",
r#"{"name":"rollback","value":9223372036854775807}"#,
);
assert!(!failed.status.success());
assert!(String::from_utf8_lossy(&failed.stderr).contains("integer arithmetic fault"));
assert_eq!(run("count", "{}"), 1);
assert_eq!(run("remove", r#"{"id":1}"#), 1);
assert_eq!(run("count", "{}"), 0);
let embedded_nul = format!("{}\0", "雪".repeat(63));
let nul_record = run(
"create",
&serde_json::json!({"name": embedded_nul}).to_string(),
);
assert_eq!(nul_record["name"], embedded_nul);
assert!(
!execute(
&binary,
"demo.account.create",
&serde_json::json!({"name": format!("{}\0", "雪".repeat(64))}).to_string()
)
.status
.success()
);
assert_eq!(
run(
"remove",
&serde_json::json!({"id": nul_record["id"]}).to_string()
),
1
);
let missing = execute(&binary, "demo.account.get", r#"{"id":1}"#);
assert!(!missing.status.success());
assert!(String::from_utf8_lossy(&missing.stderr).contains("database operation failed"));
assert!(
!execute(
&binary,
"demo.account.stage",
r#"{"name":"committed stage","value":9223372036854775807}"#
)
.status
.success()
);
assert_eq!(run("count", "{}"), 1);
let stage = run("find", "{}");
assert_eq!(stage["name"], "committed stage");
run(
"remove",
&serde_json::json!({"id": stage["id"]}).to_string(),
);
http_round_trip(&binary, &directory.0);
}
fn http_round_trip(binary: &Path, directory: &Path) {
use super::http::{Server, request};
use std::{
net::{TcpListener, TcpStream},
time::{Duration, Instant},
};
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
drop(listener);
let config = directory.join("setting-http.json");
std::fs::write(&config, format!(r#"{{"http":{{"host":"127.0.0.1","port":{port}}},"sites":{{"public":{{"path":"","hosts":["fixture.test"]}}}},"database":{{"default":{{"type":"sqlite","path":"data/account.db","max_connections":1,"max_page_size":20}}}}}}"#)).unwrap();
let mut server = Server(
Command::new(binary)
.args(["--config", config.to_str().unwrap()])
.stdout(std::process::Stdio::null())
.spawn()
.unwrap(),
);
let deadline = Instant::now() + Duration::from_secs(5);
while TcpStream::connect(("127.0.0.1", port)).is_err() {
assert!(server.0.try_wait().unwrap().is_none());
assert!(Instant::now() < deadline);
std::thread::sleep(Duration::from_millis(10));
}
let (status, body) = request(
port,
"POST",
"/demo/account/create",
"fixture.test",
"Content-Type: application/json\r\n",
r#"{"name":"HTTP 中文"}"#,
);
assert_eq!(status, 200, "{body}");
let created: serde_json::Value = serde_json::from_str(&body).unwrap();
let id = created["data"]["id"].as_i64().unwrap();
let (status, body) = request(
port,
"GET",
&format!("/demo/account/get?id={id}"),
"fixture.test",
"",
"",
);
assert_eq!(status, 200, "{body}");
assert_eq!(
serde_json::from_str::<serde_json::Value>(&body).unwrap(),
created
);
assert_eq!(
request(
port,
"POST",
"/demo/account/explode",
"fixture.test",
"Content-Type: application/json\r\n",
r#"{"name":"rollback","value":9223372036854775807}"#
)
.0,
500
);
assert_eq!(
request(
port,
"GET",
"/demo/account/get?id=99999",
"fixture.test",
"",
""
)
.0,
500
);
let count = execute(binary, "demo.account.count", "{}");
assert!(count.status.success());
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&count.stdout).unwrap()["data"],
1
);
assert_eq!(
request(
port,
"POST",
"/demo/account/stage",
"fixture.test",
"Content-Type: application/json\r\n",
r#"{"name":"HTTP committed stage","value":9223372036854775807}"#
)
.0,
500
);
let count = execute(binary, "demo.account.count", "{}");
assert!(count.status.success());
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&count.stdout).unwrap()["data"],
2
);
assert!(
Command::new("sh")
.args([
"-c",
"kill -TERM \"$1\"",
"dever-test",
&server.0.id().to_string()
])
.status()
.unwrap()
.success()
);
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if let Some(status) = server.0.try_wait().unwrap() {
assert!(status.success());
break;
}
assert!(Instant::now() < deadline);
std::thread::sleep(Duration::from_millis(10));
}
}