Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user