From 7c2cb2e0e58e06f3b15989fe2d4890ecf33f0fd4 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Wed, 29 Apr 2026 21:53:02 +0200 Subject: [PATCH 1/8] copy test_server to agdb_api Co-authored-by: Copilot --- agdb_api/rust/Cargo.toml | 4 +- agdb_api/rust/src/api_types.rs | 63 +++ agdb_api/rust/src/lib.rs | 7 + agdb_api/rust/src/test_server.rs | 503 ++++++++++++++++++++++++ agdb_server/src/config.rs | 81 +--- agdb_server/src/password.rs | 2 +- agdb_server/tests/routes/misc_routes.rs | 8 +- agdb_server/tests/test_server.rs | 14 +- 8 files changed, 599 insertions(+), 83 deletions(-) create mode 100644 agdb_api/rust/src/test_server.rs diff --git a/agdb_api/rust/Cargo.toml b/agdb_api/rust/Cargo.toml index 4abf2d736..4ca60785b 100644 --- a/agdb_api/rust/Cargo.toml +++ b/agdb_api/rust/Cargo.toml @@ -16,7 +16,7 @@ categories = ["database", "api-bindings"] [features] default = [] tls = ["reqwest/default-tls"] -api = ["agdb/api"] +api = ["agdb/api", "dep:tokio", "dep:anyhow"] [dependencies] agdb = { version = "0.12.10", path = "../../agdb", features = ["serde", "openapi"] } @@ -24,3 +24,5 @@ reqwest = { version = "0.13", default-features = false, features = ["charset", " serde = { version = "1", features = ["derive"] } serde_json = "1" utoipa = "5" +tokio = { version = "1", features = ["full"], optional = true } +anyhow = { version = "1", optional = true} diff --git a/agdb_api/rust/src/api_types.rs b/agdb_api/rust/src/api_types.rs index f6d862da3..094489099 100644 --- a/agdb_api/rust/src/api_types.rs +++ b/agdb_api/rust/src/api_types.rs @@ -8,6 +8,34 @@ use serde::Serialize; use std::fmt::Display; use utoipa::ToSchema; +pub const SALT_LEN: usize = 16; +pub const DEFAULT_LOG_BODY_LIMIT: u64 = 10 * 1024; +pub const DEFAULT_REQUEST_BODY_LIMIT: u64 = 10 * 1024 * 1024; + +#[derive(Debug)] +pub struct ConfigImpl { + pub bind: String, + pub address: String, + pub basepath: String, + pub static_roots: Vec, + pub admin: String, + pub log_level: LogLevelFilter, + pub log_body_limit: u64, + pub request_body_limit: u64, + pub data_dir: String, + pub pepper_path: String, + pub tls_certificate: String, + pub tls_key: String, + pub tls_root: String, + pub cluster_token: String, + pub cluster_heartbeat_timeout_ms: u64, + pub cluster_term_timeout_ms: u64, + pub cluster: Vec, + pub cluster_node_id: usize, + pub start_time: u64, + pub pepper: Option<[u8; SALT_LEN]>, +} + #[derive( Debug, Default, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq, Eq, PartialOrd, Ord, )] @@ -315,6 +343,41 @@ impl TryFrom<&str> for LogLevelFilter { } } +pub fn config_to_str(config: &ConfigImpl) -> String { + let mut buffer = String::new(); + buffer.push_str(&format!("bind: {}\n", config.bind)); + buffer.push_str(&format!("address: {}\n", config.address)); + buffer.push_str(&format!("basepath: {}\n", config.basepath)); + buffer.push_str(&format!( + "static_roots: {}\n", + config.static_roots.join(", ") + )); + buffer.push_str(&format!("admin: {}\n", config.admin)); + buffer.push_str(&format!("log_level: {}\n", config.log_level)); + buffer.push_str(&format!("log_body_limit: {}\n", config.log_body_limit)); + buffer.push_str(&format!( + "request_body_limit: {}\n", + config.request_body_limit + )); + buffer.push_str(&format!("data_dir: {}\n", config.data_dir)); + buffer.push_str(&format!("pepper_path: {}\n", config.pepper_path)); + buffer.push_str(&format!("tls_certificate: {}\n", config.tls_certificate)); + buffer.push_str(&format!("tls_key: {}\n", config.tls_key)); + buffer.push_str(&format!("tls_root: {}\n", config.tls_root)); + buffer.push_str(&format!("cluster_token: {}\n", config.cluster_token)); + + buffer.push_str(&format!( + "cluster_heartbeat_timeout_ms: {}\n", + config.cluster_heartbeat_timeout_ms + )); + buffer.push_str(&format!( + "cluster_term_timeout_ms: {}\n", + config.cluster_term_timeout_ms + )); + buffer.push_str(&format!("cluster: [{}]\n", config.cluster.join(", "))); + buffer +} + #[cfg(test)] mod tests { use super::*; diff --git a/agdb_api/rust/src/lib.rs b/agdb_api/rust/src/lib.rs index 1f0d7f82f..dfa9bc1de 100644 --- a/agdb_api/rust/src/lib.rs +++ b/agdb_api/rust/src/lib.rs @@ -5,12 +5,17 @@ mod api_result; mod api_types; mod client; mod http_client; +#[cfg(feature = "api")] +pub mod test_server; pub use api_error::AgdbApiError; pub use api_result::AgdbApiResult; pub use api_types::AdminStatus; pub use api_types::ChangePassword; pub use api_types::ClusterStatus; +pub use api_types::ConfigImpl; +pub use api_types::DEFAULT_LOG_BODY_LIMIT; +pub use api_types::DEFAULT_REQUEST_BODY_LIMIT; pub use api_types::DbAudit; pub use api_types::DbKind; pub use api_types::DbResource; @@ -20,10 +25,12 @@ pub use api_types::LogLevelFilter; pub use api_types::Queries; pub use api_types::QueriesResults; pub use api_types::QueryAudit; +pub use api_types::SALT_LEN; pub use api_types::ServerDatabase; pub use api_types::UserCredentials; pub use api_types::UserLogin; pub use api_types::UserStatus; +pub use api_types::config_to_str; pub use client::AgdbApi; pub use http_client::HttpClient; pub use http_client::ReqwestClient; diff --git a/agdb_api/rust/src/test_server.rs b/agdb_api/rust/src/test_server.rs new file mode 100644 index 000000000..b9de12e79 --- /dev/null +++ b/agdb_api/rust/src/test_server.rs @@ -0,0 +1,503 @@ +use crate::AgdbApi; +use crate::ClusterStatus; +use crate::ConfigImpl; +use crate::DEFAULT_LOG_BODY_LIMIT; +use crate::DEFAULT_REQUEST_BODY_LIMIT; +use crate::ReqwestClient; +use crate::config_to_str; +use std::collections::HashMap; +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::Weak; +use std::sync::atomic::AtomicU16; +use std::sync::atomic::Ordering; +use std::time::Duration; +use std::time::Instant; +use tokio::process::Child; +use tokio::process::Command; +use tokio::sync::RwLock; + +const ADMIN: &str = "admin"; +const BINARY: &str = "agdb_server"; +const CONFIG_FILE: &str = "agdb_server.yaml"; +const DEFAULT_PORT: u16 = 3000; +const HOST: &str = "localhost"; +const POLL_INTERVAL: u64 = 100; +const RETRY_TIMEOUT: Duration = Duration::from_secs(1); +const RETRY_ATTEMPS: u16 = 10; +const SERVER_DATA_DIR: &str = "agdb_server_data"; +const SHUTDOWN_RETRY_TIMEOUT: Duration = Duration::from_millis(100); +const SHUTDOWN_RETRY_ATTEMPTS: u16 = 100; +const TEST_TIMEOUT: u128 = 30000; +const CLIENT_TIMEOUT: Duration = Duration::from_secs(30); + +type ClusterImpl = Vec; + +static PORT: AtomicU16 = AtomicU16::new(DEFAULT_PORT); +static COUNTER: AtomicU16 = AtomicU16::new(1); +static SERVER: std::sync::OnceLock>> = std::sync::OnceLock::new(); +static CLUSTER: std::sync::OnceLock>> = std::sync::OnceLock::new(); + +fn server_bin() -> anyhow::Result { + let mut path = std::env::current_exe()?; + path.pop(); + path.pop(); + Ok(path.join(format!("{BINARY}{}", std::env::consts::EXE_SUFFIX))) +} + +pub struct TestServer { + pub dir: String, + pub data_dir: String, + pub api: AgdbApi, + pub server: Arc, +} + +pub struct TestServerImpl { + pub dir: String, + pub data_dir: String, + pub address: String, + pub process: Option, +} + +pub struct TestCluster { + apis: Vec>, + _cluster: Arc, +} + +#[cfg(feature = "tls")] +pub fn root_ca() -> reqwest::Certificate { + static ROOT_CA: std::sync::OnceLock = std::sync::OnceLock::new(); + + ROOT_CA + .get_or_init(|| { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let root_ca_buf = + std::fs::read(format!("{manifest_dir}/tests/test_root_ca.pem")).unwrap(); + reqwest::Certificate::from_pem(&root_ca_buf).unwrap() + }) + .clone() +} + +#[cfg(feature = "tls")] +pub fn reqwest_client() -> reqwest::Client { + reqwest::Client::builder() + .add_root_certificate(root_ca()) + .use_rustls_tls() + .timeout(CLIENT_TIMEOUT) + .build() + .unwrap() +} + +#[cfg(not(feature = "tls"))] +pub fn reqwest_client() -> reqwest::Client { + reqwest::Client::builder() + .timeout(CLIENT_TIMEOUT) + .build() + .unwrap() +} + +impl TestServerImpl { + pub async fn with_config(mut config: ConfigImpl) -> anyhow::Result { + if config.address.is_empty() { + let port = Self::next_port(); + let address = format!("http://{HOST}:{port}"); + config.bind = format!("{HOST}:{port}"); + config.address = address; + }; + + let dir = format!( + "{BINARY}.{}.test", + config.address.split(':').next_back().unwrap() + ); + let data_dir = format!("{dir}/{SERVER_DATA_DIR}"); + + Self::remove_dir_if_exists(&dir)?; + std::fs::create_dir(&dir)?; + + std::fs::write(Path::new(&dir).join(CONFIG_FILE), config_to_str(&config))?; + + let api_address = if config.basepath.is_empty() { + config.address.clone() + } else { + format!("{}{}", config.address, config.basepath) + }; + + let mut process = Command::new(server_bin()?) + .current_dir(&dir) + .kill_on_drop(true) + .spawn()?; + let api = AgdbApi::new(ReqwestClient::with_client(reqwest_client()), &api_address); + + for _ in 0..RETRY_ATTEMPS { + match api.status().await { + Ok(200) => { + return Ok(Self { + dir, + data_dir, + address: api_address, + process: Some(process), + }); + } + Ok(status) => println!("Server at {api_address} is not ready: {status}"), + Err(e) => println!("Failed to contact server at {api_address}: {e:?}"), + } + + std::thread::sleep(RETRY_TIMEOUT); + } + + let mut status = "running".to_string(); + if let Ok(Some(s)) = process.try_wait() + && let Some(code) = s.code() + { + status = code.to_string() + } + + anyhow::bail!("Failed to start server '{api_address}' ({status})") + } + + pub async fn new() -> anyhow::Result { + let config = ConfigImpl { + bind: String::new(), + address: String::new(), + basepath: String::new(), + static_roots: Vec::new(), + admin: ADMIN.to_string(), + log_level: crate::LogLevelFilter::Info, + log_body_limit: DEFAULT_LOG_BODY_LIMIT, + request_body_limit: DEFAULT_REQUEST_BODY_LIMIT, + data_dir: SERVER_DATA_DIR.into(), + pepper_path: String::new(), + tls_certificate: String::new(), + tls_key: String::new(), + tls_root: String::new(), + cluster_token: "test".to_string(), + cluster_heartbeat_timeout_ms: 1000, + cluster_term_timeout_ms: 3000, + cluster: Vec::new(), + cluster_node_id: 0, + start_time: 0, + pepper: None, + }; + + Self::with_config(config).await + } + + pub fn next_port() -> u16 { + PORT.fetch_add(1, Ordering::Relaxed) + std::process::id() as u16 + } + + pub fn restart(&mut self) -> anyhow::Result<()> { + self.process = Some( + Command::new(server_bin()?) + .current_dir(&self.dir) + .kill_on_drop(true) + .spawn()?, + ); + Ok(()) + } + + pub async fn wait(&mut self) -> anyhow::Result<()> { + if let Some(p) = self.process.as_mut() { + p.wait().await?; + } + + Ok(()) + } + + async fn shutdown_server(mut process: Child, mut address: String) -> anyhow::Result<()> { + if process.try_wait()?.is_some() { + return Ok(()); + } + + if !address.starts_with("http") { + address = format!("http://{address}"); + } + + let mut admin = HashMap::<&str, String>::new(); + admin.insert("username", ADMIN.to_string()); + admin.insert("password", ADMIN.to_string()); + + let client = reqwest_client(); + + let token: String = client + .post(format!("{address}/api/v1/user/login")) + .json(&admin) + .timeout(CLIENT_TIMEOUT) + .send() + .await? + .json() + .await?; + + client + .post(format!("{address}/api/v1/admin/shutdown")) + .timeout(CLIENT_TIMEOUT) + .bearer_auth(token) + .send() + .await?; + + for _ in 0..SHUTDOWN_RETRY_ATTEMPTS { + if process.try_wait()?.is_some() { + return Ok(()); + } + std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); + } + + process.kill().await?; + + for _ in 0..SHUTDOWN_RETRY_ATTEMPTS { + if process.try_wait()?.is_some() { + return Ok(()); + } + std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); + } + + anyhow::bail!("Failed to shutdown server") + } + + fn remove_dir_if_exists(dir: &str) -> anyhow::Result<()> { + if Path::new(dir).exists() { + std::fs::remove_dir_all(dir)?; + } + + Ok(()) + } +} + +impl TestServer { + pub async fn new() -> anyhow::Result { + let global_server = SERVER.get_or_init(|| RwLock::new(Weak::new())); + let mut server_guard = global_server.write().await; + + let server = if let Some(server) = server_guard.upgrade() { + server + } else { + let server = Arc::new(TestServerImpl::new().await?); + *server_guard = Arc::downgrade(&server); + server + }; + + Ok(Self { + api: AgdbApi::new( + ReqwestClient::with_client(reqwest_client()), + &server.address, + ), + dir: server.dir.clone(), + data_dir: server.data_dir.clone(), + server, + }) + } + + pub fn url(&self, uri: &str) -> String { + format!("{}{uri}", self.api.address()) + } + + pub fn full_url(&self, uri: &str) -> String { + self.api.base_url().to_string() + uri + } +} + +impl Drop for TestServerImpl { + fn drop(&mut self) { + static DROP_RUNTIME: std::sync::OnceLock = + std::sync::OnceLock::new(); + + if let Some(p) = self.process.take() { + let address = self.address.clone(); + let dir = self.dir.clone(); + + let f = DROP_RUNTIME + .get_or_init(|| tokio::runtime::Runtime::new().unwrap()) + .spawn(async move { + let _ = Self::shutdown_server(p, address) + .await + .inspect_err(|e| println!("{e:?}")); + }); + + for _ in 0..SHUTDOWN_RETRY_ATTEMPTS { + if f.is_finished() { + break; + } + + std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); + } + + let _ = Self::remove_dir_if_exists(&dir).inspect_err(|e| println!("{e:?}")); + } + } +} + +impl TestCluster { + async fn new() -> anyhow::Result { + let global_cluster = CLUSTER.get_or_init(|| RwLock::new(Weak::new())); + let mut cluster_guard = global_cluster.write().await; + + let nodes = if let Some(nodes) = cluster_guard.upgrade() { + nodes + } else { + let nodes = Arc::new(create_cluster(3, false).await?); + *cluster_guard = Arc::downgrade(&nodes); + nodes + }; + + let mut cluster = Self { + apis: nodes + .iter() + .map(|s| { + Ok(AgdbApi::new( + ReqwestClient::with_client(reqwest_client()), + &s.address, + )) + }) + .collect::>>>()?, + _cluster: nodes, + }; + + cluster.apis[1].cluster_user_login(ADMIN, ADMIN).await?; + + Ok(cluster) + } +} + +pub fn next_user_name() -> String { + format!("db_user{}", COUNTER.fetch_add(1, Ordering::SeqCst)) +} + +pub fn next_db_name() -> String { + format!("db{}", COUNTER.fetch_add(1, Ordering::SeqCst)) +} + +pub async fn wait_for_ready(api: &AgdbApi) -> anyhow::Result<()> { + for _ in 0..RETRY_ATTEMPS { + if api.status().await.is_ok() { + return Ok(()); + } + + std::thread::sleep(RETRY_TIMEOUT); + } + + anyhow::bail!("Server not ready") +} + +pub async fn wait_for_leader(api: &AgdbApi) -> anyhow::Result> { + let now = Instant::now(); + + while now.elapsed().as_millis() < TEST_TIMEOUT { + let status = api.cluster_status().await?; + + if status.1.iter().any(|s| s.leader) { + return Ok(status.1); + } + + std::thread::sleep(std::time::Duration::from_millis(POLL_INTERVAL)); + } + + Err(anyhow::anyhow!( + "Leader not found within {TEST_TIMEOUT}seconds" + )) +} + +pub async fn create_cluster(nodes: usize, tls: bool) -> anyhow::Result> { + let mut configs = Vec::with_capacity(nodes); + let mut cluster = Vec::with_capacity(nodes); + let mut servers = Vec::with_capacity(nodes); + let protocol = if tls { "https" } else { "http" }; + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?; + let tls_cert = if tls { + format!("{manifest_dir}/tests/test_cert.pem") + } else { + String::new() + }; + let tls_key = if tls { + format!("{manifest_dir}/tests/test_cert.key.pem") + } else { + String::new() + }; + let tls_root = if tls { + format!("{manifest_dir}/tests/test_root_ca.pem") + } else { + String::new() + }; + + for _ in 0..nodes { + let port = TestServerImpl::next_port(); + let config = ConfigImpl { + bind: format!("{HOST}:{port}"), + address: format!("{protocol}://{HOST}:{port}"), + basepath: String::new(), + static_roots: Vec::new(), + admin: ADMIN.to_string(), + log_level: crate::LogLevelFilter::Info, + log_body_limit: DEFAULT_LOG_BODY_LIMIT, + request_body_limit: DEFAULT_REQUEST_BODY_LIMIT, + data_dir: SERVER_DATA_DIR.into(), + pepper_path: String::new(), + tls_certificate: tls_cert.clone(), + tls_key: tls_key.clone(), + tls_root: tls_root.clone(), + cluster_token: "test".to_string(), + cluster_heartbeat_timeout_ms: 1000, + cluster_term_timeout_ms: 3000, + cluster: Vec::new(), + cluster_node_id: 0, + start_time: 0, + pepper: None, + }; + + configs.push(config); + cluster.push(format!("{protocol}://{HOST}:{port}")); + } + + for config in &mut configs { + config.cluster = cluster.clone(); + } + + for server in configs + .into_iter() + .map(|c| tokio::spawn(async move { TestServerImpl::with_config(c).await })) + { + let server = server.await??; + let api = AgdbApi::new( + ReqwestClient::with_client(reqwest_client()), + &server.address, + ); + servers.push((server, api)); + } + + let mut statuses = Vec::with_capacity(nodes); + + for server in &servers { + statuses.push(wait_for_leader(&server.1).await?); + } + + for status in &statuses[1..] { + assert_eq!(statuses[0], *status); + } + + let leader = statuses[0] + .iter() + .enumerate() + .find_map(|(i, s)| if s.leader { Some(i) } else { None }) + .unwrap(); + servers.swap(0, leader); + + Ok(servers.into_iter().map(|(s, _)| s).collect()) +} + +pub struct TestDir { + pub dir: PathBuf, +} + +impl TestDir { + pub fn new() -> anyhow::Result { + let dir = format!("static_files_test{}", TestServerImpl::next_port()).into(); + std::fs::create_dir_all(&dir)?; + Ok(Self { dir }) + } +} + +impl Drop for TestDir { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.dir); + } +} diff --git a/agdb_server/src/config.rs b/agdb_server/src/config.rs index 91ad773f8..ddc5475f4 100644 --- a/agdb_server/src/config.rs +++ b/agdb_server/src/config.rs @@ -1,38 +1,14 @@ +use agdb_api::ConfigImpl; +use agdb_api::DEFAULT_LOG_BODY_LIMIT; +use agdb_api::DEFAULT_REQUEST_BODY_LIMIT; use agdb_api::LogLevelFilter; +use agdb_api::SALT_LEN; use std::sync::Arc; use std::time::SystemTime; use std::time::UNIX_EPOCH; pub(crate) type Config = Arc; -pub(crate) const SALT_LEN: usize = 16; -pub(crate) const DEFAULT_LOG_BODY_LIMIT: u64 = 10 * 1024; -pub(crate) const DEFAULT_REQUEST_BODY_LIMIT: u64 = 10 * 1024 * 1024; - -#[derive(Debug)] -pub struct ConfigImpl { - pub(crate) bind: String, - pub(crate) address: String, - pub(crate) basepath: String, - pub(crate) static_roots: Vec, - pub(crate) admin: String, - pub(crate) log_level: LogLevelFilter, - pub(crate) log_body_limit: u64, - pub(crate) request_body_limit: u64, - pub(crate) data_dir: String, - pub(crate) pepper_path: String, - pub(crate) tls_certificate: String, - pub(crate) tls_key: String, - pub(crate) tls_root: String, - pub(crate) cluster_token: String, - pub(crate) cluster_heartbeat_timeout_ms: u64, - pub(crate) cluster_term_timeout_ms: u64, - pub(crate) cluster: Vec, - pub(crate) cluster_node_id: usize, - pub(crate) start_time: u64, - pub(crate) pepper: Option<[u8; SALT_LEN]>, -} - pub(crate) fn new(config_file: &str) -> Result { if let Ok(content) = std::fs::read_to_string(config_file) { let mut config_impl: ConfigImpl = from_str(&content)?; @@ -88,8 +64,8 @@ pub(crate) fn new(config_file: &str) -> Result { static_roots: Vec::new(), admin: "admin".to_string(), log_level: LogLevelFilter::Info, - log_body_limit: DEFAULT_LOG_BODY_LIMIT, - request_body_limit: DEFAULT_REQUEST_BODY_LIMIT, + log_body_limit: agdb_api::DEFAULT_LOG_BODY_LIMIT, + request_body_limit: agdb_api::DEFAULT_REQUEST_BODY_LIMIT, data_dir: "agdb_server_data".to_string(), pepper_path: String::new(), tls_certificate: String::new(), @@ -107,7 +83,7 @@ pub(crate) fn new(config_file: &str) -> Result { pepper: None, }; - std::fs::write(config_file, to_str(&config)) + std::fs::write(config_file, agdb_api::config_to_str(&config)) .map_err(|e| format!("Failed to write config file '{}': {e:?}", config_file))?; Ok(Config::new(config)) @@ -214,41 +190,6 @@ pub(crate) fn from_str(content: &str) -> Result { Ok(config) } -pub(crate) fn to_str(config: &ConfigImpl) -> String { - let mut buffer = String::new(); - buffer.push_str(&format!("bind: {}\n", config.bind)); - buffer.push_str(&format!("address: {}\n", config.address)); - buffer.push_str(&format!("basepath: {}\n", config.basepath)); - buffer.push_str(&format!( - "static_roots: {}\n", - config.static_roots.join(", ") - )); - buffer.push_str(&format!("admin: {}\n", config.admin)); - buffer.push_str(&format!("log_level: {}\n", config.log_level)); - buffer.push_str(&format!("log_body_limit: {}\n", config.log_body_limit)); - buffer.push_str(&format!( - "request_body_limit: {}\n", - config.request_body_limit - )); - buffer.push_str(&format!("data_dir: {}\n", config.data_dir)); - buffer.push_str(&format!("pepper_path: {}\n", config.pepper_path)); - buffer.push_str(&format!("tls_certificate: {}\n", config.tls_certificate)); - buffer.push_str(&format!("tls_key: {}\n", config.tls_key)); - buffer.push_str(&format!("tls_root: {}\n", config.tls_root)); - buffer.push_str(&format!("cluster_token: {}\n", config.cluster_token)); - - buffer.push_str(&format!( - "cluster_heartbeat_timeout_ms: {}\n", - config.cluster_heartbeat_timeout_ms - )); - buffer.push_str(&format!( - "cluster_term_timeout_ms: {}\n", - config.cluster_term_timeout_ms - )); - buffer.push_str(&format!("cluster: [{}]\n", config.cluster.join(", "))); - buffer -} - #[cfg(test)] mod tests { use super::*; @@ -305,7 +246,7 @@ mod tests { start_time: 0, pepper: None, }; - std::fs::write(test_file.filename, to_str(&config)).unwrap(); + std::fs::write(test_file.filename, agdb_api::config_to_str(&config)).unwrap(); config::new(test_file.filename).unwrap_err(); } @@ -339,7 +280,7 @@ mod tests { pepper: None, }; - std::fs::write(test_file.filename, to_str(&config)).unwrap(); + std::fs::write(test_file.filename, agdb_api::config_to_str(&config)).unwrap(); let config = config::new(test_file.filename).unwrap(); @@ -371,7 +312,7 @@ mod tests { start_time: 0, pepper: None, }; - std::fs::write(test_file.filename, to_str(&config)).unwrap(); + std::fs::write(test_file.filename, agdb_api::config_to_str(&config)).unwrap(); config::new(test_file.filename).unwrap_err(); } @@ -403,7 +344,7 @@ mod tests { start_time: 0, pepper: None, }; - std::fs::write(test_file.filename, to_str(&config)).unwrap(); + std::fs::write(test_file.filename, agdb_api::config_to_str(&config)).unwrap(); config::new(test_file.filename).unwrap_err(); } diff --git a/agdb_server/src/password.rs b/agdb_server/src/password.rs index e86c63717..68403929d 100644 --- a/agdb_server/src/password.rs +++ b/agdb_server/src/password.rs @@ -1,5 +1,6 @@ use crate::error_code::ErrorCode; use crate::server_error::ServerResult; +use agdb_api::SALT_LEN; use ring::digest; use ring::pbkdf2; use ring::rand::SecureRandom; @@ -10,7 +11,6 @@ use std::num::NonZeroU32; use std::sync::OnceLock; pub(crate) const PASSWORD_LEN: usize = digest::SHA256_OUTPUT_LEN; -pub(crate) const SALT_LEN: usize = 16; pub(crate) static BUILTIN_PEPPER: &[u8; SALT_LEN] = std::include_bytes!("../pepper"); pub(crate) static PEPPER: OnceLock<[u8; SALT_LEN]> = OnceLock::new(); diff --git a/agdb_server/tests/routes/misc_routes.rs b/agdb_server/tests/routes/misc_routes.rs index 1b4d5e454..ed583a8b1 100644 --- a/agdb_server/tests/routes/misc_routes.rs +++ b/agdb_server/tests/routes/misc_routes.rs @@ -155,7 +155,7 @@ async fn basepath_test() -> anyhow::Result<()> { use crate::DEFAULT_LOG_BODY_LIMIT; use crate::DEFAULT_REQUEST_BODY_LIMIT; - let config = crate::config::ConfigImpl { + let config = agdb_api::ConfigImpl { bind: String::new(), address: String::new(), basepath: "/public".to_string(), @@ -329,7 +329,7 @@ async fn studio() -> anyhow::Result<()> { #[tokio::test] async fn large_payload() -> anyhow::Result<()> { - let config = crate::config::ConfigImpl { + let config = agdb_api::ConfigImpl { bind: String::new(), address: String::new(), basepath: String::new(), @@ -408,7 +408,7 @@ async fn static_files() -> anyhow::Result<()> { let test_dir1 = TestDir::new()?; let test_dir2 = TestDir::new()?; - let config = crate::config::ConfigImpl { + let config = agdb_api::ConfigImpl { bind: String::new(), address: String::new(), basepath: String::new(), @@ -473,7 +473,7 @@ async fn static_files_with_basepath() -> anyhow::Result<()> { let test_dir1 = TestDir::new()?; let test_dir2 = TestDir::new()?; - let config = crate::config::ConfigImpl { + let config = agdb_api::ConfigImpl { bind: String::new(), address: String::new(), basepath: "/some_basepath".to_string(), diff --git a/agdb_server/tests/test_server.rs b/agdb_server/tests/test_server.rs index 068dffc11..dc1741981 100644 --- a/agdb_server/tests/test_server.rs +++ b/agdb_server/tests/test_server.rs @@ -1,15 +1,12 @@ -#[path = "../src/config.rs"] -pub mod config; mod routes; #[cfg(feature = "tls")] mod tls; -use crate::config::ConfigImpl; -use crate::config::DEFAULT_LOG_BODY_LIMIT; -use crate::config::DEFAULT_REQUEST_BODY_LIMIT; -use crate::config::to_str; use agdb_api::AgdbApi; use agdb_api::ClusterStatus; +use agdb_api::ConfigImpl; +use agdb_api::DEFAULT_LOG_BODY_LIMIT; +use agdb_api::DEFAULT_REQUEST_BODY_LIMIT; use agdb_api::ReqwestClient; use std::collections::HashMap; use std::path::Path; @@ -121,7 +118,10 @@ impl TestServerImpl { Self::remove_dir_if_exists(&dir)?; std::fs::create_dir(&dir)?; - std::fs::write(Path::new(&dir).join(CONFIG_FILE), to_str(&config))?; + std::fs::write( + Path::new(&dir).join(CONFIG_FILE), + agdb_api::config_to_str(&config), + )?; let api_address = if config.basepath.is_empty() { config.address.clone() From 6951b7c64d869d08c91cc6db2484cf21d4fee3b1 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Fri, 1 May 2026 14:51:02 +0200 Subject: [PATCH 2/8] wip --- agdb/src/type_def.rs | 31 +++++++ agdb_api/rust/src/api_types.rs | 1 + agdb_api/rust/src/test_server.rs | 150 +++++++++++++++++++++++++------ 3 files changed, 154 insertions(+), 28 deletions(-) diff --git a/agdb/src/type_def.rs b/agdb/src/type_def.rs index 15a4fa236..176d700de 100644 --- a/agdb/src/type_def.rs +++ b/agdb/src/type_def.rs @@ -288,6 +288,37 @@ impl TypeDefinition for (T, V) { } impl ImplDefinition for (T, V) {} +impl TypeDefinition for std::sync::Arc { + fn type_def() -> Type { + Type::Pointer(Pointer { + kind: PointerKind::Arc, + ty: T::type_def, + }) + } +} + +impl ImplDefinition for std::sync::Arc {} + +impl TypeDefinition for [T; N] { + fn type_def() -> Type { + Type::Slice(T::type_def) + } +} + +impl TypeDefinition for std::path::PathBuf { + fn type_def() -> Type { + Type::Struct(Struct { + name: "PathBuf", + generics: &[], + fields: &[Variable { + name: "inner", + ty: Some(|| Type::Vec(Box::::type_def)), + }], + functions: &[], + }) + } +} + macro_rules! impl_type_def_fn_ptr { ($(($($arg:ident),*)),* $(,)?) => { $( diff --git a/agdb_api/rust/src/api_types.rs b/agdb_api/rust/src/api_types.rs index 094489099..919e0ca6f 100644 --- a/agdb_api/rust/src/api_types.rs +++ b/agdb_api/rust/src/api_types.rs @@ -13,6 +13,7 @@ pub const DEFAULT_LOG_BODY_LIMIT: u64 = 10 * 1024; pub const DEFAULT_REQUEST_BODY_LIMIT: u64 = 10 * 1024 * 1024; #[derive(Debug)] +#[cfg_attr(feature = "api", derive(agdb::TypeDefImpl))] pub struct ConfigImpl { pub bind: String, pub address: String, diff --git a/agdb_api/rust/src/test_server.rs b/agdb_api/rust/src/test_server.rs index b9de12e79..46af2650d 100644 --- a/agdb_api/rust/src/test_server.rs +++ b/agdb_api/rust/src/test_server.rs @@ -1,11 +1,15 @@ use crate::AgdbApi; +use crate::AgdbApiError; use crate::ClusterStatus; use crate::ConfigImpl; use crate::DEFAULT_LOG_BODY_LIMIT; use crate::DEFAULT_REQUEST_BODY_LIMIT; use crate::ReqwestClient; use crate::config_to_str; +use agdb::type_def::Struct; +use agdb::type_def::TypeDefinition; use std::collections::HashMap; +use std::env::VarError; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -39,13 +43,83 @@ static COUNTER: AtomicU16 = AtomicU16::new(1); static SERVER: std::sync::OnceLock>> = std::sync::OnceLock::new(); static CLUSTER: std::sync::OnceLock>> = std::sync::OnceLock::new(); -fn server_bin() -> anyhow::Result { +#[derive(Debug, agdb::TypeDefImpl)] +pub struct TestError { + description: String, +} + +#[cfg_attr(feature = "api", agdb::fn_def())] +fn bail(description: String) -> TestError { + TestError { description } +} + +impl From for TestError { + #[track_caller] + fn from(error: std::io::Error) -> Self { + TestError { + description: error.to_string(), + } + } +} + +impl From for TestError { + #[track_caller] + fn from(error: reqwest::Error) -> Self { + TestError { + description: error.to_string(), + } + } +} + +impl From for TestError { + #[track_caller] + fn from(error: AgdbApiError) -> Self { + TestError { + description: error.to_string(), + } + } +} + +impl From for TestError { + #[track_caller] + fn from(error: VarError) -> Self { + TestError { + description: error.to_string(), + } + } +} + +impl From for TestError { + #[track_caller] + fn from(error: tokio::task::JoinError) -> Self { + TestError { + description: error.to_string(), + } + } +} + +pub struct TestServerProcess(pub Child); + +impl TypeDefinition for TestServerProcess { + fn type_def() -> agdb::type_def::Type { + agdb::type_def::Type::Struct(Struct { + name: "TestServerProcess", + generics: &[], + fields: &[], + functions: &[], + }) + } +} + +#[cfg_attr(feature = "api", agdb::fn_def())] +fn server_bin() -> Result { let mut path = std::env::current_exe()?; path.pop(); path.pop(); Ok(path.join(format!("{BINARY}{}", std::env::consts::EXE_SUFFIX))) } +#[derive(agdb::TypeDef)] pub struct TestServer { pub dir: String, pub data_dir: String, @@ -53,13 +127,15 @@ pub struct TestServer { pub server: Arc, } +#[derive(agdb::TypeDef)] pub struct TestServerImpl { pub dir: String, pub data_dir: String, pub address: String, - pub process: Option, + pub process: Option, } +#[derive(agdb::TypeDef)] pub struct TestCluster { apis: Vec>, _cluster: Arc, @@ -97,8 +173,9 @@ pub fn reqwest_client() -> reqwest::Client { .unwrap() } +#[cfg_attr(feature = "api", agdb::impl_def())] impl TestServerImpl { - pub async fn with_config(mut config: ConfigImpl) -> anyhow::Result { + pub async fn with_config(mut config: ConfigImpl) -> Result { if config.address.is_empty() { let port = Self::next_port(); let address = format!("http://{HOST}:{port}"); @@ -136,7 +213,7 @@ impl TestServerImpl { dir, data_dir, address: api_address, - process: Some(process), + process: Some(TestServerProcess(process)), }); } Ok(status) => println!("Server at {api_address} is not ready: {status}"), @@ -153,10 +230,12 @@ impl TestServerImpl { status = code.to_string() } - anyhow::bail!("Failed to start server '{api_address}' ({status})") + Err(bail(format!( + "Failed to start server '{api_address}' ({status})" + ))) } - pub async fn new() -> anyhow::Result { + pub async fn new() -> Result { let config = ConfigImpl { bind: String::new(), address: String::new(), @@ -187,26 +266,29 @@ impl TestServerImpl { PORT.fetch_add(1, Ordering::Relaxed) + std::process::id() as u16 } - pub fn restart(&mut self) -> anyhow::Result<()> { - self.process = Some( + pub fn restart(&mut self) -> Result<(), TestError> { + self.process = Some(TestServerProcess( Command::new(server_bin()?) .current_dir(&self.dir) .kill_on_drop(true) .spawn()?, - ); + )); Ok(()) } - pub async fn wait(&mut self) -> anyhow::Result<()> { + pub async fn wait(&mut self) -> Result<(), TestError> { if let Some(p) = self.process.as_mut() { - p.wait().await?; + p.0.wait().await?; } Ok(()) } - async fn shutdown_server(mut process: Child, mut address: String) -> anyhow::Result<()> { - if process.try_wait()?.is_some() { + async fn shutdown_server( + mut process: TestServerProcess, + mut address: String, + ) -> Result<(), TestError> { + if process.0.try_wait()?.is_some() { return Ok(()); } @@ -237,25 +319,25 @@ impl TestServerImpl { .await?; for _ in 0..SHUTDOWN_RETRY_ATTEMPTS { - if process.try_wait()?.is_some() { + if process.0.try_wait()?.is_some() { return Ok(()); } std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); } - process.kill().await?; + process.0.kill().await?; for _ in 0..SHUTDOWN_RETRY_ATTEMPTS { - if process.try_wait()?.is_some() { + if process.0.try_wait()?.is_some() { return Ok(()); } std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); } - anyhow::bail!("Failed to shutdown server") + Err(bail("Failed to shutdown server".to_string())) } - fn remove_dir_if_exists(dir: &str) -> anyhow::Result<()> { + fn remove_dir_if_exists(dir: &str) -> Result<(), TestError> { if Path::new(dir).exists() { std::fs::remove_dir_all(dir)?; } @@ -264,8 +346,9 @@ impl TestServerImpl { } } +#[cfg_attr(feature = "api", agdb::impl_def())] impl TestServer { - pub async fn new() -> anyhow::Result { + pub async fn new() -> Result { let global_server = SERVER.get_or_init(|| RwLock::new(Weak::new())); let mut server_guard = global_server.write().await; @@ -297,6 +380,7 @@ impl TestServer { } } +#[cfg_attr(feature = "api", agdb::impl_def())] impl Drop for TestServerImpl { fn drop(&mut self) { static DROP_RUNTIME: std::sync::OnceLock = @@ -327,8 +411,9 @@ impl Drop for TestServerImpl { } } +#[cfg_attr(feature = "api", agdb::impl_def())] impl TestCluster { - async fn new() -> anyhow::Result { + async fn new() -> Result { let global_cluster = CLUSTER.get_or_init(|| RwLock::new(Weak::new())); let mut cluster_guard = global_cluster.write().await; @@ -349,7 +434,7 @@ impl TestCluster { &s.address, )) }) - .collect::>>>()?, + .collect::>, TestError>>()?, _cluster: nodes, }; @@ -359,15 +444,18 @@ impl TestCluster { } } +#[cfg_attr(feature = "api", agdb::fn_def())] pub fn next_user_name() -> String { format!("db_user{}", COUNTER.fetch_add(1, Ordering::SeqCst)) } +#[cfg_attr(feature = "api", agdb::fn_def())] pub fn next_db_name() -> String { format!("db{}", COUNTER.fetch_add(1, Ordering::SeqCst)) } -pub async fn wait_for_ready(api: &AgdbApi) -> anyhow::Result<()> { +#[cfg_attr(feature = "api", agdb::fn_def())] +pub async fn wait_for_ready(api: &AgdbApi) -> Result<(), TestError> { for _ in 0..RETRY_ATTEMPS { if api.status().await.is_ok() { return Ok(()); @@ -376,10 +464,15 @@ pub async fn wait_for_ready(api: &AgdbApi) -> anyhow::Result<()> std::thread::sleep(RETRY_TIMEOUT); } - anyhow::bail!("Server not ready") + Err(TestError { + description: "Server not ready".to_string(), + }) } -pub async fn wait_for_leader(api: &AgdbApi) -> anyhow::Result> { +#[cfg_attr(feature = "api", agdb::fn_def())] +pub async fn wait_for_leader( + api: &AgdbApi, +) -> Result, TestError> { let now = Instant::now(); while now.elapsed().as_millis() < TEST_TIMEOUT { @@ -392,12 +485,13 @@ pub async fn wait_for_leader(api: &AgdbApi) -> anyhow::Result anyhow::Result> { +#[cfg_attr(feature = "api", agdb::fn_def())] +pub async fn create_cluster(nodes: usize, tls: bool) -> Result, TestError> { let mut configs = Vec::with_capacity(nodes); let mut cluster = Vec::with_capacity(nodes); let mut servers = Vec::with_capacity(nodes); From 641eb5cdb27230d0257065a3956b4ab4d3397e27 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Fri, 1 May 2026 20:20:29 +0200 Subject: [PATCH 3/8] update the test_server definition --- agdb/src/type_def.rs | 3 - agdb_api/rust/Cargo.toml | 5 +- agdb_api/rust/src/api_types.rs | 66 +------------ agdb_api/rust/src/api_types/config_impl.rs | 71 ++++++++++++++ agdb_api/rust/src/lib.rs | 8 +- agdb_api/rust/src/test_server.rs | 90 +++--------------- agdb_api/rust/src/test_server/test_error.rs | 72 ++++++++++++++ .../src/type_def_parser/expression_parser.rs | 2 +- agdb_server/src/config.rs | 93 ++++--------------- agdb_server/src/password.rs | 2 +- agdb_server/tests/routes/misc_routes.rs | 8 +- agdb_server/tests/test_server.rs | 8 +- 12 files changed, 195 insertions(+), 233 deletions(-) create mode 100644 agdb_api/rust/src/api_types/config_impl.rs create mode 100644 agdb_api/rust/src/test_server/test_error.rs diff --git a/agdb/src/type_def.rs b/agdb/src/type_def.rs index b569fe890..2ed71e3a2 100644 --- a/agdb/src/type_def.rs +++ b/agdb/src/type_def.rs @@ -277,8 +277,6 @@ impl TypeDefinition for std::sync::Arc { } } -impl ImplDefinition for std::sync::Arc {} - impl TypeDefinition for [T; N] { fn type_def() -> Type { Type::Slice(T::type_def) @@ -294,7 +292,6 @@ impl TypeDefinition for std::path::PathBuf { name: "inner", ty: Some(|| Type::Vec(Box::::type_def)), }], - functions: &[], }) } } diff --git a/agdb_api/rust/Cargo.toml b/agdb_api/rust/Cargo.toml index 4ca60785b..41f724b8a 100644 --- a/agdb_api/rust/Cargo.toml +++ b/agdb_api/rust/Cargo.toml @@ -16,7 +16,8 @@ categories = ["database", "api-bindings"] [features] default = [] tls = ["reqwest/default-tls"] -api = ["agdb/api", "dep:tokio", "dep:anyhow"] +api = ["agdb/api"] +test_server = ["dep:tokio"] [dependencies] agdb = { version = "0.12.10", path = "../../agdb", features = ["serde", "openapi"] } @@ -25,4 +26,4 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" utoipa = "5" tokio = { version = "1", features = ["full"], optional = true } -anyhow = { version = "1", optional = true} + diff --git a/agdb_api/rust/src/api_types.rs b/agdb_api/rust/src/api_types.rs index 919e0ca6f..49376e94c 100644 --- a/agdb_api/rust/src/api_types.rs +++ b/agdb_api/rust/src/api_types.rs @@ -1,3 +1,5 @@ +pub mod config_impl; + use agdb::DbError; use agdb::DbSerialize; use agdb::DbValue; @@ -8,35 +10,6 @@ use serde::Serialize; use std::fmt::Display; use utoipa::ToSchema; -pub const SALT_LEN: usize = 16; -pub const DEFAULT_LOG_BODY_LIMIT: u64 = 10 * 1024; -pub const DEFAULT_REQUEST_BODY_LIMIT: u64 = 10 * 1024 * 1024; - -#[derive(Debug)] -#[cfg_attr(feature = "api", derive(agdb::TypeDefImpl))] -pub struct ConfigImpl { - pub bind: String, - pub address: String, - pub basepath: String, - pub static_roots: Vec, - pub admin: String, - pub log_level: LogLevelFilter, - pub log_body_limit: u64, - pub request_body_limit: u64, - pub data_dir: String, - pub pepper_path: String, - pub tls_certificate: String, - pub tls_key: String, - pub tls_root: String, - pub cluster_token: String, - pub cluster_heartbeat_timeout_ms: u64, - pub cluster_term_timeout_ms: u64, - pub cluster: Vec, - pub cluster_node_id: usize, - pub start_time: u64, - pub pepper: Option<[u8; SALT_LEN]>, -} - #[derive( Debug, Default, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq, Eq, PartialOrd, Ord, )] @@ -344,41 +317,6 @@ impl TryFrom<&str> for LogLevelFilter { } } -pub fn config_to_str(config: &ConfigImpl) -> String { - let mut buffer = String::new(); - buffer.push_str(&format!("bind: {}\n", config.bind)); - buffer.push_str(&format!("address: {}\n", config.address)); - buffer.push_str(&format!("basepath: {}\n", config.basepath)); - buffer.push_str(&format!( - "static_roots: {}\n", - config.static_roots.join(", ") - )); - buffer.push_str(&format!("admin: {}\n", config.admin)); - buffer.push_str(&format!("log_level: {}\n", config.log_level)); - buffer.push_str(&format!("log_body_limit: {}\n", config.log_body_limit)); - buffer.push_str(&format!( - "request_body_limit: {}\n", - config.request_body_limit - )); - buffer.push_str(&format!("data_dir: {}\n", config.data_dir)); - buffer.push_str(&format!("pepper_path: {}\n", config.pepper_path)); - buffer.push_str(&format!("tls_certificate: {}\n", config.tls_certificate)); - buffer.push_str(&format!("tls_key: {}\n", config.tls_key)); - buffer.push_str(&format!("tls_root: {}\n", config.tls_root)); - buffer.push_str(&format!("cluster_token: {}\n", config.cluster_token)); - - buffer.push_str(&format!( - "cluster_heartbeat_timeout_ms: {}\n", - config.cluster_heartbeat_timeout_ms - )); - buffer.push_str(&format!( - "cluster_term_timeout_ms: {}\n", - config.cluster_term_timeout_ms - )); - buffer.push_str(&format!("cluster: [{}]\n", config.cluster.join(", "))); - buffer -} - #[cfg(test)] mod tests { use super::*; diff --git a/agdb_api/rust/src/api_types/config_impl.rs b/agdb_api/rust/src/api_types/config_impl.rs new file mode 100644 index 000000000..6538c961e --- /dev/null +++ b/agdb_api/rust/src/api_types/config_impl.rs @@ -0,0 +1,71 @@ +use crate::LogLevelFilter; + +pub const SALT_LEN: usize = 16; +pub const DEFAULT_LOG_BODY_LIMIT: u64 = 10 * 1024; +pub const DEFAULT_REQUEST_BODY_LIMIT: u64 = 10 * 1024 * 1024; + +#[derive(Debug)] +#[cfg_attr(feature = "api", derive(agdb::TypeDefImpl))] +pub struct ConfigImpl { + pub bind: String, + pub address: String, + pub basepath: String, + pub static_roots: Vec, + pub admin: String, + pub log_level: LogLevelFilter, + pub log_body_limit: u64, + pub request_body_limit: u64, + pub data_dir: String, + pub pepper_path: String, + pub tls_certificate: String, + pub tls_key: String, + pub tls_root: String, + pub cluster_token: String, + pub cluster_heartbeat_timeout_ms: u64, + pub cluster_term_timeout_ms: u64, + pub cluster: Vec, + pub cluster_node_id: usize, + pub start_time: u64, + pub pepper: Option<[u8; SALT_LEN]>, +} + +impl ConfigImpl { + pub fn server_url(&self) -> String { + format!("{}{}", self.address, self.basepath) + } +} + +pub fn config_to_str(config: &ConfigImpl) -> String { + let mut buffer = String::new(); + buffer.push_str(&format!("bind: {}\n", config.bind)); + buffer.push_str(&format!("address: {}\n", config.address)); + buffer.push_str(&format!("basepath: {}\n", config.basepath)); + buffer.push_str(&format!( + "static_roots: {}\n", + config.static_roots.join(", ") + )); + buffer.push_str(&format!("admin: {}\n", config.admin)); + buffer.push_str(&format!("log_level: {}\n", config.log_level)); + buffer.push_str(&format!("log_body_limit: {}\n", config.log_body_limit)); + buffer.push_str(&format!( + "request_body_limit: {}\n", + config.request_body_limit + )); + buffer.push_str(&format!("data_dir: {}\n", config.data_dir)); + buffer.push_str(&format!("pepper_path: {}\n", config.pepper_path)); + buffer.push_str(&format!("tls_certificate: {}\n", config.tls_certificate)); + buffer.push_str(&format!("tls_key: {}\n", config.tls_key)); + buffer.push_str(&format!("tls_root: {}\n", config.tls_root)); + buffer.push_str(&format!("cluster_token: {}\n", config.cluster_token)); + + buffer.push_str(&format!( + "cluster_heartbeat_timeout_ms: {}\n", + config.cluster_heartbeat_timeout_ms + )); + buffer.push_str(&format!( + "cluster_term_timeout_ms: {}\n", + config.cluster_term_timeout_ms + )); + buffer.push_str(&format!("cluster: [{}]\n", config.cluster.join(", "))); + buffer +} diff --git a/agdb_api/rust/src/lib.rs b/agdb_api/rust/src/lib.rs index dfa9bc1de..f524c8d54 100644 --- a/agdb_api/rust/src/lib.rs +++ b/agdb_api/rust/src/lib.rs @@ -5,7 +5,7 @@ mod api_result; mod api_types; mod client; mod http_client; -#[cfg(feature = "api")] +#[cfg(feature = "test_server")] pub mod test_server; pub use api_error::AgdbApiError; @@ -13,9 +13,6 @@ pub use api_result::AgdbApiResult; pub use api_types::AdminStatus; pub use api_types::ChangePassword; pub use api_types::ClusterStatus; -pub use api_types::ConfigImpl; -pub use api_types::DEFAULT_LOG_BODY_LIMIT; -pub use api_types::DEFAULT_REQUEST_BODY_LIMIT; pub use api_types::DbAudit; pub use api_types::DbKind; pub use api_types::DbResource; @@ -25,12 +22,11 @@ pub use api_types::LogLevelFilter; pub use api_types::Queries; pub use api_types::QueriesResults; pub use api_types::QueryAudit; -pub use api_types::SALT_LEN; pub use api_types::ServerDatabase; pub use api_types::UserCredentials; pub use api_types::UserLogin; pub use api_types::UserStatus; -pub use api_types::config_to_str; +pub use api_types::config_impl; pub use client::AgdbApi; pub use http_client::HttpClient; pub use http_client::ReqwestClient; diff --git a/agdb_api/rust/src/test_server.rs b/agdb_api/rust/src/test_server.rs index 46af2650d..9b9ea323f 100644 --- a/agdb_api/rust/src/test_server.rs +++ b/agdb_api/rust/src/test_server.rs @@ -1,15 +1,17 @@ +pub mod test_error; + use crate::AgdbApi; -use crate::AgdbApiError; use crate::ClusterStatus; -use crate::ConfigImpl; -use crate::DEFAULT_LOG_BODY_LIMIT; -use crate::DEFAULT_REQUEST_BODY_LIMIT; use crate::ReqwestClient; -use crate::config_to_str; +use crate::config_impl::ConfigImpl; +use crate::config_impl::DEFAULT_LOG_BODY_LIMIT; +use crate::config_impl::DEFAULT_REQUEST_BODY_LIMIT; +use crate::config_impl::config_to_str; +use crate::test_server::test_error::bail; +use crate::test_server::test_error::TestError; use agdb::type_def::Struct; use agdb::type_def::TypeDefinition; use std::collections::HashMap; -use std::env::VarError; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -43,61 +45,6 @@ static COUNTER: AtomicU16 = AtomicU16::new(1); static SERVER: std::sync::OnceLock>> = std::sync::OnceLock::new(); static CLUSTER: std::sync::OnceLock>> = std::sync::OnceLock::new(); -#[derive(Debug, agdb::TypeDefImpl)] -pub struct TestError { - description: String, -} - -#[cfg_attr(feature = "api", agdb::fn_def())] -fn bail(description: String) -> TestError { - TestError { description } -} - -impl From for TestError { - #[track_caller] - fn from(error: std::io::Error) -> Self { - TestError { - description: error.to_string(), - } - } -} - -impl From for TestError { - #[track_caller] - fn from(error: reqwest::Error) -> Self { - TestError { - description: error.to_string(), - } - } -} - -impl From for TestError { - #[track_caller] - fn from(error: AgdbApiError) -> Self { - TestError { - description: error.to_string(), - } - } -} - -impl From for TestError { - #[track_caller] - fn from(error: VarError) -> Self { - TestError { - description: error.to_string(), - } - } -} - -impl From for TestError { - #[track_caller] - fn from(error: tokio::task::JoinError) -> Self { - TestError { - description: error.to_string(), - } - } -} - pub struct TestServerProcess(pub Child); impl TypeDefinition for TestServerProcess { @@ -106,7 +53,6 @@ impl TypeDefinition for TestServerProcess { name: "TestServerProcess", generics: &[], fields: &[], - functions: &[], }) } } @@ -230,9 +176,7 @@ impl TestServerImpl { status = code.to_string() } - Err(bail(format!( - "Failed to start server '{api_address}' ({status})" - ))) + bail!("Failed to start server '{api_address}' ({status})") } pub async fn new() -> Result { @@ -334,7 +278,7 @@ impl TestServerImpl { std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); } - Err(bail("Failed to shutdown server".to_string())) + bail!("Failed to shutdown server") } fn remove_dir_if_exists(dir: &str) -> Result<(), TestError> { @@ -464,9 +408,7 @@ pub async fn wait_for_ready(api: &AgdbApi) -> Result<(), TestErro std::thread::sleep(RETRY_TIMEOUT); } - Err(TestError { - description: "Server not ready".to_string(), - }) + bail!("Server not ready") } #[cfg_attr(feature = "api", agdb::fn_def())] @@ -485,9 +427,7 @@ pub async fn wait_for_leader( std::thread::sleep(std::time::Duration::from_millis(POLL_INTERVAL)); } - Err(TestError { - description: format!("Leader not found within {TEST_TIMEOUT}seconds"), - }) + bail!("Leader not found within {TEST_TIMEOUT}seconds") } #[cfg_attr(feature = "api", agdb::fn_def())] @@ -571,11 +511,11 @@ pub async fn create_cluster(nodes: usize, tls: bool) -> Result anyhow::Result { + pub fn new() -> Result { let dir = format!("static_files_test{}", TestServerImpl::next_port()).into(); std::fs::create_dir_all(&dir)?; Ok(Self { dir }) diff --git a/agdb_api/rust/src/test_server/test_error.rs b/agdb_api/rust/src/test_server/test_error.rs new file mode 100644 index 000000000..ed8ac52ab --- /dev/null +++ b/agdb_api/rust/src/test_server/test_error.rs @@ -0,0 +1,72 @@ +use crate::AgdbApiError; +use std::env::VarError; + +#[derive(Debug, agdb::TypeDefImpl)] +pub struct TestError { + description: String, +} + +impl TestError { + pub(crate) fn new(description: impl Into) -> Self { + TestError { + description: description.into(), + } + } +} + +/// Early-returns an `Err(TestError)` from the current function. +/// Supports the same format-string syntax as `format!`. +macro_rules! bail { + ($fmt:literal $(,)?) => { + return Err(crate::test_server::test_error::TestError::new(format!($fmt))) + }; + ($fmt:literal, $($arg:tt)*) => { + return Err(crate::test_server::test_error::TestError::new(format!($fmt, $($arg)*))) + }; +} +pub(crate) use bail; + +impl From for TestError { + #[track_caller] + fn from(error: std::io::Error) -> Self { + TestError { + description: error.to_string(), + } + } +} + +impl From for TestError { + #[track_caller] + fn from(error: reqwest::Error) -> Self { + TestError { + description: error.to_string(), + } + } +} + +impl From for TestError { + #[track_caller] + fn from(error: AgdbApiError) -> Self { + TestError { + description: error.to_string(), + } + } +} + +impl From for TestError { + #[track_caller] + fn from(error: VarError) -> Self { + TestError { + description: error.to_string(), + } + } +} + +impl From for TestError { + #[track_caller] + fn from(error: tokio::task::JoinError) -> Self { + TestError { + description: error.to_string(), + } + } +} diff --git a/agdb_derive/src/type_def_parser/expression_parser.rs b/agdb_derive/src/type_def_parser/expression_parser.rs index 2f27ed2c2..dee2f34f1 100644 --- a/agdb_derive/src/type_def_parser/expression_parser.rs +++ b/agdb_derive/src/type_def_parser/expression_parser.rs @@ -744,7 +744,7 @@ fn parse_macro_by_name( // Common macros treated as function calls "panic" | "todo" | "unimplemented" | "println" | "eprintln" | "dbg" | "assert" | "assert_eq" | "assert_ne" | "debug_assert" | "debug_assert_eq" | "debug_assert_ne" - | "matches" | "unreachable" | "write" | "writeln" => { + | "matches" | "unreachable" | "write" | "writeln" | "bail" => { let macro_args = args.iter().map(|arg| parse_expression(arg, generics)); quote! { ::agdb::type_def::Expression::Call { diff --git a/agdb_server/src/config.rs b/agdb_server/src/config.rs index 4bf778c70..64a0ac3d2 100644 --- a/agdb_server/src/config.rs +++ b/agdb_server/src/config.rs @@ -1,4 +1,8 @@ use agdb_api::LogLevelFilter; +use agdb_api::config_impl::ConfigImpl; +use agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT; +use agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT; +use agdb_api::config_impl::SALT_LEN; use std::sync::Arc; use std::time::SystemTime; use std::time::UNIX_EPOCH; @@ -6,40 +10,6 @@ use tracing::warn; pub(crate) type Config = Arc; -pub(crate) const SALT_LEN: usize = 16; -pub(crate) const DEFAULT_LOG_BODY_LIMIT: u64 = 10 * 1024; -pub(crate) const DEFAULT_REQUEST_BODY_LIMIT: u64 = 10 * 1024 * 1024; - -#[derive(Debug)] -pub struct ConfigImpl { - pub(crate) bind: String, - pub(crate) address: String, - pub(crate) basepath: String, - pub(crate) static_roots: Vec, - pub(crate) admin: String, - pub(crate) log_level: LogLevelFilter, - pub(crate) log_body_limit: u64, - pub(crate) request_body_limit: u64, - pub(crate) data_dir: String, - pub(crate) pepper_path: String, - pub(crate) tls_certificate: String, - pub(crate) tls_key: String, - pub(crate) tls_root: String, - pub(crate) cluster_token: String, - pub(crate) cluster_heartbeat_timeout_ms: u64, - pub(crate) cluster_term_timeout_ms: u64, - pub(crate) cluster: Vec, - pub(crate) cluster_node_id: usize, - pub(crate) start_time: u64, - pub(crate) pepper: Option<[u8; SALT_LEN]>, -} - -impl ConfigImpl { - pub fn server_url(&self) -> String { - format!("{}{}", self.address, self.basepath) - } -} - pub(crate) fn new(config_file: &str) -> Result { if let Ok(content) = std::fs::read_to_string(config_file) { let mut config_impl: ConfigImpl = from_str(&content)?; @@ -117,7 +87,7 @@ pub(crate) fn new(config_file: &str) -> Result { pepper: None, }; - std::fs::write(config_file, to_str(&config)) + std::fs::write(config_file, agdb_api::config_impl::config_to_str(&config)) .map_err(|e| format!("Failed to write config file '{}': {e:?}", config_file))?; Ok(Config::new(config)) @@ -249,41 +219,6 @@ fn normalize_address(config: &mut ConfigImpl) { } } -pub(crate) fn to_str(config: &ConfigImpl) -> String { - let mut buffer = String::new(); - buffer.push_str(&format!("bind: {}\n", config.bind)); - buffer.push_str(&format!("address: {}\n", config.address)); - buffer.push_str(&format!("basepath: {}\n", config.basepath)); - buffer.push_str(&format!( - "static_roots: {}\n", - config.static_roots.join(", ") - )); - buffer.push_str(&format!("admin: {}\n", config.admin)); - buffer.push_str(&format!("log_level: {}\n", config.log_level)); - buffer.push_str(&format!("log_body_limit: {}\n", config.log_body_limit)); - buffer.push_str(&format!( - "request_body_limit: {}\n", - config.request_body_limit - )); - buffer.push_str(&format!("data_dir: {}\n", config.data_dir)); - buffer.push_str(&format!("pepper_path: {}\n", config.pepper_path)); - buffer.push_str(&format!("tls_certificate: {}\n", config.tls_certificate)); - buffer.push_str(&format!("tls_key: {}\n", config.tls_key)); - buffer.push_str(&format!("tls_root: {}\n", config.tls_root)); - buffer.push_str(&format!("cluster_token: {}\n", config.cluster_token)); - - buffer.push_str(&format!( - "cluster_heartbeat_timeout_ms: {}\n", - config.cluster_heartbeat_timeout_ms - )); - buffer.push_str(&format!( - "cluster_term_timeout_ms: {}\n", - config.cluster_term_timeout_ms - )); - buffer.push_str(&format!("cluster: [{}]\n", config.cluster.join(", "))); - buffer -} - #[cfg(test)] mod tests { use super::*; @@ -344,7 +279,11 @@ mod tests { pepper: None, }; - std::fs::write(test_file.filename, to_str(&config)).unwrap(); + std::fs::write( + test_file.filename, + agdb_api::config_impl::config_to_str(&config), + ) + .unwrap(); let config = config::new(test_file.filename).unwrap(); @@ -376,7 +315,11 @@ mod tests { start_time: 0, pepper: None, }; - std::fs::write(test_file.filename, to_str(&config)).unwrap(); + std::fs::write( + test_file.filename, + agdb_api::config_impl::config_to_str(&config), + ) + .unwrap(); config::new(test_file.filename).unwrap_err(); } @@ -408,7 +351,11 @@ mod tests { start_time: 0, pepper: None, }; - std::fs::write(test_file.filename, to_str(&config)).unwrap(); + std::fs::write( + test_file.filename, + agdb_api::config_impl::config_to_str(&config), + ) + .unwrap(); config::new(test_file.filename).unwrap_err(); } diff --git a/agdb_server/src/password.rs b/agdb_server/src/password.rs index 68403929d..6f5844d85 100644 --- a/agdb_server/src/password.rs +++ b/agdb_server/src/password.rs @@ -1,6 +1,6 @@ use crate::error_code::ErrorCode; use crate::server_error::ServerResult; -use agdb_api::SALT_LEN; +use agdb_api::config_impl::SALT_LEN; use ring::digest; use ring::pbkdf2; use ring::rand::SecureRandom; diff --git a/agdb_server/tests/routes/misc_routes.rs b/agdb_server/tests/routes/misc_routes.rs index ed583a8b1..11592029f 100644 --- a/agdb_server/tests/routes/misc_routes.rs +++ b/agdb_server/tests/routes/misc_routes.rs @@ -155,7 +155,7 @@ async fn basepath_test() -> anyhow::Result<()> { use crate::DEFAULT_LOG_BODY_LIMIT; use crate::DEFAULT_REQUEST_BODY_LIMIT; - let config = agdb_api::ConfigImpl { + let config = agdb_api::config_impl::ConfigImpl { bind: String::new(), address: String::new(), basepath: "/public".to_string(), @@ -329,7 +329,7 @@ async fn studio() -> anyhow::Result<()> { #[tokio::test] async fn large_payload() -> anyhow::Result<()> { - let config = agdb_api::ConfigImpl { + let config = agdb_api::config_impl::ConfigImpl { bind: String::new(), address: String::new(), basepath: String::new(), @@ -408,7 +408,7 @@ async fn static_files() -> anyhow::Result<()> { let test_dir1 = TestDir::new()?; let test_dir2 = TestDir::new()?; - let config = agdb_api::ConfigImpl { + let config = agdb_api::config_impl::ConfigImpl { bind: String::new(), address: String::new(), basepath: String::new(), @@ -473,7 +473,7 @@ async fn static_files_with_basepath() -> anyhow::Result<()> { let test_dir1 = TestDir::new()?; let test_dir2 = TestDir::new()?; - let config = agdb_api::ConfigImpl { + let config = agdb_api::config_impl::ConfigImpl { bind: String::new(), address: String::new(), basepath: "/some_basepath".to_string(), diff --git a/agdb_server/tests/test_server.rs b/agdb_server/tests/test_server.rs index dc1741981..116814435 100644 --- a/agdb_server/tests/test_server.rs +++ b/agdb_server/tests/test_server.rs @@ -4,10 +4,10 @@ mod tls; use agdb_api::AgdbApi; use agdb_api::ClusterStatus; -use agdb_api::ConfigImpl; -use agdb_api::DEFAULT_LOG_BODY_LIMIT; -use agdb_api::DEFAULT_REQUEST_BODY_LIMIT; use agdb_api::ReqwestClient; +use agdb_api::config_impl::ConfigImpl; +use agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT; +use agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -120,7 +120,7 @@ impl TestServerImpl { std::fs::write( Path::new(&dir).join(CONFIG_FILE), - agdb_api::config_to_str(&config), + agdb_api::config_impl::config_to_str(&config), )?; let api_address = if config.basepath.is_empty() { From d671f8d05f4dbe8cb96aae4e7cd52c1d4e4ede0f Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Fri, 1 May 2026 20:32:25 +0200 Subject: [PATCH 4/8] use test_server from agdb_api --- agdb_api/rust/src/test_server.rs | 17 +- agdb_api/rust/src/test_server/test_error.rs | 8 + agdb_server/Cargo.toml | 1 + agdb_server/tests/routes/admin_db_add_test.rs | 8 +- .../tests/routes/admin_db_audit_test.rs | 8 +- .../routes/admin_db_backup_restore_test.rs | 8 +- .../tests/routes/admin_db_clear_test.rs | 8 +- .../tests/routes/admin_db_convert_test.rs | 8 +- .../tests/routes/admin_db_copy_test.rs | 10 +- .../tests/routes/admin_db_delete_test.rs | 8 +- .../tests/routes/admin_db_exec_test.rs | 8 +- .../tests/routes/admin_db_list_test.rs | 8 +- .../tests/routes/admin_db_optimize_test.rs | 8 +- .../tests/routes/admin_db_remove_test.rs | 8 +- .../tests/routes/admin_db_rename_test.rs | 8 +- .../tests/routes/admin_db_user_add_test.rs | 8 +- .../tests/routes/admin_db_user_list_test.rs | 8 +- .../tests/routes/admin_db_user_remove_test.rs | 8 +- .../tests/routes/admin_set_log_level_test.rs | 10 +- agdb_server/tests/routes/admin_status_test.rs | 6 +- .../tests/routes/admin_user_add_test.rs | 6 +- .../routes/admin_user_change_password_test.rs | 6 +- .../tests/routes/admin_user_delete_test.rs | 8 +- .../tests/routes/admin_user_list_test.rs | 6 +- .../tests/routes/admin_user_logout_all.rs | 10 +- .../tests/routes/admin_user_logout_test.rs | 6 +- agdb_server/tests/routes/cluster_test.rs | 18 +- agdb_server/tests/routes/db_add_test.rs | 8 +- agdb_server/tests/routes/db_audit_test.rs | 8 +- .../tests/routes/db_backup_restore_test.rs | 8 +- agdb_server/tests/routes/db_clear_test.rs | 8 +- agdb_server/tests/routes/db_convert_test.rs | 8 +- agdb_server/tests/routes/db_copy_test.rs | 8 +- agdb_server/tests/routes/db_delete_test.rs | 8 +- agdb_server/tests/routes/db_exec_test.rs | 8 +- agdb_server/tests/routes/db_list_test.rs | 8 +- agdb_server/tests/routes/db_optimize_test.rs | 8 +- agdb_server/tests/routes/db_remove_test.rs | 8 +- agdb_server/tests/routes/db_rename_test.rs | 8 +- agdb_server/tests/routes/db_user_add_test.rs | 8 +- agdb_server/tests/routes/db_user_list.rs | 8 +- .../tests/routes/db_user_remove_test.rs | 8 +- agdb_server/tests/routes/misc_routes.rs | 41 +- .../tests/routes/user_change_password_test.rs | 6 +- agdb_server/tests/routes/user_login_test.rs | 8 +- agdb_server/tests/routes/user_logout_test.rs | 6 +- agdb_server/tests/routes/user_status.rs | 6 +- agdb_server/tests/test_server.rs | 509 ------------------ agdb_server/tests/tests.rs | 3 + agdb_server/tests/tls/mod.rs | 23 +- 50 files changed, 223 insertions(+), 723 deletions(-) delete mode 100644 agdb_server/tests/test_server.rs create mode 100644 agdb_server/tests/tests.rs diff --git a/agdb_api/rust/src/test_server.rs b/agdb_api/rust/src/test_server.rs index 9b9ea323f..1632989b4 100644 --- a/agdb_api/rust/src/test_server.rs +++ b/agdb_api/rust/src/test_server.rs @@ -7,8 +7,8 @@ use crate::config_impl::ConfigImpl; use crate::config_impl::DEFAULT_LOG_BODY_LIMIT; use crate::config_impl::DEFAULT_REQUEST_BODY_LIMIT; use crate::config_impl::config_to_str; -use crate::test_server::test_error::bail; use crate::test_server::test_error::TestError; +use crate::test_server::test_error::bail; use agdb::type_def::Struct; use agdb::type_def::TypeDefinition; use std::collections::HashMap; @@ -24,15 +24,16 @@ use tokio::process::Child; use tokio::process::Command; use tokio::sync::RwLock; -const ADMIN: &str = "admin"; +pub const ADMIN: &str = "admin"; +pub const CONFIG_FILE: &str = "agdb_server.yaml"; +pub const SERVER_DATA_DIR: &str = "agdb_server_data"; + const BINARY: &str = "agdb_server"; -const CONFIG_FILE: &str = "agdb_server.yaml"; const DEFAULT_PORT: u16 = 3000; const HOST: &str = "localhost"; const POLL_INTERVAL: u64 = 100; const RETRY_TIMEOUT: Duration = Duration::from_secs(1); const RETRY_ATTEMPS: u16 = 10; -const SERVER_DATA_DIR: &str = "agdb_server_data"; const SHUTDOWN_RETRY_TIMEOUT: Duration = Duration::from_millis(100); const SHUTDOWN_RETRY_ATTEMPTS: u16 = 100; const TEST_TIMEOUT: u128 = 30000; @@ -83,8 +84,8 @@ pub struct TestServerImpl { #[derive(agdb::TypeDef)] pub struct TestCluster { - apis: Vec>, - _cluster: Arc, + pub apis: Vec>, + pub cluster: Arc, } #[cfg(feature = "tls")] @@ -357,7 +358,7 @@ impl Drop for TestServerImpl { #[cfg_attr(feature = "api", agdb::impl_def())] impl TestCluster { - async fn new() -> Result { + pub async fn new() -> Result { let global_cluster = CLUSTER.get_or_init(|| RwLock::new(Weak::new())); let mut cluster_guard = global_cluster.write().await; @@ -379,7 +380,7 @@ impl TestCluster { )) }) .collect::>, TestError>>()?, - _cluster: nodes, + cluster: nodes, }; cluster.apis[1].cluster_user_login(ADMIN, ADMIN).await?; diff --git a/agdb_api/rust/src/test_server/test_error.rs b/agdb_api/rust/src/test_server/test_error.rs index ed8ac52ab..cd4c7af90 100644 --- a/agdb_api/rust/src/test_server/test_error.rs +++ b/agdb_api/rust/src/test_server/test_error.rs @@ -14,6 +14,14 @@ impl TestError { } } +impl std::fmt::Display for TestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.description) + } +} + +impl std::error::Error for TestError {} + /// Early-returns an `Err(TestError)` from the current function. /// Supports the same format-string syntax as `format!`. macro_rules! bail { diff --git a/agdb_server/Cargo.toml b/agdb_server/Cargo.toml index cc126b872..244dfb686 100644 --- a/agdb_server/Cargo.toml +++ b/agdb_server/Cargo.toml @@ -39,4 +39,5 @@ utoipa-rapidoc = { version = "6", features = ["axum"] } uuid = { version = "1", features = ["v4"] } [dev-dependencies] +agdb_api = { version = "0.12.10", path = "../agdb_api/rust", features = ["test_server"] } anyhow = "1" diff --git a/agdb_server/tests/routes/admin_db_add_test.rs b/agdb_server/tests/routes/admin_db_add_test.rs index 02580675e..b7dd242a1 100644 --- a/agdb_server/tests/routes/admin_db_add_test.rs +++ b/agdb_server/tests/routes/admin_db_add_test.rs @@ -1,8 +1,8 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; use agdb_api::DbKind; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_audit_test.rs b/agdb_server/tests/routes/admin_db_audit_test.rs index 6091d2742..d3944a9e1 100644 --- a/agdb_server/tests/routes/admin_db_audit_test.rs +++ b/agdb_server/tests/routes/admin_db_audit_test.rs @@ -1,9 +1,9 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; +use agdb_api::test_server::ADMIN; #[tokio::test] async fn admin_audit() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_backup_restore_test.rs b/agdb_server/tests/routes/admin_db_backup_restore_test.rs index 4398e7733..a7568f4f2 100644 --- a/agdb_server/tests/routes/admin_db_backup_restore_test.rs +++ b/agdb_server/tests/routes/admin_db_backup_restore_test.rs @@ -1,12 +1,12 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; use agdb::QueryResult; use agdb_api::DbKind; +use agdb_api::test_server::ADMIN; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_clear_test.rs b/agdb_server/tests/routes/admin_db_clear_test.rs index 2b8566c0e..5c55bc4f8 100644 --- a/agdb_server/tests/routes/admin_db_clear_test.rs +++ b/agdb_server/tests/routes/admin_db_clear_test.rs @@ -1,11 +1,11 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::DbResource; use agdb_api::DbUserRole; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_convert_test.rs b/agdb_server/tests/routes/admin_db_convert_test.rs index f31887ade..fc5a2a05a 100644 --- a/agdb_server/tests/routes/admin_db_convert_test.rs +++ b/agdb_server/tests/routes/admin_db_convert_test.rs @@ -1,8 +1,8 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; #[tokio::test] async fn memory_to_mapped() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_copy_test.rs b/agdb_server/tests/routes/admin_db_copy_test.rs index 577003de0..5d8f988c3 100644 --- a/agdb_server/tests/routes/admin_db_copy_test.rs +++ b/agdb_server/tests/routes/admin_db_copy_test.rs @@ -1,12 +1,12 @@ -use crate::ADMIN; -use crate::TestCluster; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; use agdb_api::DbKind; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestCluster; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_delete_test.rs b/agdb_server/tests/routes/admin_db_delete_test.rs index e8a0a090a..fe7a53da7 100644 --- a/agdb_server/tests/routes/admin_db_delete_test.rs +++ b/agdb_server/tests/routes/admin_db_delete_test.rs @@ -1,8 +1,8 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; use agdb_api::DbKind; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_exec_test.rs b/agdb_server/tests/routes/admin_db_exec_test.rs index c99687776..a667279ba 100644 --- a/agdb_server/tests/routes/admin_db_exec_test.rs +++ b/agdb_server/tests/routes/admin_db_exec_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; diff --git a/agdb_server/tests/routes/admin_db_list_test.rs b/agdb_server/tests/routes/admin_db_list_test.rs index c3c9ef745..879ba2b44 100644 --- a/agdb_server/tests/routes/admin_db_list_test.rs +++ b/agdb_server/tests/routes/admin_db_list_test.rs @@ -1,10 +1,10 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; use agdb_api::ServerDatabase; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn db_list() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_optimize_test.rs b/agdb_server/tests/routes/admin_db_optimize_test.rs index 3f1ace82e..4d6760180 100644 --- a/agdb_server/tests/routes/admin_db_optimize_test.rs +++ b/agdb_server/tests/routes/admin_db_optimize_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; diff --git a/agdb_server/tests/routes/admin_db_remove_test.rs b/agdb_server/tests/routes/admin_db_remove_test.rs index 044ce2242..c3534a687 100644 --- a/agdb_server/tests/routes/admin_db_remove_test.rs +++ b/agdb_server/tests/routes/admin_db_remove_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use std::path::Path; diff --git a/agdb_server/tests/routes/admin_db_rename_test.rs b/agdb_server/tests/routes/admin_db_rename_test.rs index ef6593ae5..81f5d7e49 100644 --- a/agdb_server/tests/routes/admin_db_rename_test.rs +++ b/agdb_server/tests/routes/admin_db_rename_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use std::path::Path; diff --git a/agdb_server/tests/routes/admin_db_user_add_test.rs b/agdb_server/tests/routes/admin_db_user_add_test.rs index 351c5c243..f3c4a62b6 100644 --- a/agdb_server/tests/routes/admin_db_user_add_test.rs +++ b/agdb_server/tests/routes/admin_db_user_add_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; use agdb_api::ServerDatabase; diff --git a/agdb_server/tests/routes/admin_db_user_list_test.rs b/agdb_server/tests/routes/admin_db_user_list_test.rs index db8033031..4895e81a7 100644 --- a/agdb_server/tests/routes/admin_db_user_list_test.rs +++ b/agdb_server/tests/routes/admin_db_user_list_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUser; use agdb_api::DbUserRole; diff --git a/agdb_server/tests/routes/admin_db_user_remove_test.rs b/agdb_server/tests/routes/admin_db_user_remove_test.rs index 2f4174c52..cb2475ff5 100644 --- a/agdb_server/tests/routes/admin_db_user_remove_test.rs +++ b/agdb_server/tests/routes/admin_db_user_remove_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; diff --git a/agdb_server/tests/routes/admin_set_log_level_test.rs b/agdb_server/tests/routes/admin_set_log_level_test.rs index ea5f82b9d..fb5b391c4 100644 --- a/agdb_server/tests/routes/admin_set_log_level_test.rs +++ b/agdb_server/tests/routes/admin_set_log_level_test.rs @@ -1,10 +1,10 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::TestServerImpl; -use crate::next_user_name; -use crate::reqwest_client; use agdb_api::AgdbApi; use agdb_api::ReqwestClient; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::TestServerImpl; +use agdb_api::test_server::next_user_name; +use agdb_api::test_server::reqwest_client; #[tokio::test] async fn set_log_level() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_status_test.rs b/agdb_server/tests/routes/admin_status_test.rs index c0c893372..3d44d4396 100644 --- a/agdb_server/tests/routes/admin_status_test.rs +++ b/agdb_server/tests/routes/admin_status_test.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_user_add_test.rs b/agdb_server/tests/routes/admin_user_add_test.rs index c38705ad2..b99032aa3 100644 --- a/agdb_server/tests/routes/admin_user_add_test.rs +++ b/agdb_server/tests/routes/admin_user_add_test.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn add() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_user_change_password_test.rs b/agdb_server/tests/routes/admin_user_change_password_test.rs index 5f875728c..86c87df30 100644 --- a/agdb_server/tests/routes/admin_user_change_password_test.rs +++ b/agdb_server/tests/routes/admin_user_change_password_test.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn change_password() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_user_delete_test.rs b/agdb_server/tests/routes/admin_user_delete_test.rs index 5e67abc2d..66ef5b43b 100644 --- a/agdb_server/tests/routes/admin_user_delete_test.rs +++ b/agdb_server/tests/routes/admin_user_delete_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; use std::path::Path; diff --git a/agdb_server/tests/routes/admin_user_list_test.rs b/agdb_server/tests/routes/admin_user_list_test.rs index dc0fd971d..ea3bc02ae 100644 --- a/agdb_server/tests/routes/admin_user_list_test.rs +++ b/agdb_server/tests/routes/admin_user_list_test.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; use agdb_api::UserStatus; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_user_logout_all.rs b/agdb_server/tests/routes/admin_user_logout_all.rs index 59665c59a..bf8100c99 100644 --- a/agdb_server/tests/routes/admin_user_logout_all.rs +++ b/agdb_server/tests/routes/admin_user_logout_all.rs @@ -1,10 +1,10 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::TestServerImpl; -use crate::next_user_name; -use crate::reqwest_client; use agdb_api::AgdbApi; use agdb_api::ReqwestClient; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::TestServerImpl; +use agdb_api::test_server::next_user_name; +use agdb_api::test_server::reqwest_client; #[tokio::test] async fn logout_all() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_user_logout_test.rs b/agdb_server/tests/routes/admin_user_logout_test.rs index 8e8fed43a..735008df0 100644 --- a/agdb_server/tests/routes/admin_user_logout_test.rs +++ b/agdb_server/tests/routes/admin_user_logout_test.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn logout() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/cluster_test.rs b/agdb_server/tests/routes/cluster_test.rs index 32dd8fb3e..0b52fc5a4 100644 --- a/agdb_server/tests/routes/cluster_test.rs +++ b/agdb_server/tests/routes/cluster_test.rs @@ -1,12 +1,3 @@ -use crate::ADMIN; -use crate::TestCluster; -use crate::TestServer; -use crate::create_cluster; -use crate::next_db_name; -use crate::next_user_name; -use crate::reqwest_client; -use crate::wait_for_leader; -use crate::wait_for_ready; use agdb::Comparison; use agdb::QueryBuilder; use agdb_api::AgdbApi; @@ -14,6 +5,15 @@ use agdb_api::DbKind; use agdb_api::DbResource; use agdb_api::DbUserRole; use agdb_api::ReqwestClient; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestCluster; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::create_cluster; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; +use agdb_api::test_server::reqwest_client; +use agdb_api::test_server::wait_for_leader; +use agdb_api::test_server::wait_for_ready; #[tokio::test] async fn rebalance() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/db_add_test.rs b/agdb_server/tests/routes/db_add_test.rs index a000dc4ff..4f69117b5 100644 --- a/agdb_server/tests/routes/db_add_test.rs +++ b/agdb_server/tests/routes/db_add_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use std::path::Path; diff --git a/agdb_server/tests/routes/db_audit_test.rs b/agdb_server/tests/routes/db_audit_test.rs index 6f4e9b6ca..edcf48af6 100644 --- a/agdb_server/tests/routes/db_audit_test.rs +++ b/agdb_server/tests/routes/db_audit_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; use std::path::Path; diff --git a/agdb_server/tests/routes/db_backup_restore_test.rs b/agdb_server/tests/routes/db_backup_restore_test.rs index 3aae385ae..d2fff11dd 100644 --- a/agdb_server/tests/routes/db_backup_restore_test.rs +++ b/agdb_server/tests/routes/db_backup_restore_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; diff --git a/agdb_server/tests/routes/db_clear_test.rs b/agdb_server/tests/routes/db_clear_test.rs index fefcb36e1..dfdda5ecd 100644 --- a/agdb_server/tests/routes/db_clear_test.rs +++ b/agdb_server/tests/routes/db_clear_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::DbResource; diff --git a/agdb_server/tests/routes/db_convert_test.rs b/agdb_server/tests/routes/db_convert_test.rs index 9269872c0..af87264a5 100644 --- a/agdb_server/tests/routes/db_convert_test.rs +++ b/agdb_server/tests/routes/db_convert_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::DbUserRole; diff --git a/agdb_server/tests/routes/db_copy_test.rs b/agdb_server/tests/routes/db_copy_test.rs index 436133257..302cf6e73 100644 --- a/agdb_server/tests/routes/db_copy_test.rs +++ b/agdb_server/tests/routes/db_copy_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; diff --git a/agdb_server/tests/routes/db_delete_test.rs b/agdb_server/tests/routes/db_delete_test.rs index 3b44f1ab2..bfc213a0a 100644 --- a/agdb_server/tests/routes/db_delete_test.rs +++ b/agdb_server/tests/routes/db_delete_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; use std::path::Path; diff --git a/agdb_server/tests/routes/db_exec_test.rs b/agdb_server/tests/routes/db_exec_test.rs index 42b7bd7c8..7f6e158a8 100644 --- a/agdb_server/tests/routes/db_exec_test.rs +++ b/agdb_server/tests/routes/db_exec_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; diff --git a/agdb_server/tests/routes/db_list_test.rs b/agdb_server/tests/routes/db_list_test.rs index 0654b0de1..f438b49ad 100644 --- a/agdb_server/tests/routes/db_list_test.rs +++ b/agdb_server/tests/routes/db_list_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; use agdb_api::ServerDatabase; diff --git a/agdb_server/tests/routes/db_optimize_test.rs b/agdb_server/tests/routes/db_optimize_test.rs index 1a4e4602d..c18904bde 100644 --- a/agdb_server/tests/routes/db_optimize_test.rs +++ b/agdb_server/tests/routes/db_optimize_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::DbUserRole; diff --git a/agdb_server/tests/routes/db_remove_test.rs b/agdb_server/tests/routes/db_remove_test.rs index 978a07367..3dab9caab 100644 --- a/agdb_server/tests/routes/db_remove_test.rs +++ b/agdb_server/tests/routes/db_remove_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; use std::path::Path; diff --git a/agdb_server/tests/routes/db_rename_test.rs b/agdb_server/tests/routes/db_rename_test.rs index 27e78afaf..e26d2ea30 100644 --- a/agdb_server/tests/routes/db_rename_test.rs +++ b/agdb_server/tests/routes/db_rename_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; use std::path::Path; diff --git a/agdb_server/tests/routes/db_user_add_test.rs b/agdb_server/tests/routes/db_user_add_test.rs index 0c8117339..71a80a231 100644 --- a/agdb_server/tests/routes/db_user_add_test.rs +++ b/agdb_server/tests/routes/db_user_add_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; use agdb_api::ServerDatabase; diff --git a/agdb_server/tests/routes/db_user_list.rs b/agdb_server/tests/routes/db_user_list.rs index 139101ef5..2f4c9158f 100644 --- a/agdb_server/tests/routes/db_user_list.rs +++ b/agdb_server/tests/routes/db_user_list.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUser; use agdb_api::DbUserRole; diff --git a/agdb_server/tests/routes/db_user_remove_test.rs b/agdb_server/tests/routes/db_user_remove_test.rs index ca0ef2f9d..1b92a68a4 100644 --- a/agdb_server/tests/routes/db_user_remove_test.rs +++ b/agdb_server/tests/routes/db_user_remove_test.rs @@ -1,7 +1,7 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_db_name; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::DbUserRole; diff --git a/agdb_server/tests/routes/misc_routes.rs b/agdb_server/tests/routes/misc_routes.rs index 11592029f..fd53a0e54 100644 --- a/agdb_server/tests/routes/misc_routes.rs +++ b/agdb_server/tests/routes/misc_routes.rs @@ -1,16 +1,15 @@ -use crate::ADMIN; -use crate::CONFIG_FILE; -use crate::DEFAULT_LOG_BODY_LIMIT; -use crate::TestDir; -use crate::TestServer; -use crate::TestServerImpl; -use crate::next_db_name; -use crate::reqwest_client; -use crate::wait_for_ready; use agdb::QueryBuilder; use agdb_api::AgdbApi; use agdb_api::DbKind; use agdb_api::ReqwestClient; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::CONFIG_FILE; +use agdb_api::test_server::TestDir; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::TestServerImpl; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::reqwest_client; +use agdb_api::test_server::wait_for_ready; use reqwest::StatusCode; use std::path::Path; @@ -152,8 +151,8 @@ async fn db_list_after_shutdown_corrupted_data() -> anyhow::Result<()> { #[cfg(feature = "studio")] #[tokio::test] async fn basepath_test() -> anyhow::Result<()> { - use crate::DEFAULT_LOG_BODY_LIMIT; - use crate::DEFAULT_REQUEST_BODY_LIMIT; + use agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT; + use agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT; let config = agdb_api::config_impl::ConfigImpl { bind: String::new(), @@ -164,7 +163,7 @@ async fn basepath_test() -> anyhow::Result<()> { log_level: agdb_api::LogLevelFilter::Info, log_body_limit: DEFAULT_LOG_BODY_LIMIT, request_body_limit: DEFAULT_REQUEST_BODY_LIMIT, - data_dir: crate::SERVER_DATA_DIR.into(), + data_dir: agdb_api::test_server::SERVER_DATA_DIR.into(), pepper_path: String::new(), tls_certificate: String::new(), tls_key: String::new(), @@ -336,9 +335,9 @@ async fn large_payload() -> anyhow::Result<()> { static_roots: Vec::new(), admin: ADMIN.to_string(), log_level: agdb_api::LogLevelFilter::Info, - log_body_limit: DEFAULT_LOG_BODY_LIMIT, - request_body_limit: 1024, - data_dir: crate::SERVER_DATA_DIR.into(), + log_body_limit: agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT, + request_body_limit: agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT, + data_dir: agdb_api::test_server::SERVER_DATA_DIR.into(), pepper_path: String::new(), tls_certificate: String::new(), tls_key: String::new(), @@ -424,9 +423,9 @@ async fn static_files() -> anyhow::Result<()> { ], admin: ADMIN.to_string(), log_level: agdb_api::LogLevelFilter::Info, - log_body_limit: DEFAULT_LOG_BODY_LIMIT, - request_body_limit: 1024, - data_dir: crate::SERVER_DATA_DIR.into(), + log_body_limit: agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT, + request_body_limit: agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT, + data_dir: agdb_api::test_server::SERVER_DATA_DIR.into(), pepper_path: String::new(), tls_certificate: String::new(), tls_key: String::new(), @@ -489,9 +488,9 @@ async fn static_files_with_basepath() -> anyhow::Result<()> { ], admin: ADMIN.to_string(), log_level: agdb_api::LogLevelFilter::Info, - log_body_limit: DEFAULT_LOG_BODY_LIMIT, - request_body_limit: 1024, - data_dir: crate::SERVER_DATA_DIR.into(), + log_body_limit: agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT, + request_body_limit: agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT, + data_dir: agdb_api::test_server::SERVER_DATA_DIR.into(), pepper_path: String::new(), tls_certificate: String::new(), tls_key: String::new(), diff --git a/agdb_server/tests/routes/user_change_password_test.rs b/agdb_server/tests/routes/user_change_password_test.rs index 8eec9d531..be9410479 100644 --- a/agdb_server/tests/routes/user_change_password_test.rs +++ b/agdb_server/tests/routes/user_change_password_test.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn change_password() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/user_login_test.rs b/agdb_server/tests/routes/user_login_test.rs index 3f1e8aa46..d619d9a13 100644 --- a/agdb_server/tests/routes/user_login_test.rs +++ b/agdb_server/tests/routes/user_login_test.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn login() -> anyhow::Result<()> { @@ -74,7 +74,7 @@ async fn concurrent_logins() -> anyhow::Result<()> { for _ in 0..3 { apis.push(( agdb_api::AgdbApi::new( - agdb_api::ReqwestClient::with_client(crate::reqwest_client()), + agdb_api::ReqwestClient::with_client(agdb_api::test_server::reqwest_client()), server.api.address(), ), user.to_string(), diff --git a/agdb_server/tests/routes/user_logout_test.rs b/agdb_server/tests/routes/user_logout_test.rs index 44e6b24f1..ea48c9485 100644 --- a/agdb_server/tests/routes/user_logout_test.rs +++ b/agdb_server/tests/routes/user_logout_test.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn logout() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/user_status.rs b/agdb_server/tests/routes/user_status.rs index 2a1624519..e6be9d0f2 100644 --- a/agdb_server/tests/routes/user_status.rs +++ b/agdb_server/tests/routes/user_status.rs @@ -1,6 +1,6 @@ -use crate::ADMIN; -use crate::TestServer; -use crate::next_user_name; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn user() -> anyhow::Result<()> { diff --git a/agdb_server/tests/test_server.rs b/agdb_server/tests/test_server.rs deleted file mode 100644 index 116814435..000000000 --- a/agdb_server/tests/test_server.rs +++ /dev/null @@ -1,509 +0,0 @@ -mod routes; -#[cfg(feature = "tls")] -mod tls; - -use agdb_api::AgdbApi; -use agdb_api::ClusterStatus; -use agdb_api::ReqwestClient; -use agdb_api::config_impl::ConfigImpl; -use agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT; -use agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT; -use std::collections::HashMap; -use std::path::Path; -use std::path::PathBuf; -use std::sync::Arc; -use std::sync::Weak; -use std::sync::atomic::AtomicU16; -use std::sync::atomic::Ordering; -use std::time::Duration; -use std::time::Instant; -use tokio::process::Child; -use tokio::process::Command; -use tokio::sync::RwLock; - -const ADMIN: &str = "admin"; -const BINARY: &str = "agdb_server"; -const CONFIG_FILE: &str = "agdb_server.yaml"; -const DEFAULT_PORT: u16 = 3000; -const HOST: &str = "localhost"; -const POLL_INTERVAL: u64 = 100; -const RETRY_TIMEOUT: Duration = Duration::from_secs(1); -const RETRY_ATTEMPS: u16 = 10; -const SERVER_DATA_DIR: &str = "agdb_server_data"; -const SHUTDOWN_RETRY_TIMEOUT: Duration = Duration::from_millis(100); -const SHUTDOWN_RETRY_ATTEMPTS: u16 = 100; -const TEST_TIMEOUT: u128 = 30000; -const CLIENT_TIMEOUT: Duration = Duration::from_secs(30); - -type ClusterImpl = Vec; - -static PORT: AtomicU16 = AtomicU16::new(DEFAULT_PORT); -static COUNTER: AtomicU16 = AtomicU16::new(1); -static SERVER: std::sync::OnceLock>> = std::sync::OnceLock::new(); -static CLUSTER: std::sync::OnceLock>> = std::sync::OnceLock::new(); - -fn server_bin() -> anyhow::Result { - let mut path = std::env::current_exe()?; - path.pop(); - path.pop(); - Ok(path.join(format!("{BINARY}{}", std::env::consts::EXE_SUFFIX))) -} - -pub struct TestServer { - pub dir: String, - pub data_dir: String, - pub api: AgdbApi, - pub server: Arc, -} - -pub struct TestServerImpl { - pub dir: String, - pub data_dir: String, - pub address: String, - pub process: Option, -} - -pub struct TestCluster { - apis: Vec>, - _cluster: Arc, -} - -#[cfg(feature = "tls")] -pub fn root_ca() -> reqwest::Certificate { - static ROOT_CA: std::sync::OnceLock = std::sync::OnceLock::new(); - - ROOT_CA - .get_or_init(|| { - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let root_ca_buf = - std::fs::read(format!("{manifest_dir}/tests/test_root_ca.pem")).unwrap(); - reqwest::Certificate::from_pem(&root_ca_buf).unwrap() - }) - .clone() -} - -#[cfg(feature = "tls")] -pub fn reqwest_client() -> reqwest::Client { - reqwest::Client::builder() - .add_root_certificate(root_ca()) - .use_rustls_tls() - .timeout(CLIENT_TIMEOUT) - .build() - .unwrap() -} - -#[cfg(not(feature = "tls"))] -pub fn reqwest_client() -> reqwest::Client { - reqwest::Client::builder() - .timeout(CLIENT_TIMEOUT) - .build() - .unwrap() -} - -impl TestServerImpl { - pub async fn with_config(mut config: ConfigImpl) -> anyhow::Result { - if config.address.is_empty() { - let port = Self::next_port(); - let address = format!("http://{HOST}:{port}"); - config.bind = format!("{HOST}:{port}"); - config.address = address; - }; - - let dir = format!( - "{BINARY}.{}.test", - config.address.split(':').next_back().unwrap() - ); - let data_dir = format!("{dir}/{SERVER_DATA_DIR}"); - - Self::remove_dir_if_exists(&dir)?; - std::fs::create_dir(&dir)?; - - std::fs::write( - Path::new(&dir).join(CONFIG_FILE), - agdb_api::config_impl::config_to_str(&config), - )?; - - let api_address = if config.basepath.is_empty() { - config.address.clone() - } else { - format!("{}{}", config.address, config.basepath) - }; - - let mut process = Command::new(server_bin()?) - .current_dir(&dir) - .kill_on_drop(true) - .spawn()?; - let api = AgdbApi::new(ReqwestClient::with_client(reqwest_client()), &api_address); - - for _ in 0..RETRY_ATTEMPS { - match api.status().await { - Ok(200) => { - return Ok(Self { - dir, - data_dir, - address: api_address, - process: Some(process), - }); - } - Ok(status) => println!("Server at {api_address} is not ready: {status}"), - Err(e) => println!("Failed to contact server at {api_address}: {e:?}"), - } - - std::thread::sleep(RETRY_TIMEOUT); - } - - let mut status = "running".to_string(); - if let Ok(Some(s)) = process.try_wait() - && let Some(code) = s.code() - { - status = code.to_string() - } - - anyhow::bail!("Failed to start server '{api_address}' ({status})") - } - - pub async fn new() -> anyhow::Result { - let config = ConfigImpl { - bind: String::new(), - address: String::new(), - basepath: String::new(), - static_roots: Vec::new(), - admin: ADMIN.to_string(), - log_level: agdb_api::LogLevelFilter::Info, - log_body_limit: DEFAULT_LOG_BODY_LIMIT, - request_body_limit: DEFAULT_REQUEST_BODY_LIMIT, - data_dir: SERVER_DATA_DIR.into(), - pepper_path: String::new(), - tls_certificate: String::new(), - tls_key: String::new(), - tls_root: String::new(), - cluster_token: "test".to_string(), - cluster_heartbeat_timeout_ms: 1000, - cluster_term_timeout_ms: 3000, - cluster: Vec::new(), - cluster_node_id: 0, - start_time: 0, - pepper: None, - }; - - Self::with_config(config).await - } - - pub fn next_port() -> u16 { - PORT.fetch_add(1, Ordering::Relaxed) + std::process::id() as u16 - } - - pub fn restart(&mut self) -> anyhow::Result<()> { - self.process = Some( - Command::new(server_bin()?) - .current_dir(&self.dir) - .kill_on_drop(true) - .spawn()?, - ); - Ok(()) - } - - pub async fn wait(&mut self) -> anyhow::Result<()> { - if let Some(p) = self.process.as_mut() { - p.wait().await?; - } - - Ok(()) - } - - async fn shutdown_server(mut process: Child, mut address: String) -> anyhow::Result<()> { - if process.try_wait()?.is_some() { - return Ok(()); - } - - if !address.starts_with("http") { - address = format!("http://{address}"); - } - - let mut admin = HashMap::<&str, String>::new(); - admin.insert("username", ADMIN.to_string()); - admin.insert("password", ADMIN.to_string()); - - let client = reqwest_client(); - - let token: String = client - .post(format!("{address}/api/v1/user/login")) - .json(&admin) - .timeout(CLIENT_TIMEOUT) - .send() - .await? - .json() - .await?; - - client - .post(format!("{address}/api/v1/admin/shutdown")) - .timeout(CLIENT_TIMEOUT) - .bearer_auth(token) - .send() - .await?; - - for _ in 0..SHUTDOWN_RETRY_ATTEMPTS { - if process.try_wait()?.is_some() { - return Ok(()); - } - std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); - } - - process.kill().await?; - - for _ in 0..SHUTDOWN_RETRY_ATTEMPTS { - if process.try_wait()?.is_some() { - return Ok(()); - } - std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); - } - - anyhow::bail!("Failed to shutdown server") - } - - fn remove_dir_if_exists(dir: &str) -> anyhow::Result<()> { - if Path::new(dir).exists() { - std::fs::remove_dir_all(dir)?; - } - - Ok(()) - } -} - -impl TestServer { - pub async fn new() -> anyhow::Result { - let global_server = SERVER.get_or_init(|| RwLock::new(Weak::new())); - let mut server_guard = global_server.write().await; - - let server = if let Some(server) = server_guard.upgrade() { - server - } else { - let server = Arc::new(TestServerImpl::new().await?); - *server_guard = Arc::downgrade(&server); - server - }; - - Ok(Self { - api: AgdbApi::new( - ReqwestClient::with_client(reqwest_client()), - &server.address, - ), - dir: server.dir.clone(), - data_dir: server.data_dir.clone(), - server, - }) - } - - pub fn url(&self, uri: &str) -> String { - format!("{}{uri}", self.api.address()) - } - - pub fn full_url(&self, uri: &str) -> String { - self.api.base_url().to_string() + uri - } -} - -impl Drop for TestServerImpl { - fn drop(&mut self) { - static DROP_RUNTIME: std::sync::OnceLock = - std::sync::OnceLock::new(); - - if let Some(p) = self.process.take() { - let address = self.address.clone(); - let dir = self.dir.clone(); - - let f = DROP_RUNTIME - .get_or_init(|| tokio::runtime::Runtime::new().unwrap()) - .spawn(async move { - let _ = Self::shutdown_server(p, address) - .await - .inspect_err(|e| println!("{e:?}")); - }); - - for _ in 0..SHUTDOWN_RETRY_ATTEMPTS { - if f.is_finished() { - break; - } - - std::thread::sleep(SHUTDOWN_RETRY_TIMEOUT); - } - - let _ = Self::remove_dir_if_exists(&dir).inspect_err(|e| println!("{e:?}")); - } - } -} - -impl TestCluster { - async fn new() -> anyhow::Result { - let global_cluster = CLUSTER.get_or_init(|| RwLock::new(Weak::new())); - let mut cluster_guard = global_cluster.write().await; - - let nodes = if let Some(nodes) = cluster_guard.upgrade() { - nodes - } else { - let nodes = Arc::new(create_cluster(3, false).await?); - *cluster_guard = Arc::downgrade(&nodes); - nodes - }; - - let mut cluster = Self { - apis: nodes - .iter() - .map(|s| { - Ok(AgdbApi::new( - ReqwestClient::with_client(reqwest_client()), - &s.address, - )) - }) - .collect::>>>()?, - _cluster: nodes, - }; - - cluster.apis[1].cluster_user_login(ADMIN, ADMIN).await?; - - Ok(cluster) - } -} - -pub fn next_user_name() -> String { - format!("db_user{}", COUNTER.fetch_add(1, Ordering::SeqCst)) -} - -pub fn next_db_name() -> String { - format!("db{}", COUNTER.fetch_add(1, Ordering::SeqCst)) -} - -pub async fn wait_for_ready(api: &AgdbApi) -> anyhow::Result<()> { - for _ in 0..RETRY_ATTEMPS { - if api.status().await.is_ok() { - return Ok(()); - } - - std::thread::sleep(RETRY_TIMEOUT); - } - - anyhow::bail!("Server not ready") -} - -pub async fn wait_for_leader(api: &AgdbApi) -> anyhow::Result> { - let now = Instant::now(); - - while now.elapsed().as_millis() < TEST_TIMEOUT { - let status = api.cluster_status().await?; - - if status.1.iter().any(|s| s.leader) { - return Ok(status.1); - } - - std::thread::sleep(std::time::Duration::from_millis(POLL_INTERVAL)); - } - - Err(anyhow::anyhow!( - "Leader not found within {TEST_TIMEOUT}seconds" - )) -} - -pub async fn create_cluster(nodes: usize, tls: bool) -> anyhow::Result> { - let mut configs = Vec::with_capacity(nodes); - let mut cluster = Vec::with_capacity(nodes); - let mut servers = Vec::with_capacity(nodes); - let protocol = if tls { "https" } else { "http" }; - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?; - let tls_cert = if tls { - format!("{manifest_dir}/tests/test_cert.pem") - } else { - String::new() - }; - let tls_key = if tls { - format!("{manifest_dir}/tests/test_cert.key.pem") - } else { - String::new() - }; - let tls_root = if tls { - format!("{manifest_dir}/tests/test_root_ca.pem") - } else { - String::new() - }; - - for _ in 0..nodes { - let port = TestServerImpl::next_port(); - let config = ConfigImpl { - bind: format!("{HOST}:{port}"), - address: format!("{protocol}://{HOST}:{port}"), - basepath: String::new(), - static_roots: Vec::new(), - admin: ADMIN.to_string(), - log_level: agdb_api::LogLevelFilter::Info, - log_body_limit: DEFAULT_LOG_BODY_LIMIT, - request_body_limit: DEFAULT_REQUEST_BODY_LIMIT, - data_dir: SERVER_DATA_DIR.into(), - pepper_path: String::new(), - tls_certificate: tls_cert.clone(), - tls_key: tls_key.clone(), - tls_root: tls_root.clone(), - cluster_token: "test".to_string(), - cluster_heartbeat_timeout_ms: 1000, - cluster_term_timeout_ms: 3000, - cluster: Vec::new(), - cluster_node_id: 0, - start_time: 0, - pepper: None, - }; - - configs.push(config); - cluster.push(format!("{protocol}://{HOST}:{port}")); - } - - for config in &mut configs { - config.cluster = cluster.clone(); - } - - for server in configs - .into_iter() - .map(|c| tokio::spawn(async move { TestServerImpl::with_config(c).await })) - { - let server = server.await??; - let api = AgdbApi::new( - ReqwestClient::with_client(reqwest_client()), - &server.address, - ); - servers.push((server, api)); - } - - let mut statuses = Vec::with_capacity(nodes); - - for server in &servers { - statuses.push(wait_for_leader(&server.1).await?); - } - - for status in &statuses[1..] { - assert_eq!(statuses[0], *status); - } - - let leader = statuses[0] - .iter() - .enumerate() - .find_map(|(i, s)| if s.leader { Some(i) } else { None }) - .unwrap(); - servers.swap(0, leader); - - Ok(servers.into_iter().map(|(s, _)| s).collect()) -} - -pub struct TestDir { - pub dir: PathBuf, -} - -impl TestDir { - pub fn new() -> anyhow::Result { - let dir = format!("static_files_test{}", TestServerImpl::next_port()).into(); - std::fs::create_dir_all(&dir)?; - Ok(Self { dir }) - } -} - -impl Drop for TestDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.dir); - } -} diff --git a/agdb_server/tests/tests.rs b/agdb_server/tests/tests.rs new file mode 100644 index 000000000..631fc7dfb --- /dev/null +++ b/agdb_server/tests/tests.rs @@ -0,0 +1,3 @@ +mod routes; +#[cfg(feature = "tls")] +mod tls; diff --git a/agdb_server/tests/tls/mod.rs b/agdb_server/tests/tls/mod.rs index fc936768a..ae1f13b64 100644 --- a/agdb_server/tests/tls/mod.rs +++ b/agdb_server/tests/tls/mod.rs @@ -1,15 +1,12 @@ -use crate::ADMIN; -use crate::ConfigImpl; -use crate::DEFAULT_LOG_BODY_LIMIT; -use crate::DEFAULT_REQUEST_BODY_LIMIT; -use crate::SERVER_DATA_DIR; -use crate::TestServerImpl; -use crate::create_cluster; -use crate::reqwest_client; -use crate::wait_for_leader; -use crate::wait_for_ready; use agdb_api::AgdbApi; use agdb_api::ReqwestClient; +use agdb_api::config_impl::ConfigImpl; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServerImpl; +use agdb_api::test_server::create_cluster; +use agdb_api::test_server::reqwest_client; +use agdb_api::test_server::wait_for_leader; +use agdb_api::test_server::wait_for_ready; #[tokio::test] async fn https() -> anyhow::Result<()> { @@ -22,9 +19,9 @@ async fn https() -> anyhow::Result<()> { static_roots: Vec::new(), admin: ADMIN.to_string(), log_level: agdb_api::LogLevelFilter::Info, - log_body_limit: DEFAULT_LOG_BODY_LIMIT, - request_body_limit: DEFAULT_REQUEST_BODY_LIMIT, - data_dir: SERVER_DATA_DIR.into(), + log_body_limit: agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT, + request_body_limit: agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT, + data_dir: agdb_api::test_server::SERVER_DATA_DIR.into(), pepper_path: String::new(), tls_certificate: format!("{manifest_dir}/tests/test_cert.pem"), tls_key: format!("{manifest_dir}/tests/test_cert.key.pem"), From 2b194d1fefe0a724977754a80827ac69708643b2 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Fri, 1 May 2026 20:36:12 +0200 Subject: [PATCH 5/8] Update misc_routes.rs --- agdb_server/tests/routes/misc_routes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agdb_server/tests/routes/misc_routes.rs b/agdb_server/tests/routes/misc_routes.rs index fd53a0e54..2e3b4574c 100644 --- a/agdb_server/tests/routes/misc_routes.rs +++ b/agdb_server/tests/routes/misc_routes.rs @@ -336,7 +336,7 @@ async fn large_payload() -> anyhow::Result<()> { admin: ADMIN.to_string(), log_level: agdb_api::LogLevelFilter::Info, log_body_limit: agdb_api::config_impl::DEFAULT_LOG_BODY_LIMIT, - request_body_limit: agdb_api::config_impl::DEFAULT_REQUEST_BODY_LIMIT, + request_body_limit: 1024, data_dir: agdb_api::test_server::SERVER_DATA_DIR.into(), pepper_path: String::new(), tls_certificate: String::new(), From 635811de08de10515fb4aedbc8149072293374c4 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Fri, 1 May 2026 20:36:37 +0200 Subject: [PATCH 6/8] fix formatting --- agdb_server/tests/routes/admin_db_audit_test.rs | 6 +++--- agdb_server/tests/routes/admin_db_backup_restore_test.rs | 6 +++--- agdb_server/tests/routes/admin_db_clear_test.rs | 4 ++-- agdb_server/tests/routes/admin_db_convert_test.rs | 2 +- agdb_server/tests/routes/admin_db_exec_test.rs | 8 ++++---- agdb_server/tests/routes/admin_db_optimize_test.rs | 4 ++-- agdb_server/tests/routes/admin_db_remove_test.rs | 2 +- agdb_server/tests/routes/admin_db_rename_test.rs | 2 +- agdb_server/tests/routes/admin_db_user_add_test.rs | 6 +++--- agdb_server/tests/routes/admin_db_user_list_test.rs | 6 +++--- agdb_server/tests/routes/admin_db_user_remove_test.rs | 4 ++-- agdb_server/tests/routes/admin_status_test.rs | 2 +- agdb_server/tests/routes/admin_user_delete_test.rs | 4 ++-- agdb_server/tests/routes/admin_user_list_test.rs | 2 +- agdb_server/tests/routes/db_add_test.rs | 2 +- agdb_server/tests/routes/db_audit_test.rs | 4 ++-- agdb_server/tests/routes/db_backup_restore_test.rs | 8 ++++---- agdb_server/tests/routes/db_clear_test.rs | 8 ++++---- agdb_server/tests/routes/db_convert_test.rs | 6 +++--- agdb_server/tests/routes/db_copy_test.rs | 8 ++++---- agdb_server/tests/routes/db_delete_test.rs | 4 ++-- agdb_server/tests/routes/db_exec_test.rs | 8 ++++---- agdb_server/tests/routes/db_list_test.rs | 6 +++--- agdb_server/tests/routes/db_optimize_test.rs | 6 +++--- agdb_server/tests/routes/db_remove_test.rs | 4 ++-- agdb_server/tests/routes/db_rename_test.rs | 4 ++-- agdb_server/tests/routes/db_user_add_test.rs | 6 +++--- agdb_server/tests/routes/db_user_list.rs | 6 +++--- agdb_server/tests/routes/db_user_remove_test.rs | 4 ++-- 29 files changed, 71 insertions(+), 71 deletions(-) diff --git a/agdb_server/tests/routes/admin_db_audit_test.rs b/agdb_server/tests/routes/admin_db_audit_test.rs index d3944a9e1..f51798f0c 100644 --- a/agdb_server/tests/routes/admin_db_audit_test.rs +++ b/agdb_server/tests/routes/admin_db_audit_test.rs @@ -1,9 +1,9 @@ -use agdb_api::test_server::TestServer; -use agdb_api::test_server::next_db_name; -use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn admin_audit() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_backup_restore_test.rs b/agdb_server/tests/routes/admin_db_backup_restore_test.rs index a7568f4f2..7547cec0c 100644 --- a/agdb_server/tests/routes/admin_db_backup_restore_test.rs +++ b/agdb_server/tests/routes/admin_db_backup_restore_test.rs @@ -1,12 +1,12 @@ -use agdb_api::test_server::TestServer; -use agdb_api::test_server::next_db_name; -use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; use agdb::QueryResult; use agdb_api::DbKind; use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_clear_test.rs b/agdb_server/tests/routes/admin_db_clear_test.rs index 5c55bc4f8..e3904df8b 100644 --- a/agdb_server/tests/routes/admin_db_clear_test.rs +++ b/agdb_server/tests/routes/admin_db_clear_test.rs @@ -1,11 +1,11 @@ -use agdb_api::test_server::next_db_name; -use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::DbResource; use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_convert_test.rs b/agdb_server/tests/routes/admin_db_convert_test.rs index fc5a2a05a..0cf3665a2 100644 --- a/agdb_server/tests/routes/admin_db_convert_test.rs +++ b/agdb_server/tests/routes/admin_db_convert_test.rs @@ -1,8 +1,8 @@ -use agdb_api::test_server::next_user_name; use agdb_api::DbKind; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn memory_to_mapped() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_exec_test.rs b/agdb_server/tests/routes/admin_db_exec_test.rs index a667279ba..41d174a80 100644 --- a/agdb_server/tests/routes/admin_db_exec_test.rs +++ b/agdb_server/tests/routes/admin_db_exec_test.rs @@ -1,12 +1,12 @@ -use agdb_api::test_server::ADMIN; -use agdb_api::test_server::TestServer; -use agdb_api::test_server::next_db_name; -use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; use agdb::QueryResult; use agdb_api::DbKind; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn read_write() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_optimize_test.rs b/agdb_server/tests/routes/admin_db_optimize_test.rs index 4d6760180..cfd7aaeb7 100644 --- a/agdb_server/tests/routes/admin_db_optimize_test.rs +++ b/agdb_server/tests/routes/admin_db_optimize_test.rs @@ -1,9 +1,9 @@ +use agdb::QueryBuilder; +use agdb_api::DbKind; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb::QueryBuilder; -use agdb_api::DbKind; #[tokio::test] async fn optimize() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_remove_test.rs b/agdb_server/tests/routes/admin_db_remove_test.rs index c3534a687..b0ce711bb 100644 --- a/agdb_server/tests/routes/admin_db_remove_test.rs +++ b/agdb_server/tests/routes/admin_db_remove_test.rs @@ -1,8 +1,8 @@ +use agdb_api::DbKind; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_rename_test.rs b/agdb_server/tests/routes/admin_db_rename_test.rs index 81f5d7e49..7b48d858d 100644 --- a/agdb_server/tests/routes/admin_db_rename_test.rs +++ b/agdb_server/tests/routes/admin_db_rename_test.rs @@ -1,8 +1,8 @@ +use agdb_api::DbKind; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_db_user_add_test.rs b/agdb_server/tests/routes/admin_db_user_add_test.rs index f3c4a62b6..a028a8121 100644 --- a/agdb_server/tests/routes/admin_db_user_add_test.rs +++ b/agdb_server/tests/routes/admin_db_user_add_test.rs @@ -1,10 +1,10 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; +use agdb_api::ServerDatabase; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; -use agdb_api::ServerDatabase; #[tokio::test] async fn db_user_add() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_user_list_test.rs b/agdb_server/tests/routes/admin_db_user_list_test.rs index 4895e81a7..c7f157e71 100644 --- a/agdb_server/tests/routes/admin_db_user_list_test.rs +++ b/agdb_server/tests/routes/admin_db_user_list_test.rs @@ -1,10 +1,10 @@ +use agdb_api::DbKind; +use agdb_api::DbUser; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUser; -use agdb_api::DbUserRole; #[tokio::test] async fn list() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_db_user_remove_test.rs b/agdb_server/tests/routes/admin_db_user_remove_test.rs index cb2475ff5..8e63f1588 100644 --- a/agdb_server/tests/routes/admin_db_user_remove_test.rs +++ b/agdb_server/tests/routes/admin_db_user_remove_test.rs @@ -1,9 +1,9 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; #[tokio::test] async fn remove() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_status_test.rs b/agdb_server/tests/routes/admin_status_test.rs index 3d44d4396..6742c0c74 100644 --- a/agdb_server/tests/routes/admin_status_test.rs +++ b/agdb_server/tests/routes/admin_status_test.rs @@ -1,7 +1,7 @@ +use agdb_api::DbKind; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; #[tokio::test] async fn status() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/admin_user_delete_test.rs b/agdb_server/tests/routes/admin_user_delete_test.rs index 66ef5b43b..d32433767 100644 --- a/agdb_server/tests/routes/admin_user_delete_test.rs +++ b/agdb_server/tests/routes/admin_user_delete_test.rs @@ -1,9 +1,9 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/admin_user_list_test.rs b/agdb_server/tests/routes/admin_user_list_test.rs index ea3bc02ae..df25bd303 100644 --- a/agdb_server/tests/routes/admin_user_list_test.rs +++ b/agdb_server/tests/routes/admin_user_list_test.rs @@ -1,7 +1,7 @@ +use agdb_api::UserStatus; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_user_name; -use agdb_api::UserStatus; #[tokio::test] async fn user_list() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/db_add_test.rs b/agdb_server/tests/routes/db_add_test.rs index 4f69117b5..f4a63a053 100644 --- a/agdb_server/tests/routes/db_add_test.rs +++ b/agdb_server/tests/routes/db_add_test.rs @@ -1,8 +1,8 @@ +use agdb_api::DbKind; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/db_audit_test.rs b/agdb_server/tests/routes/db_audit_test.rs index edcf48af6..f1c3db4c8 100644 --- a/agdb_server/tests/routes/db_audit_test.rs +++ b/agdb_server/tests/routes/db_audit_test.rs @@ -1,9 +1,9 @@ +use agdb::QueryBuilder; +use agdb_api::DbKind; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb::QueryBuilder; -use agdb_api::DbKind; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/db_backup_restore_test.rs b/agdb_server/tests/routes/db_backup_restore_test.rs index d2fff11dd..b6b9d9cb8 100644 --- a/agdb_server/tests/routes/db_backup_restore_test.rs +++ b/agdb_server/tests/routes/db_backup_restore_test.rs @@ -1,13 +1,13 @@ -use agdb_api::test_server::ADMIN; -use agdb_api::test_server::TestServer; -use agdb_api::test_server::next_db_name; -use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; use agdb::QueryResult; use agdb_api::DbKind; use agdb_api::DbUserRole; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/db_clear_test.rs b/agdb_server/tests/routes/db_clear_test.rs index dfdda5ecd..8ffd8c9ba 100644 --- a/agdb_server/tests/routes/db_clear_test.rs +++ b/agdb_server/tests/routes/db_clear_test.rs @@ -1,11 +1,11 @@ -use agdb_api::test_server::ADMIN; -use agdb_api::test_server::TestServer; -use agdb_api::test_server::next_db_name; -use agdb_api::test_server::next_user_name; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::DbResource; use agdb_api::DbUserRole; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/db_convert_test.rs b/agdb_server/tests/routes/db_convert_test.rs index af87264a5..abe3036fd 100644 --- a/agdb_server/tests/routes/db_convert_test.rs +++ b/agdb_server/tests/routes/db_convert_test.rs @@ -1,10 +1,10 @@ +use agdb::QueryBuilder; +use agdb_api::DbKind; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb::QueryBuilder; -use agdb_api::DbKind; -use agdb_api::DbUserRole; #[tokio::test] async fn memory_to_mapped() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/db_copy_test.rs b/agdb_server/tests/routes/db_copy_test.rs index 302cf6e73..72d45e1cd 100644 --- a/agdb_server/tests/routes/db_copy_test.rs +++ b/agdb_server/tests/routes/db_copy_test.rs @@ -1,12 +1,12 @@ -use agdb_api::test_server::ADMIN; -use agdb_api::test_server::TestServer; -use agdb_api::test_server::next_db_name; -use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::DbUserRole; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/db_delete_test.rs b/agdb_server/tests/routes/db_delete_test.rs index bfc213a0a..554dd0a41 100644 --- a/agdb_server/tests/routes/db_delete_test.rs +++ b/agdb_server/tests/routes/db_delete_test.rs @@ -1,9 +1,9 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/db_exec_test.rs b/agdb_server/tests/routes/db_exec_test.rs index 7f6e158a8..0ddf20dbb 100644 --- a/agdb_server/tests/routes/db_exec_test.rs +++ b/agdb_server/tests/routes/db_exec_test.rs @@ -1,13 +1,13 @@ -use agdb_api::test_server::ADMIN; -use agdb_api::test_server::TestServer; -use agdb_api::test_server::next_db_name; -use agdb_api::test_server::next_user_name; use agdb::DbElement; use agdb::DbId; use agdb::QueryBuilder; use agdb::QueryResult; use agdb_api::DbKind; use agdb_api::DbUserRole; +use agdb_api::test_server::ADMIN; +use agdb_api::test_server::TestServer; +use agdb_api::test_server::next_db_name; +use agdb_api::test_server::next_user_name; #[tokio::test] async fn read_write() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/db_list_test.rs b/agdb_server/tests/routes/db_list_test.rs index f438b49ad..ba2c1d153 100644 --- a/agdb_server/tests/routes/db_list_test.rs +++ b/agdb_server/tests/routes/db_list_test.rs @@ -1,10 +1,10 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; +use agdb_api::ServerDatabase; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; -use agdb_api::ServerDatabase; #[tokio::test] async fn list() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/db_optimize_test.rs b/agdb_server/tests/routes/db_optimize_test.rs index c18904bde..601a716fd 100644 --- a/agdb_server/tests/routes/db_optimize_test.rs +++ b/agdb_server/tests/routes/db_optimize_test.rs @@ -1,10 +1,10 @@ +use agdb::QueryBuilder; +use agdb_api::DbKind; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb::QueryBuilder; -use agdb_api::DbKind; -use agdb_api::DbUserRole; #[tokio::test] async fn optimize() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/db_remove_test.rs b/agdb_server/tests/routes/db_remove_test.rs index 3dab9caab..afdcdfcd9 100644 --- a/agdb_server/tests/routes/db_remove_test.rs +++ b/agdb_server/tests/routes/db_remove_test.rs @@ -1,9 +1,9 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/db_rename_test.rs b/agdb_server/tests/routes/db_rename_test.rs index e26d2ea30..7eb429799 100644 --- a/agdb_server/tests/routes/db_rename_test.rs +++ b/agdb_server/tests/routes/db_rename_test.rs @@ -1,9 +1,9 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/db_user_add_test.rs b/agdb_server/tests/routes/db_user_add_test.rs index 71a80a231..aac84c47a 100644 --- a/agdb_server/tests/routes/db_user_add_test.rs +++ b/agdb_server/tests/routes/db_user_add_test.rs @@ -1,10 +1,10 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; +use agdb_api::ServerDatabase; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; -use agdb_api::ServerDatabase; #[tokio::test] async fn add_db_user() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/db_user_list.rs b/agdb_server/tests/routes/db_user_list.rs index 2f4c9158f..b1219b662 100644 --- a/agdb_server/tests/routes/db_user_list.rs +++ b/agdb_server/tests/routes/db_user_list.rs @@ -1,10 +1,10 @@ +use agdb_api::DbKind; +use agdb_api::DbUser; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUser; -use agdb_api::DbUserRole; #[tokio::test] async fn list() -> anyhow::Result<()> { diff --git a/agdb_server/tests/routes/db_user_remove_test.rs b/agdb_server/tests/routes/db_user_remove_test.rs index 1b92a68a4..e5cfa1037 100644 --- a/agdb_server/tests/routes/db_user_remove_test.rs +++ b/agdb_server/tests/routes/db_user_remove_test.rs @@ -1,9 +1,9 @@ +use agdb_api::DbKind; +use agdb_api::DbUserRole; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; -use agdb_api::DbKind; -use agdb_api::DbUserRole; #[tokio::test] async fn remove() -> anyhow::Result<()> { From 17599ca670ce100515be6be64cb71d66529348e2 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Fri, 1 May 2026 20:58:59 +0200 Subject: [PATCH 7/8] update based on PR comments --- agdb/src/type_def.rs | 2 +- agdb_api/rust/src/api_types/config_impl.rs | 1 + agdb_api/rust/src/test_server.rs | 231 +++--------------- agdb_api/rust/src/test_server/test_cluster.rs | 166 +++++++++++++ agdb_api/rust/src/test_server/test_dir.rs | 21 ++ .../tests/routes/admin_db_copy_test.rs | 2 +- agdb_server/tests/routes/cluster_test.rs | 6 +- agdb_server/tests/routes/misc_routes.rs | 2 +- agdb_server/tests/tls/mod.rs | 4 +- 9 files changed, 228 insertions(+), 207 deletions(-) create mode 100644 agdb_api/rust/src/test_server/test_cluster.rs create mode 100644 agdb_api/rust/src/test_server/test_dir.rs diff --git a/agdb/src/type_def.rs b/agdb/src/type_def.rs index 2ed71e3a2..d0e1dec92 100644 --- a/agdb/src/type_def.rs +++ b/agdb/src/type_def.rs @@ -290,7 +290,7 @@ impl TypeDefinition for std::path::PathBuf { generics: &[], fields: &[Variable { name: "inner", - ty: Some(|| Type::Vec(Box::::type_def)), + ty: Some(|| Type::Vec(u8::type_def)), }], }) } diff --git a/agdb_api/rust/src/api_types/config_impl.rs b/agdb_api/rust/src/api_types/config_impl.rs index 6538c961e..6fb0aa933 100644 --- a/agdb_api/rust/src/api_types/config_impl.rs +++ b/agdb_api/rust/src/api_types/config_impl.rs @@ -35,6 +35,7 @@ impl ConfigImpl { } } +#[cfg_attr(feature = "api", agdb::fn_def())] pub fn config_to_str(config: &ConfigImpl) -> String { let mut buffer = String::new(); buffer.push_str(&format!("bind: {}\n", config.bind)); diff --git a/agdb_api/rust/src/test_server.rs b/agdb_api/rust/src/test_server.rs index 1632989b4..6c238dce4 100644 --- a/agdb_api/rust/src/test_server.rs +++ b/agdb_api/rust/src/test_server.rs @@ -1,7 +1,8 @@ +pub mod test_cluster; +pub mod test_dir; pub mod test_error; use crate::AgdbApi; -use crate::ClusterStatus; use crate::ReqwestClient; use crate::config_impl::ConfigImpl; use crate::config_impl::DEFAULT_LOG_BODY_LIMIT; @@ -9,8 +10,6 @@ use crate::config_impl::DEFAULT_REQUEST_BODY_LIMIT; use crate::config_impl::config_to_str; use crate::test_server::test_error::TestError; use crate::test_server::test_error::bail; -use agdb::type_def::Struct; -use agdb::type_def::TypeDefinition; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -19,7 +18,6 @@ use std::sync::Weak; use std::sync::atomic::AtomicU16; use std::sync::atomic::Ordering; use std::time::Duration; -use std::time::Instant; use tokio::process::Child; use tokio::process::Command; use tokio::sync::RwLock; @@ -28,9 +26,10 @@ pub const ADMIN: &str = "admin"; pub const CONFIG_FILE: &str = "agdb_server.yaml"; pub const SERVER_DATA_DIR: &str = "agdb_server_data"; +pub(crate) const HOST: &str = "localhost"; + const BINARY: &str = "agdb_server"; const DEFAULT_PORT: u16 = 3000; -const HOST: &str = "localhost"; const POLL_INTERVAL: u64 = 100; const RETRY_TIMEOUT: Duration = Duration::from_secs(1); const RETRY_ATTEMPS: u16 = 10; @@ -39,18 +38,16 @@ const SHUTDOWN_RETRY_ATTEMPTS: u16 = 100; const TEST_TIMEOUT: u128 = 30000; const CLIENT_TIMEOUT: Duration = Duration::from_secs(30); -type ClusterImpl = Vec; - static PORT: AtomicU16 = AtomicU16::new(DEFAULT_PORT); static COUNTER: AtomicU16 = AtomicU16::new(1); static SERVER: std::sync::OnceLock>> = std::sync::OnceLock::new(); -static CLUSTER: std::sync::OnceLock>> = std::sync::OnceLock::new(); pub struct TestServerProcess(pub Child); -impl TypeDefinition for TestServerProcess { +#[cfg(feature = "api")] +impl agdb::type_def::TypeDefinition for TestServerProcess { fn type_def() -> agdb::type_def::Type { - agdb::type_def::Type::Struct(Struct { + agdb::type_def::Type::Struct(agdb::type_def::Struct { name: "TestServerProcess", generics: &[], fields: &[], @@ -66,7 +63,30 @@ fn server_bin() -> Result { Ok(path.join(format!("{BINARY}{}", std::env::consts::EXE_SUFFIX))) } -#[derive(agdb::TypeDef)] +#[cfg_attr(feature = "api", agdb::fn_def())] +pub fn next_user_name() -> String { + format!("db_user{}", COUNTER.fetch_add(1, Ordering::SeqCst)) +} + +#[cfg_attr(feature = "api", agdb::fn_def())] +pub fn next_db_name() -> String { + format!("db{}", COUNTER.fetch_add(1, Ordering::SeqCst)) +} + +#[cfg_attr(feature = "api", agdb::fn_def())] +pub async fn wait_for_ready(api: &AgdbApi) -> Result<(), TestError> { + for _ in 0..RETRY_ATTEMPS { + if api.status().await.is_ok() { + return Ok(()); + } + + std::thread::sleep(RETRY_TIMEOUT); + } + + bail!("Server not ready") +} + +#[cfg_attr(feature = "api", derive(agdb::TypeDef))] pub struct TestServer { pub dir: String, pub data_dir: String, @@ -74,7 +94,7 @@ pub struct TestServer { pub server: Arc, } -#[derive(agdb::TypeDef)] +#[cfg_attr(feature = "api", derive(agdb::TypeDef))] pub struct TestServerImpl { pub dir: String, pub data_dir: String, @@ -82,12 +102,6 @@ pub struct TestServerImpl { pub process: Option, } -#[derive(agdb::TypeDef)] -pub struct TestCluster { - pub apis: Vec>, - pub cluster: Arc, -} - #[cfg(feature = "tls")] pub fn root_ca() -> reqwest::Certificate { static ROOT_CA: std::sync::OnceLock = std::sync::OnceLock::new(); @@ -355,184 +369,3 @@ impl Drop for TestServerImpl { } } } - -#[cfg_attr(feature = "api", agdb::impl_def())] -impl TestCluster { - pub async fn new() -> Result { - let global_cluster = CLUSTER.get_or_init(|| RwLock::new(Weak::new())); - let mut cluster_guard = global_cluster.write().await; - - let nodes = if let Some(nodes) = cluster_guard.upgrade() { - nodes - } else { - let nodes = Arc::new(create_cluster(3, false).await?); - *cluster_guard = Arc::downgrade(&nodes); - nodes - }; - - let mut cluster = Self { - apis: nodes - .iter() - .map(|s| { - Ok(AgdbApi::new( - ReqwestClient::with_client(reqwest_client()), - &s.address, - )) - }) - .collect::>, TestError>>()?, - cluster: nodes, - }; - - cluster.apis[1].cluster_user_login(ADMIN, ADMIN).await?; - - Ok(cluster) - } -} - -#[cfg_attr(feature = "api", agdb::fn_def())] -pub fn next_user_name() -> String { - format!("db_user{}", COUNTER.fetch_add(1, Ordering::SeqCst)) -} - -#[cfg_attr(feature = "api", agdb::fn_def())] -pub fn next_db_name() -> String { - format!("db{}", COUNTER.fetch_add(1, Ordering::SeqCst)) -} - -#[cfg_attr(feature = "api", agdb::fn_def())] -pub async fn wait_for_ready(api: &AgdbApi) -> Result<(), TestError> { - for _ in 0..RETRY_ATTEMPS { - if api.status().await.is_ok() { - return Ok(()); - } - - std::thread::sleep(RETRY_TIMEOUT); - } - - bail!("Server not ready") -} - -#[cfg_attr(feature = "api", agdb::fn_def())] -pub async fn wait_for_leader( - api: &AgdbApi, -) -> Result, TestError> { - let now = Instant::now(); - - while now.elapsed().as_millis() < TEST_TIMEOUT { - let status = api.cluster_status().await?; - - if status.1.iter().any(|s| s.leader) { - return Ok(status.1); - } - - std::thread::sleep(std::time::Duration::from_millis(POLL_INTERVAL)); - } - - bail!("Leader not found within {TEST_TIMEOUT}seconds") -} - -#[cfg_attr(feature = "api", agdb::fn_def())] -pub async fn create_cluster(nodes: usize, tls: bool) -> Result, TestError> { - let mut configs = Vec::with_capacity(nodes); - let mut cluster = Vec::with_capacity(nodes); - let mut servers = Vec::with_capacity(nodes); - let protocol = if tls { "https" } else { "http" }; - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?; - let tls_cert = if tls { - format!("{manifest_dir}/tests/test_cert.pem") - } else { - String::new() - }; - let tls_key = if tls { - format!("{manifest_dir}/tests/test_cert.key.pem") - } else { - String::new() - }; - let tls_root = if tls { - format!("{manifest_dir}/tests/test_root_ca.pem") - } else { - String::new() - }; - - for _ in 0..nodes { - let port = TestServerImpl::next_port(); - let config = ConfigImpl { - bind: format!("{HOST}:{port}"), - address: format!("{protocol}://{HOST}:{port}"), - basepath: String::new(), - static_roots: Vec::new(), - admin: ADMIN.to_string(), - log_level: crate::LogLevelFilter::Info, - log_body_limit: DEFAULT_LOG_BODY_LIMIT, - request_body_limit: DEFAULT_REQUEST_BODY_LIMIT, - data_dir: SERVER_DATA_DIR.into(), - pepper_path: String::new(), - tls_certificate: tls_cert.clone(), - tls_key: tls_key.clone(), - tls_root: tls_root.clone(), - cluster_token: "test".to_string(), - cluster_heartbeat_timeout_ms: 1000, - cluster_term_timeout_ms: 3000, - cluster: Vec::new(), - cluster_node_id: 0, - start_time: 0, - pepper: None, - }; - - configs.push(config); - cluster.push(format!("{protocol}://{HOST}:{port}")); - } - - for config in &mut configs { - config.cluster = cluster.clone(); - } - - for server in configs - .into_iter() - .map(|c| tokio::spawn(async move { TestServerImpl::with_config(c).await })) - { - let server = server.await??; - let api = AgdbApi::new( - ReqwestClient::with_client(reqwest_client()), - &server.address, - ); - servers.push((server, api)); - } - - let mut statuses = Vec::with_capacity(nodes); - - for server in &servers { - statuses.push(wait_for_leader(&server.1).await?); - } - - for status in &statuses[1..] { - assert_eq!(statuses[0], *status); - } - - let leader = statuses[0] - .iter() - .enumerate() - .find_map(|x| if x.1.leader { Some(x.0) } else { None }) - .unwrap(); - servers.swap(0, leader); - - Ok(servers.into_iter().map(|x| x.0).collect()) -} - -pub struct TestDir { - pub dir: PathBuf, -} - -impl TestDir { - pub fn new() -> Result { - let dir = format!("static_files_test{}", TestServerImpl::next_port()).into(); - std::fs::create_dir_all(&dir)?; - Ok(Self { dir }) - } -} - -impl Drop for TestDir { - fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.dir); - } -} diff --git a/agdb_api/rust/src/test_server/test_cluster.rs b/agdb_api/rust/src/test_server/test_cluster.rs new file mode 100644 index 000000000..121519896 --- /dev/null +++ b/agdb_api/rust/src/test_server/test_cluster.rs @@ -0,0 +1,166 @@ +use crate::AgdbApi; +use crate::ClusterStatus; +use crate::ReqwestClient; +use crate::config_impl::ConfigImpl; +use crate::test_server::ADMIN; +use crate::test_server::HOST; +use crate::test_server::POLL_INTERVAL; +use crate::test_server::TEST_TIMEOUT; +use crate::test_server::TestServerImpl; +use crate::test_server::reqwest_client; +use crate::test_server::test_error::TestError; +use crate::test_server::test_error::bail; +use std::sync::Arc; +use std::sync::Weak; +use std::time::Instant; +use tokio::sync::RwLock; + +type ClusterImpl = Vec; + +static CLUSTER: std::sync::OnceLock>> = std::sync::OnceLock::new(); + +#[cfg_attr(feature = "api", derive(agdb::TypeDef))] +pub struct TestCluster { + pub apis: Vec>, + pub cluster: Arc, +} + +#[cfg_attr(feature = "api", agdb::impl_def())] +impl TestCluster { + pub async fn new() -> Result { + let global_cluster = CLUSTER.get_or_init(|| RwLock::new(Weak::new())); + let mut cluster_guard = global_cluster.write().await; + + let nodes = if let Some(nodes) = cluster_guard.upgrade() { + nodes + } else { + let nodes = Arc::new(create_cluster(3, false).await?); + *cluster_guard = Arc::downgrade(&nodes); + nodes + }; + + let mut cluster = Self { + apis: nodes + .iter() + .map(|s| { + Ok(AgdbApi::new( + ReqwestClient::with_client(reqwest_client()), + &s.address, + )) + }) + .collect::>, TestError>>()?, + cluster: nodes, + }; + + cluster.apis[1].cluster_user_login(ADMIN, ADMIN).await?; + + Ok(cluster) + } +} + +#[cfg_attr(feature = "api", agdb::fn_def())] +pub async fn wait_for_leader( + api: &AgdbApi, +) -> Result, TestError> { + let now = Instant::now(); + + while now.elapsed().as_millis() < TEST_TIMEOUT { + let status = api.cluster_status().await?; + + if status.1.iter().any(|s| s.leader) { + return Ok(status.1); + } + + std::thread::sleep(std::time::Duration::from_millis(POLL_INTERVAL)); + } + + bail!("Leader not found within {TEST_TIMEOUT}seconds") +} + +#[cfg_attr(feature = "api", agdb::fn_def())] +pub async fn create_cluster(nodes: usize, tls: bool) -> Result, TestError> { + let mut configs = Vec::with_capacity(nodes); + let mut cluster = Vec::with_capacity(nodes); + let mut servers = Vec::with_capacity(nodes); + let protocol = if tls { "https" } else { "http" }; + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?; + let tls_cert = if tls { + format!("{manifest_dir}/tests/test_cert.pem") + } else { + String::new() + }; + let tls_key = if tls { + format!("{manifest_dir}/tests/test_cert.key.pem") + } else { + String::new() + }; + let tls_root = if tls { + format!("{manifest_dir}/tests/test_root_ca.pem") + } else { + String::new() + }; + + for _ in 0..nodes { + let port = TestServerImpl::next_port(); + let config = ConfigImpl { + bind: format!("{HOST}:{port}"), + address: format!("{protocol}://{HOST}:{port}"), + basepath: String::new(), + static_roots: Vec::new(), + admin: ADMIN.to_string(), + log_level: crate::LogLevelFilter::Info, + log_body_limit: crate::config_impl::DEFAULT_LOG_BODY_LIMIT, + request_body_limit: crate::config_impl::DEFAULT_REQUEST_BODY_LIMIT, + data_dir: super::SERVER_DATA_DIR.into(), + pepper_path: String::new(), + tls_certificate: tls_cert.clone(), + tls_key: tls_key.clone(), + tls_root: tls_root.clone(), + cluster_token: "test".to_string(), + cluster_heartbeat_timeout_ms: 1000, + cluster_term_timeout_ms: 3000, + cluster: Vec::new(), + cluster_node_id: 0, + start_time: 0, + pepper: None, + }; + + configs.push(config); + cluster.push(format!("{protocol}://{HOST}:{port}")); + } + + for config in &mut configs { + config.cluster = cluster.clone(); + } + + for server in configs + .into_iter() + .map(|c| tokio::spawn(async move { TestServerImpl::with_config(c).await })) + { + let server = server.await??; + let api = AgdbApi::new( + ReqwestClient::with_client(reqwest_client()), + &server.address, + ); + servers.push((server, api)); + } + + let mut statuses = Vec::with_capacity(nodes); + + for server in &servers { + statuses.push(wait_for_leader(&server.1).await?); + } + + for status in &statuses[1..] { + assert_eq!(statuses[0], *status); + } + + let leader = statuses[0] + .iter() + .enumerate() + .find_map(|x| if x.1.leader { Some(x.0) } else { None }) + .unwrap(); + servers.swap(0, leader); + + Ok(servers.into_iter().map(|x| x.0).collect()) +} diff --git a/agdb_api/rust/src/test_server/test_dir.rs b/agdb_api/rust/src/test_server/test_dir.rs new file mode 100644 index 000000000..651c54275 --- /dev/null +++ b/agdb_api/rust/src/test_server/test_dir.rs @@ -0,0 +1,21 @@ +use crate::test_server::TestServerImpl; +use crate::test_server::test_error::TestError; +use std::path::PathBuf; + +pub struct TestDir { + pub dir: PathBuf, +} + +impl TestDir { + pub fn new() -> Result { + let dir = format!("static_files_test{}", TestServerImpl::next_port()).into(); + std::fs::create_dir_all(&dir)?; + Ok(Self { dir }) + } +} + +impl Drop for TestDir { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.dir); + } +} diff --git a/agdb_server/tests/routes/admin_db_copy_test.rs b/agdb_server/tests/routes/admin_db_copy_test.rs index 5d8f988c3..2d846ce4b 100644 --- a/agdb_server/tests/routes/admin_db_copy_test.rs +++ b/agdb_server/tests/routes/admin_db_copy_test.rs @@ -3,10 +3,10 @@ use agdb::DbId; use agdb::QueryBuilder; use agdb_api::DbKind; use agdb_api::test_server::ADMIN; -use agdb_api::test_server::TestCluster; use agdb_api::test_server::TestServer; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; +use agdb_api::test_server::test_cluster::TestCluster; use std::path::Path; #[tokio::test] diff --git a/agdb_server/tests/routes/cluster_test.rs b/agdb_server/tests/routes/cluster_test.rs index 0b52fc5a4..eb8eec3fb 100644 --- a/agdb_server/tests/routes/cluster_test.rs +++ b/agdb_server/tests/routes/cluster_test.rs @@ -6,13 +6,13 @@ use agdb_api::DbResource; use agdb_api::DbUserRole; use agdb_api::ReqwestClient; use agdb_api::test_server::ADMIN; -use agdb_api::test_server::TestCluster; use agdb_api::test_server::TestServer; -use agdb_api::test_server::create_cluster; use agdb_api::test_server::next_db_name; use agdb_api::test_server::next_user_name; use agdb_api::test_server::reqwest_client; -use agdb_api::test_server::wait_for_leader; +use agdb_api::test_server::test_cluster::TestCluster; +use agdb_api::test_server::test_cluster::create_cluster; +use agdb_api::test_server::test_cluster::wait_for_leader; use agdb_api::test_server::wait_for_ready; #[tokio::test] diff --git a/agdb_server/tests/routes/misc_routes.rs b/agdb_server/tests/routes/misc_routes.rs index 2e3b4574c..f5eba40e9 100644 --- a/agdb_server/tests/routes/misc_routes.rs +++ b/agdb_server/tests/routes/misc_routes.rs @@ -4,11 +4,11 @@ use agdb_api::DbKind; use agdb_api::ReqwestClient; use agdb_api::test_server::ADMIN; use agdb_api::test_server::CONFIG_FILE; -use agdb_api::test_server::TestDir; use agdb_api::test_server::TestServer; use agdb_api::test_server::TestServerImpl; use agdb_api::test_server::next_db_name; use agdb_api::test_server::reqwest_client; +use agdb_api::test_server::test_dir::TestDir; use agdb_api::test_server::wait_for_ready; use reqwest::StatusCode; use std::path::Path; diff --git a/agdb_server/tests/tls/mod.rs b/agdb_server/tests/tls/mod.rs index ae1f13b64..9007e2fcf 100644 --- a/agdb_server/tests/tls/mod.rs +++ b/agdb_server/tests/tls/mod.rs @@ -3,9 +3,9 @@ use agdb_api::ReqwestClient; use agdb_api::config_impl::ConfigImpl; use agdb_api::test_server::ADMIN; use agdb_api::test_server::TestServerImpl; -use agdb_api::test_server::create_cluster; use agdb_api::test_server::reqwest_client; -use agdb_api::test_server::wait_for_leader; +use agdb_api::test_server::test_cluster::create_cluster; +use agdb_api::test_server::test_cluster::wait_for_leader; use agdb_api::test_server::wait_for_ready; #[tokio::test] From 0d6d43b878286c0306ef4dd36d31b578fcde4c1e Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Fri, 1 May 2026 21:00:28 +0200 Subject: [PATCH 8/8] Update test_error.rs --- agdb_api/rust/src/test_server/test_error.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agdb_api/rust/src/test_server/test_error.rs b/agdb_api/rust/src/test_server/test_error.rs index cd4c7af90..2713ccb22 100644 --- a/agdb_api/rust/src/test_server/test_error.rs +++ b/agdb_api/rust/src/test_server/test_error.rs @@ -1,7 +1,8 @@ use crate::AgdbApiError; use std::env::VarError; -#[derive(Debug, agdb::TypeDefImpl)] +#[derive(Debug)] +#[cfg_attr(feature = "api", derive(agdb::TypeDefImpl))] pub struct TestError { description: String, }