| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| use std::net::SocketAddr; |
| use std::sync::Arc; |
|
|
| use anyhow::{Context, Result}; |
| use camino::Utf8PathBuf; |
| use koharu_app::{App, AppConfig}; |
| use koharu_client::apis::configuration::Configuration; |
| use koharu_rpc::{BootstrapManager, server}; |
| use koharu_runtime::{ComputePolicy, RuntimeHttpConfig, RuntimeManager}; |
| use tokio::net::TcpListener; |
| use tokio::sync::OnceCell; |
|
|
| |
| |
| fn cache_root() -> Utf8PathBuf { |
| let manifest_dir = |
| Utf8PathBuf::from_path_buf(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))) |
| .expect("manifest dir UTF-8"); |
| manifest_dir.join(".cache") |
| } |
|
|
| |
| |
| static SHARED_RUNTIME: OnceCell<Arc<RuntimeManager>> = OnceCell::const_new(); |
|
|
| async fn shared_runtime() -> Result<Arc<RuntimeManager>> { |
| SHARED_RUNTIME |
| .get_or_try_init(|| async { |
| let root = cache_root(); |
| std::fs::create_dir_all(root.as_std_path()).context("create runtime cache root")?; |
| let http = RuntimeHttpConfig { |
| connect_timeout_secs: 30, |
| read_timeout_secs: 600, |
| max_retries: 2, |
| }; |
| let runtime = |
| RuntimeManager::new_with_http(root.as_std_path(), ComputePolicy::CpuOnly, http)?; |
| runtime.prepare().await.context("prepare runtime")?; |
| Ok::<_, anyhow::Error>(Arc::new(runtime)) |
| }) |
| .await |
| .cloned() |
| } |
|
|
| |
| |
| pub struct TestApp { |
| pub app: Arc<App>, |
| pub addr: SocketAddr, |
| pub base_url: String, |
| pub client_config: Configuration, |
| _server: tokio::task::JoinHandle<Result<()>>, |
| _data_dir: tempfile::TempDir, |
| } |
|
|
| impl TestApp { |
| |
| pub async fn spawn() -> Result<Self> { |
| Self::spawn_with(|_| {}).await |
| } |
|
|
| |
| pub async fn spawn_with(tweak: impl FnOnce(&mut AppConfig)) -> Result<Self> { |
| let data_dir = tempfile::tempdir()?; |
| let data_root = Utf8PathBuf::from_path_buf(data_dir.path().to_path_buf()) |
| .map_err(|p| anyhow::anyhow!("tempdir not UTF-8: {}", p.display()))?; |
|
|
| |
| |
| |
| |
| |
| |
| unsafe { |
| std::env::set_var("KOHARU_DATA_ROOT", data_root.as_str()); |
| } |
|
|
| let mut config = AppConfig::default(); |
| config.data.path = data_root; |
| tweak(&mut config); |
|
|
| |
| let runtime = shared_runtime().await?; |
| let state = BootstrapManager::new(runtime.clone()); |
| state.spawn_download_forwarder(); |
| let app = Arc::new(App::new_with_shared_state( |
| config, |
| runtime, |
| true, |
| state.shared_state(), |
| "test", |
| )?); |
| app.spawn_llm_forwarder(); |
| state |
| .set_app(app.clone()) |
| .map_err(|_| anyhow::anyhow!("test app already initialized"))?; |
|
|
| let listener = TcpListener::bind("127.0.0.1:0").await?; |
| let addr = listener.local_addr()?; |
| let base_url = format!("http://{addr}/api/v1"); |
| let server = tokio::spawn({ |
| let state = state.clone(); |
| async move { server::serve_with_listener(listener, state).await } |
| }); |
|
|
| let client_config = Configuration { |
| base_path: base_url.clone(), |
| user_agent: Some("koharu-integration-tests".to_string()), |
| client: reqwest::Client::builder() |
| .timeout(std::time::Duration::from_secs(30)) |
| .build()?, |
| ..Default::default() |
| }; |
|
|
| Ok(Self { |
| app, |
| addr, |
| base_url, |
| client_config, |
| _server: server, |
| _data_dir: data_dir, |
| }) |
| } |
|
|
| |
| pub async fn open_fresh_project(&self, name: &str) -> Result<Utf8PathBuf> { |
| let dir = Utf8PathBuf::from_path_buf(self._data_dir.path().join(format!("{name}.khrproj"))) |
| .map_err(|p| anyhow::anyhow!("project path not UTF-8: {}", p.display()))?; |
| self.app |
| .open_project(dir.clone(), Some(name.to_string())) |
| .await?; |
| Ok(dir) |
| } |
|
|
| |
| pub fn tiny_png(w: u32, h: u32, color: [u8; 4]) -> Vec<u8> { |
| let mut buf = std::io::Cursor::new(Vec::new()); |
| image::RgbaImage::from_pixel(w, h, image::Rgba(color)) |
| .write_to(&mut buf, image::ImageFormat::Png) |
| .expect("encode png"); |
| buf.into_inner() |
| } |
| } |
|
|