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

+315
View File
@@ -0,0 +1,315 @@
use super::{
auth::{KEY, tenant_token},
http::{Server, request},
*,
};
use serde_json::{Value, json};
use std::{
net::{TcpListener, TcpStream},
time::{Duration, Instant},
};
#[cfg(feature = "postgres")]
mod postgres;
fn command(binary: &Path, tenant: Option<i64>, name: &str, input: Value) -> Output {
let mut command = Command::new(binary);
if let Some(tenant) = tenant {
command.args(["--tenant", &tenant.to_string()]);
}
let output = command.args([name, &input.to_string()]).output().unwrap();
if !output.status.success() {
eprintln!(
"{name} tenant={tenant:?}: {}",
String::from_utf8_lossy(&output.stderr)
);
}
output
}
fn success(output: Output) -> Value {
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
serde_json::from_slice::<Value>(&output.stdout).unwrap()["data"].clone()
}
fn get(port: u16, tenant: i64, action: &str) -> (u16, Value) {
let name = format!("tenant-{tenant}");
let token = tenant_token(&name, "session", Some(&name));
let (status, body) = request(
port,
"GET",
&format!("/news/item/admin/entry/{action}"),
"fixture.test",
&format!("Authorization: Bearer {token}\r\n"),
"",
);
(
status,
serde_json::from_str::<Value>(&body).unwrap()["data"].clone(),
)
}
fn post(port: u16, tenant: i64, action: &str, input: Value) -> (u16, Value) {
let name = format!("tenant-{tenant}");
let token = tenant_token(&name, "session", Some(&name));
let (status, body) = request(
port,
"POST",
&format!("/news/item/admin/entry/{action}"),
"fixture.test",
&format!("Authorization: Bearer {token}\r\nContent-Type: application/json\r\n"),
&input.to_string(),
);
(
status,
serde_json::from_str::<Value>(&body).unwrap()["data"].clone(),
)
}
fn start(binary: &Path, port: u16) -> Server {
let mut server = Server(
Command::new(binary)
.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(),
"server exited before listen"
);
assert!(Instant::now() < deadline, "server did not listen");
std::thread::sleep(Duration::from_millis(10));
}
server
}
#[test]
fn tenants_execute_dever_http_rest_capabilities_and_persistent_jobs() {
let directory = Directory::new();
verify(
&directory,
json!({"type":"sqlite","path":"data/control.db", "tenant_directory":"data/tenant", "max_connections":4}),
);
}
fn verify(directory: &Directory, connection: Value) {
let project = Project::load(&fixture().with_file_name("tenant")).unwrap();
let program = project.check().unwrap();
program.check_tenant_entries().unwrap();
let binary = build::build(
&program,
&project.sources,
"main",
&directory.0.join("tenant"),
)
.unwrap();
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
drop(listener);
std::fs::create_dir(directory.0.join("config")).unwrap();
let mut settings = json!({
"http":{"host":"127.0.0.1","port":port},
"sites":{"admin":{"path":"admin","auth":"session","hosts":["fixture.test"]}},
"auth":{"providers":{"session":{"verify":"platform.account.verify","jwtSecret":KEY,"ttlSeconds":60}}},
"database":{"default":connection},
"tenant":{"database":"default","max_pools":4,"idle_timeout_ms":1000},
"runtime":{"mode":"api"},
"job":{"workers":1,"poll_ms":20,"lease_ms":3000,"retry_base_ms":100,"retry_max_ms":1000}
});
let config = directory.0.join("config/setting.json");
std::fs::write(&config, settings.to_string()).unwrap();
for tenant in [1, 2] {
assert_eq!(
success(command(
&binary,
None,
"platform.account.seed",
json!({"subject":format!("tenant-{tenant}"),"session":"session"})
)),
tenant
);
}
assert!(
!command(&binary, None, "news.item.count", json!({}))
.status
.success()
);
assert!(
!command(&binary, Some(1), "news.item.count", json!({}))
.status
.success()
);
for tenant in [1, 2] {
success(command(
&binary,
Some(tenant),
"dever.tenant.migrate",
Value::Null,
));
success(command(
&binary,
Some(tenant),
"dever.tenant.migrate",
Value::Null,
));
success(command(
&binary,
Some(tenant),
"dever.auth.owner",
json!({"site":"admin","user_id":tenant}),
));
assert_eq!(
success(command(
&binary,
Some(tenant),
"news.item.create",
json!({"title":"same-title"})
)),
1
);
}
let server = start(&binary, port);
for tenant in [1, 2] {
assert_eq!(get(port, tenant, "count"), (200, json!(1)));
}
let wrong = tenant_token("tenant-1", "session", Some("tenant-2"));
assert_eq!(
request(
port,
"GET",
"/news/item/admin/entry/count",
"fixture.test",
&format!("Authorization: Bearer {wrong}\r\n"),
""
)
.0,
401
);
let id = "01900000-0000-7000-8000-000000000001";
let (status, key) = post(port, 1, "register", json!({"id":id}));
assert_eq!(status, 200, "{key}");
assert_eq!(key, format!("capability:news.item.admin.{id}"));
assert!(
get(port, 1, "permissions")
.1
.as_array()
.unwrap()
.iter()
.any(|value| value["key"] == key)
);
assert!(
!get(port, 2, "permissions")
.1
.as_array()
.unwrap()
.iter()
.any(|value| value["key"] == key)
);
assert_eq!(post(port, 1, "remove", json!({"id":id})).0, 200);
assert!(
!get(port, 1, "permissions")
.1
.as_array()
.unwrap()
.iter()
.any(|value| value["key"] == key)
);
let first = tenant_token("tenant-1", "session", Some("tenant-1"));
let (status, body) = request(
port,
"GET",
"/news/item/admin/resource?page=1&size=10",
"fixture.test",
&format!("Authorization: Bearer {first}\r\n"),
"",
);
assert_eq!(status, 200, "{body}");
assert!(body.contains("same-title"));
success(command(
&binary,
Some(1),
"dever.tenant.disable",
json!("news"),
));
assert_eq!(get(port, 1, "count").0, 403);
assert_eq!(get(port, 2, "count").0, 200);
assert!(
!command(&binary, Some(1), "news.item.count", json!({}))
.status
.success()
);
assert_eq!(
request(
port,
"GET",
"/news/item/admin/entry/health",
"fixture.test",
"",
""
)
.0,
200
);
success(command(
&binary,
Some(1),
"dever.tenant.enable",
json!("news"),
));
assert!(
!command(&binary, Some(1), "dever.tenant.disable", json!("platform"))
.status
.success()
);
for tenant in [1, 2] {
let input = json!({"title":"system-work","key":"same-key"});
let first = success(command(
&binary,
Some(tenant),
"news.item.submit",
input.clone(),
));
let second = success(command(&binary, Some(tenant), "news.item.submit", input));
assert_eq!(first, second);
assert_eq!(
post(
port,
tenant,
"submit",
json!({"title":"user-work","key":"user-key"})
)
.0,
200
);
}
settings["runtime"]["mode"] = "worker".into();
std::fs::write(&config, settings.to_string()).unwrap();
let mut worker = Server(
Command::new(&binary)
.stdout(std::process::Stdio::null())
.spawn()
.unwrap(),
);
let deadline = Instant::now() + Duration::from_secs(10);
loop {
assert!(worker.0.try_wait().unwrap().is_none(), "worker exited");
let counts = [1, 2]
.map(|tenant| success(command(&binary, Some(tenant), "news.item.count", json!({}))));
if counts == [json!(3), json!(3)] {
break;
}
assert!(
Instant::now() < deadline,
"tasks did not finish: {counts:?}"
);
std::thread::sleep(Duration::from_millis(30));
}
drop(worker);
drop(server);
}
@@ -0,0 +1,34 @@
use super::*;
use crate::application::database::postgres::Schema;
#[test]
#[ignore = "requires explicit isolated database.postgres_test with CREATEDB"]
fn physical_tenants_execute_dever_http_rest_capabilities_and_jobs() {
let directory = Directory::new();
let control = Schema::new(
&directory.0,
"public",
"CREATE DATABASE dever_native_tenant_control",
"DROP DATABASE dever_native_tenant_control WITH (FORCE)",
);
// 每个守卫只清理它成功创建的数据库;迁移仍完整验证已有空库。
let _first = Schema::new(
&directory.0,
"public",
"CREATE DATABASE dever_native_tenant_1",
"DROP DATABASE dever_native_tenant_1 WITH (FORCE)",
);
let _second = Schema::new(
&directory.0,
"public",
"CREATE DATABASE dever_native_tenant_2",
"DROP DATABASE dever_native_tenant_2 WITH (FORCE)",
);
let mut connection = control.connection.clone();
let mut url = url::Url::parse(connection["url"].as_str().unwrap()).unwrap();
url.set_path("/dever_native_tenant_control");
url.set_query(None);
connection["url"] = url.to_string().into();
connection["tenant_database_prefix"] = "dever_native_tenant".into();
verify(&directory, connection);
}