#![allow(dead_code)]
use std::path::PathBuf;
use std::time::Duration;
use http_body_util::BodyExt;
use hyper::body::Bytes;
use hyper::client::conn::http1;
use hyper::{Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use kindling_server::{serve, ServerConfig, PROJECT_HEADER};
use serde_json::Value;
use tempfile::TempDir;
use tokio::net::UnixStream;
use tokio::task::JoinHandle;
pub struct TestDaemon {
pub socket_path: PathBuf,
pub kindling_home: PathBuf,
_home: TempDir,
handle: JoinHandle<Result<(), kindling_server::ServerError>>,
}
impl TestDaemon {
pub async fn start() -> Self {
Self::start_with_idle(Duration::from_secs(3600)).await
}
pub async fn start_with_idle(idle: Duration) -> Self {
let home = tempfile::tempdir().unwrap();
let home_path = home.path().to_path_buf();
let socket_path = home_path.join("k.sock");
let config = ServerConfig {
socket_path: socket_path.clone(),
kindling_home: home_path.clone(),
pid_path: home_path.join("k.pid"),
port_path: home_path.join("k.port"),
idle_timeout: idle,
transport: kindling_server::Transport::default(),
};
let handle = tokio::spawn(async move { serve(config).await });
for _ in 0..200 {
if socket_path.exists() {
break;
}
tokio::time::sleep(Duration::from_millis(5)).await;
}
assert!(socket_path.exists(), "daemon socket never appeared");
Self {
socket_path,
kindling_home: home_path,
_home: home,
handle,
}
}
pub async fn connect(&self) -> Client {
Client::connect(&self.socket_path).await
}
pub async fn join(self) -> Result<(), kindling_server::ServerError> {
self.handle.await.expect("serve task panicked")
}
}
pub struct Client {
sender: http1::SendRequest<String>,
socket_path: PathBuf,
}
pub struct TestResponse {
pub status: StatusCode,
pub body: Bytes,
}
impl TestResponse {
pub fn json(&self) -> Value {
if self.body.is_empty() {
return Value::Null;
}
serde_json::from_slice(&self.body)
.unwrap_or_else(|e| panic!("response body was not JSON ({e}): {:?}", self.body))
}
}
impl Client {
pub async fn connect(socket_path: &std::path::Path) -> Self {
let stream = UnixStream::connect(socket_path).await.expect("connect uds");
let io = TokioIo::new(stream);
let (sender, conn) = http1::handshake::<_, String>(io)
.await
.expect("http1 handshake");
tokio::spawn(async move {
let _ = conn.await;
});
Self {
sender,
socket_path: socket_path.to_path_buf(),
}
}
pub async fn send(
&mut self,
method: &str,
path: &str,
project: Option<&str>,
body: Option<Value>,
) -> TestResponse {
let body_str = body.map(|v| v.to_string()).unwrap_or_default();
let mut builder = Request::builder()
.method(method)
.uri(path)
.header("host", "kindling.local")
.header("content-type", "application/json");
if let Some(p) = project {
builder = builder.header(PROJECT_HEADER, p);
}
let req = builder.body(body_str).unwrap();
let resp: Response<_> = self.sender.send_request(req).await.expect("send_request");
let status = resp.status();
let body = resp
.into_body()
.collect()
.await
.expect("collect body")
.to_bytes();
TestResponse { status, body }
}
pub async fn send_with_headers(
&mut self,
method: &str,
path: &str,
project: Option<&str>,
extra_headers: &[(&str, &str)],
body: Option<Value>,
) -> TestResponse {
let body_str = body.map(|v| v.to_string()).unwrap_or_default();
let mut builder = Request::builder()
.method(method)
.uri(path)
.header("host", "kindling.local")
.header("content-type", "application/json");
if let Some(p) = project {
builder = builder.header(PROJECT_HEADER, p);
}
for (name, value) in extra_headers {
builder = builder.header(*name, *value);
}
let req = builder.body(body_str).unwrap();
let resp: Response<_> = self.sender.send_request(req).await.expect("send_request");
let status = resp.status();
let body = resp
.into_body()
.collect()
.await
.expect("collect body")
.to_bytes();
TestResponse { status, body }
}
pub fn socket_path(&self) -> &std::path::Path {
&self.socket_path
}
}