316 lines
9.1 KiB
Rust
316 lines
9.1 KiB
Rust
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);
|
|
}
|