use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::Duration;
use crate::remote::client::{Client, ClientConfig, Error};
use crate::remote::{load_tls, serve_blocking, Daemon, DaemonConfig, StopHandle, SystemClock};
pub struct TestCertificate {
pub cert_pem: String,
pub key_pem: String,
}
impl TestCertificate {
pub fn generate() -> io::Result<Self> {
let ck = rcgen::generate_simple_self_signed(vec![
"localhost".to_string(),
"127.0.0.1".to_string(),
])
.map_err(io::Error::other)?;
Ok(Self {
cert_pem: ck.cert.pem(),
key_pem: ck.signing_key.serialize_pem(),
})
}
}
pub struct TestDaemon {
pub addr: SocketAddr,
pub root_pem: String,
pub enrolment_code: String,
pub daemon: Arc<Daemon>,
stop: StopHandle,
thread: Option<JoinHandle<io::Result<()>>>,
_state_dir: tempfile::TempDir,
}
pub fn test_config(state_dir: &std::path::Path) -> DaemonConfig {
let mut cfg = DaemonConfig::new(state_dir);
cfg.limits.chunk_min = 4;
cfg.limits.chunk_max = 1 << 20;
cfg.limits.max_object_bytes = 64 << 20;
cfg
}
impl TestDaemon {
pub fn start() -> io::Result<Self> {
Self::start_with(|_| {})
}
pub fn start_with(tweak: impl FnOnce(&mut DaemonConfig)) -> io::Result<Self> {
let dir = tempfile::tempdir()?;
let mut cfg = test_config(dir.path());
tweak(&mut cfg);
let daemon = Arc::new(Daemon::open(cfg, Arc::new(SystemClock), None)?);
let cert = TestCertificate::generate()?;
let tls = load_tls(cert.cert_pem.as_bytes(), cert.key_pem.as_bytes())?;
let listener = std::net::TcpListener::bind("127.0.0.1:0")?;
let (ready_tx, ready_rx) = std::sync::mpsc::channel();
let stop = StopHandle::new();
let thread = {
let (daemon, stop) = (daemon.clone(), stop.clone());
std::thread::Builder::new()
.name("rightkit-test-daemon".into())
.spawn(move || serve_blocking(daemon, listener, tls, ready_tx, stop))?
};
let addr = match ready_rx.recv_timeout(Duration::from_secs(20)) {
Ok(addr) => addr,
Err(_) => {
stop.stop();
return match thread.join() {
Ok(Err(e)) => Err(e),
_ => Err(io::Error::other("test daemon did not report its address")),
};
}
};
let enrolment_code =
daemon.issue_enrolment_code(&["venture", "channel", "account"], 600)?;
Ok(Self {
addr,
root_pem: cert.cert_pem,
enrolment_code,
daemon,
stop,
thread: Some(thread),
_state_dir: dir,
})
}
pub fn client_config(&self) -> Result<ClientConfig, Error> {
ClientConfig::new(self.addr).root_pem(self.root_pem.as_bytes())
}
pub fn client(&self) -> Result<Client, Error> {
Client::new(self.client_config()?)
}
pub fn issue_code(&self, scopes: &[&str]) -> io::Result<String> {
self.daemon.issue_enrolment_code(scopes, 600)
}
pub fn stop_handle(&self) -> StopHandle {
self.stop.clone()
}
pub fn stop(mut self) -> io::Result<()> {
self.stop.stop();
match self.thread.take() {
Some(t) => t
.join()
.unwrap_or_else(|_| Err(io::Error::other("test daemon thread panicked"))),
None => Ok(()),
}
}
}
impl Drop for TestDaemon {
fn drop(&mut self) {
self.stop.stop();
if let Some(t) = self.thread.take() {
let _ = t.join();
}
}
}