diff --git a/Cargo.lock b/Cargo.lock index 1594004a..18ac1f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2646,6 +2646,7 @@ dependencies = [ "rand 0.8.5", "reqwest", "serde", + "serde_json", "tempfile", "tokio", ] diff --git a/crates/jstzd/Cargo.toml b/crates/jstzd/Cargo.toml index 580f95e7..0265e565 100644 --- a/crates/jstzd/Cargo.toml +++ b/crates/jstzd/Cargo.toml @@ -15,6 +15,7 @@ http.workspace = true octez = { path = "../octez" } reqwest.workspace = true serde.workspace = true +serde_json.workspace = true tempfile.workspace = true tokio.workspace = true diff --git a/crates/jstzd/src/task/endpoint.rs b/crates/jstzd/src/task/endpoint.rs index c3b5dd3c..20276aef 100644 --- a/crates/jstzd/src/task/endpoint.rs +++ b/crates/jstzd/src/task/endpoint.rs @@ -18,6 +18,12 @@ impl Endpoint { } } +impl ToString for Endpoint { + fn to_string(&self) -> String { + format!("{}://{}:{}", self.scheme, self.host, self.port) + } +} + impl TryFrom for Endpoint { type Error = anyhow::Error; fn try_from(value: Uri) -> Result { @@ -83,4 +89,10 @@ mod tests { let err = Endpoint::try_from(uri).unwrap_err(); assert_eq!(err.to_string(), "No host part in URI 'http://:9999/abc'"); } + + #[test] + fn test_to_string() { + let endpoint = Endpoint::localhost(8765); + assert!(endpoint.to_string().contains("http://localhost:8765")); + } } diff --git a/crates/jstzd/src/task/octez_client.rs b/crates/jstzd/src/task/octez_client.rs index 3984787f..b3d8b913 100644 --- a/crates/jstzd/src/task/octez_client.rs +++ b/crates/jstzd/src/task/octez_client.rs @@ -1,8 +1,24 @@ use super::{endpoint::Endpoint, octez_node::DEFAULT_RPC_ENDPOINT}; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use http::Uri; -use std::{path::PathBuf, str::FromStr}; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; use tempfile::{tempdir, TempDir}; +use tokio::process::Command; + +async fn run_command(command: &mut Command) -> Result<()> { + let output = command.output().await?; + if !output.status.success() { + return Err(anyhow!( + "Command {:?} failed:\n {}", + command, + String::from_utf8_lossy(&output.stderr) + )); + } + Ok(()) +} const DEFAULT_BINARY_PATH: &str = "octez-client"; #[derive(Default)] @@ -90,7 +106,15 @@ enum Directory { Path(PathBuf), } -#[allow(dead_code)] +impl Directory { + fn to_str(&self) -> Option<&str> { + match self { + Directory::TempDir(temp_dir) => temp_dir.path().to_str(), + Directory::Path(path) => path.to_str(), + } + } +} + pub struct OctezClient { binary_path: PathBuf, base_dir: Directory, @@ -98,6 +122,30 @@ pub struct OctezClient { disable_unsafe_disclaimer: bool, } +impl OctezClient { + fn command(&self) -> Result { + let binary_path = self.binary_path.to_str().ok_or(anyhow!("non utf-8 path"))?; + let mut command = Command::new(binary_path); + let base_dir = self.base_dir.to_str().ok_or(anyhow!("non utf-8 path"))?; + command.args(["--base-dir", base_dir]); + command.args(["--endpoint", &self.endpoint.to_string()]); + if self.disable_unsafe_disclaimer { + command.env("TEZOS_CLIENT_UNSAFE_DISABLE_DISCLAIMER", "Y"); + } + Ok(command) + } + + pub async fn config_init(&self, output_path: &Path) -> Result<()> { + let mut command = self.command()?; + command.args(["config", "init"]); + command.args([ + "--output", + output_path.to_str().ok_or(anyhow!("non utf-8 path"))?, + ]); + run_command(&mut command).await + } +} + #[cfg(test)] mod test { use tempfile::NamedTempFile; diff --git a/crates/jstzd/tests/octez_client_test.rs b/crates/jstzd/tests/octez_client_test.rs new file mode 100644 index 00000000..41e21882 --- /dev/null +++ b/crates/jstzd/tests/octez_client_test.rs @@ -0,0 +1,43 @@ +use jstzd::task::{endpoint::Endpoint, octez_client::OctezClientBuilder}; +use serde_json::Value; +use std::{ + ffi::OsStr, + fs::{read_to_string, remove_file}, + os::unix::ffi::OsStrExt, + path::PathBuf, +}; +use tempfile::{NamedTempFile, TempDir}; + +#[tokio::test] +async fn config_init() { + let temp_dir = TempDir::new().unwrap(); + let expected_base_dir = temp_dir.path().to_path_buf(); + let expected_endpoint: Endpoint = Endpoint::localhost(3000); + let config_file = NamedTempFile::new().unwrap(); + let _ = remove_file(config_file.path()); + let octez_client = OctezClientBuilder::new() + .set_base_dir(expected_base_dir.clone()) + .set_endpoint(expected_endpoint.clone()) + .build() + .unwrap(); + let res = octez_client.config_init(config_file.path()).await; + assert!(res.is_ok()); + let actual: Value = + serde_json::from_str(&read_to_string(config_file).expect("Unable to read file")) + .expect("Unable to parse JSON"); + assert_eq!( + actual["base_dir"], + expected_base_dir.to_str().unwrap().to_owned() + ); + assert_eq!(actual["endpoint"], expected_endpoint.to_string()); +} + +#[tokio::test] +async fn command_fails_on_non_utf8_path() { + let invalid_bytes = b"/tmp/\xFF\xFE"; + let invalid_file_path = PathBuf::from(OsStr::from_bytes(invalid_bytes)); + let _ = remove_file(&invalid_file_path); + let octez_client = OctezClientBuilder::new().build().unwrap(); + let res = octez_client.config_init(&invalid_file_path).await; + assert!(res.is_err_and(|e| { e.to_string().contains("non utf-8 path") })); +}