use std::sync::Arc;
use std::sync::Once;
use axum::{
body::Body,
http::{Method, Request, StatusCode},
Router,
};
use http_body_util::BodyExt;
use serde::de::DeserializeOwned;
use tempfile::TempDir;
use tower::ServiceExt;
use super::router::{build_protected_routes, build_public_routes};
use super::state::MultiUserMemoryManager;
use crate::config::ServerConfig;
pub const TEST_API_KEY: &str = "test-handler-key";
pub struct TestHarness {
pub manager: Arc<MultiUserMemoryManager>,
_temp_dir: TempDir,
}
impl TestHarness {
pub fn new() -> Self {
static ENV_INIT: Once = Once::new();
ENV_INIT.call_once(|| unsafe {
std::env::set_var("SHODH_API_KEYS", TEST_API_KEY);
});
let temp_dir = TempDir::new().expect("failed to create temp dir");
let config = ServerConfig {
storage_path: temp_dir.path().to_path_buf(),
backup_enabled: false,
..ServerConfig::default()
};
let manager = MultiUserMemoryManager::new(temp_dir.path().to_path_buf(), config)
.expect("failed to create test MultiUserMemoryManager");
Self {
manager: Arc::new(manager),
_temp_dir: temp_dir,
}
}
pub fn state(&self) -> Arc<MultiUserMemoryManager> {
self.manager.clone()
}
pub fn router(&self) -> Router {
let public = build_public_routes(self.manager.clone());
let protected = build_protected_routes(self.manager.clone())
.layer(axum::middleware::from_fn(crate::auth::auth_middleware));
Router::new().merge(public).merge(protected)
}
}
pub fn get(uri: &str) -> Request<Body> {
Request::builder()
.method(Method::GET)
.uri(uri)
.header("x-api-key", TEST_API_KEY)
.body(Body::empty())
.unwrap()
}
pub fn post_json<T: serde::Serialize>(uri: &str, body: &T) -> Request<Body> {
let json = serde_json::to_string(body).unwrap();
Request::builder()
.method(Method::POST)
.uri(uri)
.header("content-type", "application/json")
.header("x-api-key", TEST_API_KEY)
.body(Body::from(json))
.unwrap()
}
pub fn put_json<T: serde::Serialize>(uri: &str, body: &T) -> Request<Body> {
let json = serde_json::to_string(body).unwrap();
Request::builder()
.method(Method::PUT)
.uri(uri)
.header("content-type", "application/json")
.header("x-api-key", TEST_API_KEY)
.body(Body::from(json))
.unwrap()
}
pub fn delete(uri: &str) -> Request<Body> {
Request::builder()
.method(Method::DELETE)
.uri(uri)
.header("x-api-key", TEST_API_KEY)
.body(Body::empty())
.unwrap()
}
pub fn delete_json<T: serde::Serialize>(uri: &str, body: &T) -> Request<Body> {
let json = serde_json::to_string(body).unwrap();
Request::builder()
.method(Method::DELETE)
.uri(uri)
.header("content-type", "application/json")
.header("x-api-key", TEST_API_KEY)
.body(Body::from(json))
.unwrap()
}
pub fn get_unauthenticated(uri: &str) -> Request<Body> {
Request::builder()
.method(Method::GET)
.uri(uri)
.body(Body::empty())
.unwrap()
}
pub fn post_json_unauthenticated<T: serde::Serialize>(uri: &str, body: &T) -> Request<Body> {
let json = serde_json::to_string(body).unwrap();
Request::builder()
.method(Method::POST)
.uri(uri)
.header("content-type", "application/json")
.body(Body::from(json))
.unwrap()
}
pub async fn send(app: Router, req: Request<Body>) -> (StatusCode, serde_json::Value) {
let resp = app.oneshot(req).await.unwrap();
let status = resp.status();
let body_bytes = resp.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = if body_bytes.is_empty() {
serde_json::Value::Null
} else {
serde_json::from_slice(&body_bytes).unwrap_or_else(|_| {
serde_json::Value::String(String::from_utf8_lossy(&body_bytes).to_string())
})
};
(status, json)
}
pub async fn send_typed<T: DeserializeOwned>(app: Router, req: Request<Body>) -> (StatusCode, T) {
let resp = app.oneshot(req).await.unwrap();
let status = resp.status();
let body_bytes = resp.into_body().collect().await.unwrap().to_bytes();
let value: T = serde_json::from_slice(&body_bytes).unwrap_or_else(|e| {
panic!(
"failed to deserialize response: {e}\nbody: {}",
String::from_utf8_lossy(&body_bytes)
)
});
(status, value)
}