feat: Vendor pezkuwi-subxt and pezkuwi-zombienet-sdk into monorepo

- Add pezkuwi-subxt crates to vendor/pezkuwi-subxt
- Add pezkuwi-zombienet-sdk crates to vendor/pezkuwi-zombienet-sdk
- Convert git dependencies to path dependencies
- Add vendor crates to workspace members
- Remove test/example crates from vendor (not needed for SDK)
- Fix feature propagation issues detected by zepter
- Fix workspace inheritance for internal dependencies
- All 606 crates now in workspace
- All 6919 internal dependency links verified correct
- No git dependencies remaining
This commit is contained in:
2025-12-22 23:31:24 +03:00
parent 4c8f281051
commit 70ddb6516f
386 changed files with 76759 additions and 36 deletions
@@ -0,0 +1,2 @@
/target
/Cargo.lock
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "zombienet-support"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
publish = true
license.workspace = true
repository.workspace = true
description = "Support crates with common traits/structs and helpers"
keywords = ["zombienet"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = { workspace = true }
anyhow = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true }
reqwest = { workspace = true }
tokio = { workspace = true, features = ["full"] }
uuid = { workspace = true, features = ["v4"] }
nix = { workspace = true, features = ["signal"] }
rand = { workspace = true }
regex = { workspace = true }
tracing = { workspace = true }
lazy_static = { workspace = true }
serde_json = { workspace = true }
@@ -0,0 +1,24 @@
pub const VALID_REGEX: &str = "regex should be valid ";
pub const BORROWABLE: &str = "must be borrowable as mutable ";
pub const RELAY_NOT_NONE: &str = "typestate should ensure the relaychain isn't None at this point ";
pub const SHOULD_COMPILE: &str = "should compile with success ";
pub const INFAILABLE: &str = "infaillible ";
pub const NO_ERR_DEF_BUILDER: &str = "should have no errors for default builder ";
pub const RW_FAILED: &str = "should be able to read/write - failed ";
pub const DEFAULT_TYPESTATE: &str = "'default' overriding should be ensured by typestate ";
pub const VALIDATION_CHECK: &str = "validation failed ";
pub const PREFIX_CANT_BE_NONE: &str = "name prefix can't be None if a value exists ";
pub const GRAPH_CONTAINS_NAME: &str =
"graph contains node name; we initialize it with all node names";
pub const GRAPH_CONTAINS_DEP: &str = "graph contains dep_name; we filter out deps not contained in by_name and populate the graph with all nodes";
pub const INDEGREE_CONTAINS_NAME: &str =
"indegree contains node name; we initialize it with all node names";
pub const QUEUE_NOT_EMPTY: &str = "queue is not empty; we're looping over its length";
pub const THIS_IS_A_BUG: &str =
"- this is a bug please report it: https://github.com/paritytech/zombienet-sdk/issues";
/// environment variable which can be used to override node spawn timeout
pub const ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS: &str = "ZOMBIE_NODE_SPAWN_TIMEOUT_SECONDS";
+60
View File
@@ -0,0 +1,60 @@
use std::path::Path;
use async_trait::async_trait;
pub mod in_memory;
pub mod local;
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct FileSystemError(#[from] anyhow::Error);
impl From<std::io::Error> for FileSystemError {
fn from(error: std::io::Error) -> Self {
Self(error.into())
}
}
pub type FileSystemResult<T> = Result<T, FileSystemError>;
#[async_trait]
pub trait FileSystem {
async fn create_dir<P>(&self, path: P) -> FileSystemResult<()>
where
P: AsRef<Path> + Send;
async fn create_dir_all<P>(&self, path: P) -> FileSystemResult<()>
where
P: AsRef<Path> + Send;
async fn read<P>(&self, path: P) -> FileSystemResult<Vec<u8>>
where
P: AsRef<Path> + Send;
async fn read_to_string<P>(&self, path: P) -> FileSystemResult<String>
where
P: AsRef<Path> + Send;
async fn write<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
C: AsRef<[u8]> + Send;
async fn append<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
C: AsRef<[u8]> + Send;
async fn copy<P1, P2>(&self, from: P1, to: P2) -> FileSystemResult<()>
where
P1: AsRef<Path> + Send,
P2: AsRef<Path> + Send;
async fn set_mode<P>(&self, path: P, perm: u32) -> FileSystemResult<()>
where
P: AsRef<Path> + Send;
async fn exists<P>(&self, path: P) -> bool
where
P: AsRef<Path> + Send;
}
@@ -0,0 +1,879 @@
use std::{collections::HashMap, ffi::OsString, path::Path, sync::Arc};
use anyhow::anyhow;
use async_trait::async_trait;
use tokio::sync::RwLock;
use super::{FileSystem, FileSystemResult};
#[derive(Debug, Clone, PartialEq)]
pub enum InMemoryFile {
File { mode: u32, contents: Vec<u8> },
Directory { mode: u32 },
}
impl InMemoryFile {
pub fn file<C>(contents: C) -> Self
where
C: AsRef<str>,
{
Self::file_raw(contents.as_ref())
}
pub fn file_raw<C>(contents: C) -> Self
where
C: AsRef<[u8]>,
{
Self::File {
mode: 0o664,
contents: contents.as_ref().to_vec(),
}
}
pub fn empty() -> Self {
Self::file_raw(vec![])
}
pub fn dir() -> Self {
Self::Directory { mode: 0o775 }
}
pub fn mode(&self) -> u32 {
match *self {
Self::File { mode, .. } => mode,
Self::Directory { mode, .. } => mode,
}
}
pub fn contents_raw(&self) -> Option<Vec<u8>> {
match self {
Self::File { contents, .. } => Some(contents.to_vec()),
Self::Directory { .. } => None,
}
}
pub fn contents(&self) -> Option<String> {
match self {
Self::File { contents, .. } => Some(String::from_utf8_lossy(contents).to_string()),
Self::Directory { .. } => None,
}
}
}
#[derive(Default, Debug, Clone)]
pub struct InMemoryFileSystem {
pub files: Arc<RwLock<HashMap<OsString, InMemoryFile>>>,
}
impl InMemoryFileSystem {
pub fn new(files: HashMap<OsString, InMemoryFile>) -> Self {
Self {
files: Arc::new(RwLock::new(files)),
}
}
}
#[async_trait]
impl FileSystem for InMemoryFileSystem {
async fn create_dir<P>(&self, path: P) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
{
let path = path.as_ref();
let os_path = path.as_os_str();
match self.files.read().await.get(os_path) {
Some(InMemoryFile::File { .. }) => {
Err(anyhow!("file {:?} already exists", os_path.to_owned(),))?
},
Some(InMemoryFile::Directory { .. }) => {
Err(anyhow!("directory {:?} already exists", os_path.to_owned(),))?
},
None => {},
};
for path in path.ancestors().skip(1) {
match self.files.read().await.get(path.as_os_str()) {
Some(InMemoryFile::Directory { .. }) => continue,
Some(InMemoryFile::File { .. }) => Err(anyhow!(
"ancestor {:?} is not a directory",
path.as_os_str(),
))?,
None => Err(anyhow!("ancestor {:?} doesn't exists", path.as_os_str(),))?,
};
}
self.files
.write()
.await
.insert(os_path.to_owned(), InMemoryFile::dir());
Ok(())
}
async fn create_dir_all<P>(&self, path: P) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
{
let path = path.as_ref();
let mut files = self.files.write().await;
let ancestors = path
.ancestors()
.collect::<Vec<&Path>>()
.into_iter()
.rev()
.skip(1);
for path in ancestors {
match files.get(path.as_os_str()) {
Some(InMemoryFile::Directory { .. }) => continue,
Some(InMemoryFile::File { .. }) => Err(anyhow!(
"ancestor {:?} is not a directory",
path.as_os_str().to_owned(),
))?,
None => files.insert(path.as_os_str().to_owned(), InMemoryFile::dir()),
};
}
Ok(())
}
async fn read<P>(&self, path: P) -> FileSystemResult<Vec<u8>>
where
P: AsRef<Path> + Send,
{
let os_path = path.as_ref().as_os_str();
match self.files.read().await.get(os_path) {
Some(InMemoryFile::File { contents, .. }) => Ok(contents.clone()),
Some(InMemoryFile::Directory { .. }) => {
Err(anyhow!("file {os_path:?} is a directory").into())
},
None => Err(anyhow!("file {os_path:?} not found").into()),
}
}
async fn read_to_string<P>(&self, path: P) -> FileSystemResult<String>
where
P: AsRef<Path> + Send,
{
let os_path = path.as_ref().as_os_str().to_owned();
let content = self.read(path).await?;
String::from_utf8(content)
.map_err(|_| anyhow!("invalid utf-8 encoding for file {os_path:?}").into())
}
async fn write<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
C: AsRef<[u8]> + Send,
{
let path = path.as_ref();
let os_path = path.as_os_str();
let mut files = self.files.write().await;
for path in path.ancestors().skip(1) {
match files.get(path.as_os_str()) {
Some(InMemoryFile::Directory { .. }) => continue,
Some(InMemoryFile::File { .. }) => Err(anyhow!(
"ancestor {:?} is not a directory",
path.as_os_str()
))?,
None => Err(anyhow!("ancestor {:?} doesn't exists", path.as_os_str()))?,
};
}
if let Some(InMemoryFile::Directory { .. }) = files.get(os_path) {
return Err(anyhow!("file {os_path:?} is a directory").into());
}
files.insert(os_path.to_owned(), InMemoryFile::file_raw(contents));
Ok(())
}
async fn append<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
C: AsRef<[u8]> + Send,
{
let path = path.as_ref();
let mut existing_contents = match self.read(path).await {
Ok(existing_contents) => existing_contents,
Err(err) if err.to_string() == format!("file {:?} not found", path.as_os_str()) => {
vec![]
},
Err(err) => Err(err)?,
};
existing_contents.append(&mut contents.as_ref().to_vec());
self.write(path, existing_contents).await
}
async fn copy<P1, P2>(&self, from: P1, to: P2) -> FileSystemResult<()>
where
P1: AsRef<Path> + Send,
P2: AsRef<Path> + Send,
{
let from_ref = from.as_ref();
let to_ref = to.as_ref();
let content = self.read(from_ref).await?;
self.write(to_ref, content).await
}
async fn set_mode<P>(&self, path: P, mode: u32) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
{
let os_path = path.as_ref().as_os_str();
if let Some(file) = self.files.write().await.get_mut(os_path) {
match file {
InMemoryFile::File { mode: old_mode, .. } => {
*old_mode = mode;
},
InMemoryFile::Directory { mode: old_mode, .. } => {
*old_mode = mode;
},
};
Ok(())
} else {
Err(anyhow!("file {os_path:?} not found").into())
}
}
async fn exists<P>(&self, path: P) -> bool
where
P: AsRef<Path> + Send,
{
self.files
.read()
.await
.contains_key(path.as_ref().as_os_str())
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[tokio::test]
async fn create_dir_should_create_a_directory_at_root() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/").unwrap(),
InMemoryFile::dir(),
)]));
fs.create_dir("/dir").await.unwrap();
assert_eq!(fs.files.read().await.len(), 2);
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/dir").unwrap())
.unwrap(),
InMemoryFile::Directory { mode } if *mode == 0o775
));
}
#[tokio::test]
async fn create_dir_should_return_an_error_if_directory_already_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/dir").unwrap(), InMemoryFile::dir()),
]));
let err = fs.create_dir("/dir").await.unwrap_err();
assert_eq!(fs.files.read().await.len(), 2);
assert_eq!(err.to_string(), "directory \"/dir\" already exists");
}
#[tokio::test]
async fn create_dir_should_return_an_error_if_file_already_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/dir").unwrap(), InMemoryFile::empty()),
]));
let err = fs.create_dir("/dir").await.unwrap_err();
assert_eq!(fs.files.read().await.len(), 2);
assert_eq!(err.to_string(), "file \"/dir\" already exists");
}
#[tokio::test]
async fn create_dir_should_create_a_directory_if_all_ancestors_exist() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/path/to/my").unwrap(),
InMemoryFile::dir(),
),
]));
fs.create_dir("/path/to/my/dir").await.unwrap();
assert_eq!(fs.files.read().await.len(), 5);
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/path/to/my/dir").unwrap())
.unwrap(),
InMemoryFile::Directory { mode} if *mode == 0o775
));
}
#[tokio::test]
async fn create_dir_should_return_an_error_if_some_directory_ancestor_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
]));
let err = fs.create_dir("/path/to/my/dir").await.unwrap_err();
assert_eq!(fs.files.read().await.len(), 3);
assert_eq!(err.to_string(), "ancestor \"/path/to/my\" doesn't exists");
}
#[tokio::test]
async fn create_dir_should_return_an_error_if_some_ancestor_is_not_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path").unwrap(), InMemoryFile::empty()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/path/to/my").unwrap(),
InMemoryFile::dir(),
),
]));
let err = fs.create_dir("/path/to/my/dir").await.unwrap_err();
assert_eq!(fs.files.read().await.len(), 4);
assert_eq!(err.to_string(), "ancestor \"/path\" is not a directory");
}
#[tokio::test]
async fn create_dir_all_should_create_a_directory_and_all_its_ancestors_if_they_dont_exist() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/").unwrap(),
InMemoryFile::dir(),
)]));
fs.create_dir_all("/path/to/my/dir").await.unwrap();
assert_eq!(fs.files.read().await.len(), 5);
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/path").unwrap())
.unwrap(),
InMemoryFile::Directory { mode } if *mode == 0o775
));
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/path/to").unwrap())
.unwrap(),
InMemoryFile::Directory { mode } if *mode == 0o775
));
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/path/to/my").unwrap())
.unwrap(),
InMemoryFile::Directory { mode } if *mode == 0o775
));
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/path/to/my/dir").unwrap())
.unwrap(),
InMemoryFile::Directory { mode } if *mode == 0o775
));
}
#[tokio::test]
async fn create_dir_all_should_create_a_directory_and_some_of_its_ancestors_if_they_dont_exist()
{
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
]));
fs.create_dir_all("/path/to/my/dir").await.unwrap();
assert_eq!(fs.files.read().await.len(), 5);
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/path/to/my").unwrap())
.unwrap(),
InMemoryFile::Directory { mode } if *mode == 0o775
));
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/path/to/my/dir").unwrap())
.unwrap(),
InMemoryFile::Directory { mode } if *mode == 0o775
));
}
#[tokio::test]
async fn create_dir_all_should_return_an_error_if_some_ancestor_is_not_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path").unwrap(), InMemoryFile::empty()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
]));
let err = fs.create_dir_all("/path/to/my/dir").await.unwrap_err();
assert_eq!(fs.files.read().await.len(), 3);
assert_eq!(err.to_string(), "ancestor \"/path\" is not a directory");
}
#[tokio::test]
async fn read_should_return_the_file_content() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("content"),
)]));
let content = fs.read("/myfile").await.unwrap();
assert_eq!(content, "content".as_bytes().to_vec());
}
#[tokio::test]
async fn read_should_return_an_error_if_file_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::new());
let err = fs.read("/myfile").await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfile\" not found");
}
#[tokio::test]
async fn read_should_return_an_error_if_file_is_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::dir(),
)]));
let err = fs.read("/myfile").await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
}
#[tokio::test]
async fn read_to_string_should_return_the_file_content_as_a_string() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("content"),
)]));
let content = fs.read_to_string("/myfile").await.unwrap();
assert_eq!(content, "content");
}
#[tokio::test]
async fn read_to_string_should_return_an_error_if_file_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::new());
let err = fs.read_to_string("/myfile").await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfile\" not found");
}
#[tokio::test]
async fn read_to_string_should_return_an_error_if_file_is_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::dir(),
)]));
let err = fs.read_to_string("/myfile").await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
}
#[tokio::test]
async fn read_to_string_should_return_an_error_if_file_isnt_utf8_encoded() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file_raw(vec![0xC3, 0x28]),
)]));
let err = fs.read_to_string("/myfile").await.unwrap_err();
assert_eq!(
err.to_string(),
"invalid utf-8 encoding for file \"/myfile\""
);
}
#[tokio::test]
async fn write_should_create_file_with_content_if_file_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/").unwrap(),
InMemoryFile::dir(),
)]));
fs.write("/myfile", "my file content").await.unwrap();
assert_eq!(fs.files.read().await.len(), 2);
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/myfile").unwrap()),
Some(InMemoryFile::File {mode, contents, .. }) if *mode == 0o664 && contents == "my file content".as_bytes()
));
}
#[tokio::test]
async fn write_should_overwrite_file_content_if_file_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("my file content"),
),
]));
fs.write("/myfile", "my new file content").await.unwrap();
assert_eq!(fs.files.read().await.len(), 2);
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/myfile").unwrap()),
Some(InMemoryFile::File { mode, contents, .. }) if *mode == 0o664 && contents == "my new file content".as_bytes()
));
}
#[tokio::test]
async fn write_should_return_an_error_if_file_is_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/myfile").unwrap(), InMemoryFile::dir()),
]));
let err = fs.write("/myfile", "my file content").await.unwrap_err();
assert_eq!(fs.files.read().await.len(), 2);
assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
}
#[tokio::test]
async fn write_should_return_an_error_if_file_is_new_and_some_ancestor_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
]));
let err = fs
.write("/path/to/myfile", "my file content")
.await
.unwrap_err();
assert_eq!(fs.files.read().await.len(), 2);
assert_eq!(err.to_string(), "ancestor \"/path\" doesn't exists");
}
#[tokio::test]
async fn write_should_return_an_error_if_file_is_new_and_some_ancestor_is_not_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path").unwrap(), InMemoryFile::empty()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
]));
let err = fs
.write("/path/to/myfile", "my file content")
.await
.unwrap_err();
assert_eq!(fs.files.read().await.len(), 3);
assert_eq!(err.to_string(), "ancestor \"/path\" is not a directory");
}
#[tokio::test]
async fn append_should_update_file_content_if_file_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("my file content"),
),
]));
fs.append("/myfile", " has been updated with new things")
.await
.unwrap();
assert_eq!(fs.files.read().await.len(), 2);
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/myfile").unwrap()),
Some(InMemoryFile::File { mode, contents, .. }) if *mode == 0o664 && contents == "my file content has been updated with new things".as_bytes()
));
}
#[tokio::test]
async fn append_should_create_file_with_content_if_file_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/").unwrap(),
InMemoryFile::dir(),
)]));
fs.append("/myfile", "my file content").await.unwrap();
assert_eq!(fs.files.read().await.len(), 2);
assert!(matches!(
fs.files
.read()
.await
.get(&OsString::from_str("/myfile").unwrap()),
Some(InMemoryFile::File { mode,contents, .. }) if *mode == 0o664 && contents == "my file content".as_bytes()
));
}
#[tokio::test]
async fn append_should_return_an_error_if_file_is_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::dir(),
)]));
let err = fs.append("/myfile", "my file content").await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
}
#[tokio::test]
async fn append_should_return_an_error_if_file_is_new_and_some_ancestor_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
]));
let err = fs
.append("/path/to/myfile", "my file content")
.await
.unwrap_err();
assert_eq!(fs.files.read().await.len(), 2);
assert_eq!(err.to_string(), "ancestor \"/path\" doesn't exists");
}
#[tokio::test]
async fn append_should_return_an_error_if_file_is_new_and_some_ancestor_is_not_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/path").unwrap(), InMemoryFile::empty()),
(OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
]));
let err = fs
.append("/path/to/myfile", "my file content")
.await
.unwrap_err();
assert_eq!(fs.files.read().await.len(), 3);
assert_eq!(err.to_string(), "ancestor \"/path\" is not a directory");
}
#[tokio::test]
async fn copy_should_creates_new_destination_file_if_it_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("my file content"),
),
]));
fs.copy("/myfile", "/myfilecopy").await.unwrap();
assert_eq!(fs.files.read().await.len(), 3);
assert!(
matches!(fs.files.read().await.get(&OsString::from_str("/myfilecopy").unwrap()).unwrap(), InMemoryFile::File { mode, contents, .. } if *mode == 0o664 && contents == "my file content".as_bytes())
);
}
#[tokio::test]
async fn copy_should_updates_the_file_content_of_the_destination_file_if_it_already_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("my new file content"),
),
(
OsString::from_str("/myfilecopy").unwrap(),
InMemoryFile::file("my file content"),
),
]));
fs.copy("/myfile", "/myfilecopy").await.unwrap();
assert_eq!(fs.files.read().await.len(), 3);
assert!(
matches!(fs.files.read().await.get(&OsString::from_str("/myfilecopy").unwrap()).unwrap(), InMemoryFile::File { mode, contents, .. } if *mode == 0o664 && contents == "my new file content".as_bytes())
);
}
#[tokio::test]
async fn copy_should_returns_an_error_if_source_file_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/").unwrap(),
InMemoryFile::dir(),
)]));
let err = fs.copy("/myfile", "/mfilecopy").await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfile\" not found");
}
#[tokio::test]
async fn copy_should_returns_an_error_if_source_file_is_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/myfile").unwrap(), InMemoryFile::dir()),
]));
let err = fs.copy("/myfile", "/mfilecopy").await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
}
#[tokio::test]
async fn copy_should_returns_an_error_if_destination_file_is_a_directory() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("my file content"),
),
(
OsString::from_str("/myfilecopy").unwrap(),
InMemoryFile::dir(),
),
]));
let err = fs.copy("/myfile", "/myfilecopy").await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfilecopy\" is a directory");
}
#[tokio::test]
async fn copy_should_returns_an_error_if_destination_file_is_new_and_some_ancestor_doesnt_exists(
) {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("my file content"),
),
]));
let err = fs.copy("/myfile", "/somedir/myfilecopy").await.unwrap_err();
assert_eq!(fs.files.read().await.len(), 2);
assert_eq!(err.to_string(), "ancestor \"/somedir\" doesn't exists");
}
#[tokio::test]
async fn copy_should_returns_an_error_if_destination_file_is_new_and_some_ancestor_is_not_a_directory(
) {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("my file content"),
),
(
OsString::from_str("/mypath").unwrap(),
InMemoryFile::empty(),
),
]));
let err = fs.copy("/myfile", "/mypath/myfilecopy").await.unwrap_err();
assert_eq!(fs.files.read().await.len(), 3);
assert_eq!(err.to_string(), "ancestor \"/mypath\" is not a directory");
}
#[tokio::test]
async fn set_mode_should_update_the_file_mode_at_path() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(
OsString::from_str("/myfile").unwrap(),
InMemoryFile::file("my file content"),
),
]));
assert!(
matches!(fs.files.read().await.get(&OsString::from_str("/myfile").unwrap()).unwrap(), InMemoryFile::File { mode, .. } if *mode == 0o664)
);
fs.set_mode("/myfile", 0o400).await.unwrap();
assert!(
matches!(fs.files.read().await.get(&OsString::from_str("/myfile").unwrap()).unwrap(), InMemoryFile::File { mode, .. } if *mode == 0o400)
);
}
#[tokio::test]
async fn set_mode_should_update_the_directory_mode_at_path() {
let fs = InMemoryFileSystem::new(HashMap::from([
(OsString::from_str("/").unwrap(), InMemoryFile::dir()),
(OsString::from_str("/mydir").unwrap(), InMemoryFile::dir()),
]));
assert!(
matches!(fs.files.read().await.get(&OsString::from_str("/mydir").unwrap()).unwrap(), InMemoryFile::Directory { mode } if *mode == 0o775)
);
fs.set_mode("/mydir", 0o700).await.unwrap();
assert!(
matches!(fs.files.read().await.get(&OsString::from_str("/mydir").unwrap()).unwrap(), InMemoryFile::Directory { mode } if *mode == 0o700)
);
}
#[tokio::test]
async fn set_mode_should_returns_an_error_if_file_doesnt_exists() {
let fs = InMemoryFileSystem::new(HashMap::from([(
OsString::from_str("/").unwrap(),
InMemoryFile::dir(),
)]));
// intentionally forget to create file
let err = fs.set_mode("/myfile", 0o400).await.unwrap_err();
assert_eq!(err.to_string(), "file \"/myfile\" not found");
}
}
@@ -0,0 +1,390 @@
use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::Path};
use async_trait::async_trait;
use tokio::io::AsyncWriteExt;
use super::{FileSystem, FileSystemError, FileSystemResult};
#[derive(Default, Debug, Clone)]
pub struct LocalFileSystem;
#[async_trait]
impl FileSystem for LocalFileSystem {
async fn create_dir<P>(&self, path: P) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
{
tokio::fs::create_dir(path).await.map_err(Into::into)
}
async fn create_dir_all<P>(&self, path: P) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
{
tokio::fs::create_dir_all(path).await.map_err(Into::into)
}
async fn read<P>(&self, path: P) -> FileSystemResult<Vec<u8>>
where
P: AsRef<Path> + Send,
{
tokio::fs::read(path).await.map_err(Into::into)
}
async fn read_to_string<P>(&self, path: P) -> FileSystemResult<String>
where
P: AsRef<Path> + Send,
{
tokio::fs::read_to_string(path).await.map_err(Into::into)
}
async fn write<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
C: AsRef<[u8]> + Send,
{
tokio::fs::write(path, contents).await.map_err(Into::into)
}
async fn append<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
C: AsRef<[u8]> + Send,
{
let contents = contents.as_ref();
let mut file = tokio::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.await
.map_err(Into::<FileSystemError>::into)?;
file.write_all(contents)
.await
.map_err(Into::<FileSystemError>::into)?;
file.flush().await.and(Ok(())).map_err(Into::into)
}
async fn copy<P1, P2>(&self, from: P1, to: P2) -> FileSystemResult<()>
where
P1: AsRef<Path> + Send,
P2: AsRef<Path> + Send,
{
tokio::fs::copy(from, to)
.await
.and(Ok(()))
.map_err(Into::into)
}
async fn set_mode<P>(&self, path: P, mode: u32) -> FileSystemResult<()>
where
P: AsRef<Path> + Send,
{
tokio::fs::set_permissions(path, Permissions::from_mode(mode))
.await
.map_err(Into::into)
}
async fn exists<P>(&self, path: P) -> bool
where
P: AsRef<Path> + Send,
{
path.as_ref().exists()
}
}
#[cfg(test)]
mod tests {
use uuid::Uuid;
use super::*;
const FILE_BITS: u32 = 0o100000;
const DIR_BITS: u32 = 0o40000;
fn setup() -> String {
let test_dir = format!("/tmp/unit_test_{}", Uuid::new_v4());
std::fs::create_dir(&test_dir).unwrap();
test_dir
}
fn teardown(test_dir: String) {
std::fs::remove_dir_all(test_dir).unwrap();
}
#[tokio::test]
async fn create_dir_should_create_a_new_directory_at_path() {
let test_dir = setup();
let fs = LocalFileSystem;
let new_dir = format!("{test_dir}/mynewdir");
fs.create_dir(&new_dir).await.unwrap();
let new_dir_path = Path::new(&new_dir);
assert!(new_dir_path.exists() && new_dir_path.is_dir());
teardown(test_dir);
}
#[tokio::test]
async fn create_dir_should_bubble_up_error_if_some_happens() {
let test_dir = setup();
let fs = LocalFileSystem;
let new_dir = format!("{test_dir}/mynewdir");
// intentionally create new dir before calling function to force error
std::fs::create_dir(&new_dir).unwrap();
let err = fs.create_dir(&new_dir).await.unwrap_err();
assert_eq!(err.to_string(), "File exists (os error 17)");
teardown(test_dir);
}
#[tokio::test]
async fn create_dir_all_should_create_a_new_directory_and_all_of_it_ancestors_at_path() {
let test_dir = setup();
let fs = LocalFileSystem;
let new_dir = format!("{test_dir}/the/path/to/mynewdir");
fs.create_dir_all(&new_dir).await.unwrap();
let new_dir_path = Path::new(&new_dir);
assert!(new_dir_path.exists() && new_dir_path.is_dir());
teardown(test_dir);
}
#[tokio::test]
async fn create_dir_all_should_bubble_up_error_if_some_happens() {
let test_dir = setup();
let fs = LocalFileSystem;
let new_dir = format!("{test_dir}/the/path/to/mynewdir");
// intentionally create new file as ancestor before calling function to force error
std::fs::write(format!("{test_dir}/the"), b"test").unwrap();
let err = fs.create_dir_all(&new_dir).await.unwrap_err();
assert_eq!(err.to_string(), "Not a directory (os error 20)");
teardown(test_dir);
}
#[tokio::test]
async fn read_should_return_the_contents_of_the_file_at_path() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
std::fs::write(&file_path, b"Test").unwrap();
let contents = fs.read(file_path).await.unwrap();
assert_eq!(contents, b"Test");
teardown(test_dir);
}
#[tokio::test]
async fn read_should_bubble_up_error_if_some_happens() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
// intentionally forget to create file to force error
let err = fs.read(file_path).await.unwrap_err();
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
teardown(test_dir);
}
#[tokio::test]
async fn read_to_string_should_return_the_contents_of_the_file_at_path_as_string() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
std::fs::write(&file_path, b"Test").unwrap();
let contents = fs.read_to_string(file_path).await.unwrap();
assert_eq!(contents, "Test");
teardown(test_dir);
}
#[tokio::test]
async fn read_to_string_should_bubble_up_error_if_some_happens() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
// intentionally forget to create file to force error
let err = fs.read_to_string(file_path).await.unwrap_err();
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
teardown(test_dir);
}
#[tokio::test]
async fn write_should_create_a_new_file_at_path_with_contents() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
fs.write(&file_path, "Test").await.unwrap();
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test");
teardown(test_dir);
}
#[tokio::test]
async fn write_should_overwrite_an_existing_file_with_contents() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
std::fs::write(&file_path, "Test").unwrap();
assert_eq!(std::fs::read_to_string(&file_path).unwrap(), "Test");
fs.write(&file_path, "Test updated").await.unwrap();
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test updated");
teardown(test_dir);
}
#[tokio::test]
async fn write_should_bubble_up_error_if_some_happens() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
// intentionally create directory instead of file to force error
std::fs::create_dir(&file_path).unwrap();
let err = fs.write(&file_path, "Test").await.unwrap_err();
assert_eq!(err.to_string(), "Is a directory (os error 21)");
teardown(test_dir);
}
#[tokio::test]
async fn append_should_create_a_new_file_at_path_with_contents() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
fs.append(&file_path, "Test").await.unwrap();
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test");
teardown(test_dir);
}
#[tokio::test]
async fn append_should_updates_an_existing_file_by_appending_contents() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
std::fs::write(&file_path, "Test").unwrap();
assert_eq!(std::fs::read_to_string(&file_path).unwrap(), "Test");
fs.append(&file_path, " updated").await.unwrap();
assert_eq!(std::fs::read_to_string(file_path).unwrap(), "Test updated");
teardown(test_dir);
}
#[tokio::test]
async fn append_should_bubble_up_error_if_some_happens() {
let test_dir = setup();
let fs = LocalFileSystem;
let file_path = format!("{test_dir}/myfile");
// intentionally create directory instead of file to force error
std::fs::create_dir(&file_path).unwrap();
let err = fs.append(&file_path, "Test").await.unwrap_err();
assert_eq!(err.to_string(), "Is a directory (os error 21)");
teardown(test_dir);
}
#[tokio::test]
async fn copy_should_create_a_duplicate_of_source() {
let test_dir = setup();
let fs = LocalFileSystem;
let from_path = format!("{test_dir}/myfile");
std::fs::write(&from_path, "Test").unwrap();
let to_path = format!("{test_dir}/mycopy");
fs.copy(&from_path, &to_path).await.unwrap();
assert_eq!(std::fs::read_to_string(to_path).unwrap(), "Test");
teardown(test_dir);
}
#[tokio::test]
async fn copy_should_ovewrite_destination_if_alread_exists() {
let test_dir = setup();
let fs = LocalFileSystem;
let from_path = format!("{test_dir}/myfile");
std::fs::write(&from_path, "Test").unwrap();
let to_path = format!("{test_dir}/mycopy");
std::fs::write(&from_path, "Some content").unwrap();
fs.copy(&from_path, &to_path).await.unwrap();
assert_eq!(std::fs::read_to_string(to_path).unwrap(), "Some content");
teardown(test_dir);
}
#[tokio::test]
async fn copy_should_bubble_up_error_if_some_happens() {
let test_dir = setup();
let fs = LocalFileSystem;
let from_path = format!("{test_dir}/nonexistentfile");
let to_path = format!("{test_dir}/mycopy");
let err = fs.copy(&from_path, &to_path).await.unwrap_err();
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
teardown(test_dir);
}
#[tokio::test]
async fn set_mode_should_update_the_file_mode_at_path() {
let test_dir = setup();
let fs = LocalFileSystem;
let path = format!("{test_dir}/myfile");
std::fs::write(&path, "Test").unwrap();
assert!(std::fs::metadata(&path).unwrap().permissions().mode() != (FILE_BITS + 0o400));
fs.set_mode(&path, 0o400).await.unwrap();
assert_eq!(
std::fs::metadata(&path).unwrap().permissions().mode(),
FILE_BITS + 0o400
);
teardown(test_dir);
}
#[tokio::test]
async fn set_mode_should_update_the_directory_mode_at_path() {
let test_dir = setup();
let fs = LocalFileSystem;
let path = format!("{test_dir}/mydir");
std::fs::create_dir(&path).unwrap();
assert!(std::fs::metadata(&path).unwrap().permissions().mode() != (DIR_BITS + 0o700));
fs.set_mode(&path, 0o700).await.unwrap();
assert_eq!(
std::fs::metadata(&path).unwrap().permissions().mode(),
DIR_BITS + 0o700
);
teardown(test_dir);
}
#[tokio::test]
async fn set_mode_should_bubble_up_error_if_some_happens() {
let test_dir = setup();
let fs = LocalFileSystem;
let path = format!("{test_dir}/somemissingfile");
// intentionnally don't create file
let err = fs.set_mode(&path, 0o400).await.unwrap_err();
assert_eq!(err.to_string(), "No such file or directory (os error 2)");
teardown(test_dir);
}
}
@@ -0,0 +1,4 @@
pub mod constants;
pub mod fs;
pub mod net;
pub mod replacer;
+60
View File
@@ -0,0 +1,60 @@
use std::{io::Cursor, str::FromStr, time::Duration};
use reqwest::{Method, Request, StatusCode, Url};
use tracing::trace;
use crate::constants::THIS_IS_A_BUG;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
pub async fn download_file(url: String, dest: String) -> Result<()> {
let response = reqwest::get(url).await?;
let mut file = std::fs::File::create(dest)?;
let mut content = Cursor::new(response.bytes().await?);
std::io::copy(&mut content, &mut file)?;
Ok(())
}
pub async fn wait_ws_ready(url: &str) -> Result<()> {
let mut parsed = Url::from_str(url)?;
parsed
.set_scheme("http")
.map_err(|_| anyhow::anyhow!("Can not set the scheme, {THIS_IS_A_BUG}"))?;
let http_client = reqwest::Client::new();
loop {
let req = Request::new(Method::OPTIONS, parsed.clone());
let res = http_client.execute(req).await;
match res {
Ok(res) => {
if res.status() == StatusCode::OK {
// ready to go!
break;
}
trace!("http_client status: {}, continuing...", res.status());
},
Err(e) => {
if !skip_err_while_waiting(&e) {
return Err(e.into());
}
trace!("http_client err: {}, continuing... ", e.to_string());
},
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
Ok(())
}
pub fn skip_err_while_waiting(e: &reqwest::Error) -> bool {
// if the error is connecting/request could be the case that the node
// is not listening yet, so we keep waiting
// Skipped errs like:
// 'tcp connect error: Connection refused (os error 61)'
// 'operation was canceled: connection closed before message completed'
// 'connection error: Connection reset by peer (os error 54)'
e.is_connect() || e.is_request()
}
@@ -0,0 +1,197 @@
use std::collections::{HashMap, HashSet};
use lazy_static::lazy_static;
use regex::{Captures, Regex};
use tracing::{trace, warn};
use crate::constants::{SHOULD_COMPILE, THIS_IS_A_BUG};
lazy_static! {
static ref RE: Regex = Regex::new(r#"\{\{([a-zA-Z0-9_]*)\}\}"#)
.unwrap_or_else(|_| panic!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
static ref TOKEN_PLACEHOLDER: Regex = Regex::new(r#"\{\{ZOMBIE:(.*?):(.*?)\}\}"#)
.unwrap_or_else(|_| panic!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
static ref PLACEHOLDER_COMPAT: HashMap<&'static str, &'static str> = {
let mut m = HashMap::new();
m.insert("multiAddress", "multiaddr");
m.insert("wsUri", "ws_uri");
m.insert("prometheusUri", "prometheus_uri");
m
};
}
/// Return true if the text contains any TOKEN_PLACEHOLDER
pub fn has_tokens(text: &str) -> bool {
TOKEN_PLACEHOLDER.is_match(text)
}
pub fn apply_replacements(text: &str, replacements: &HashMap<&str, &str>) -> String {
let augmented_text = RE.replace_all(text, |caps: &Captures| {
if let Some(replacements_value) = replacements.get(&caps[1]) {
replacements_value.to_string()
} else {
caps[0].to_string()
}
});
augmented_text.to_string()
}
pub fn apply_env_replacements(text: &str) -> String {
let augmented_text = RE.replace_all(text, |caps: &Captures| {
if let Ok(replacements_value) = std::env::var(&caps[1]) {
replacements_value
} else {
caps[0].to_string()
}
});
augmented_text.to_string()
}
pub fn apply_running_network_replacements(text: &str, network: &serde_json::Value) -> String {
let augmented_text = TOKEN_PLACEHOLDER.replace_all(text, |caps: &Captures| {
trace!("appling replacements for caps: {caps:#?}");
if let Some(node) = network.get(&caps[1]) {
trace!("caps1 {} - node: {node}", &caps[1]);
let field = *PLACEHOLDER_COMPAT.get(&caps[2]).unwrap_or(&&caps[2]);
if let Some(val) = node.get(field) {
trace!("caps2 {} - node: {node}", field);
val.as_str().unwrap_or("Invalid string").to_string()
} else {
warn!(
"⚠️ The node with name {} doesn't have the value {} in context",
&caps[1], &caps[2]
);
caps[0].to_string()
}
} else {
warn!("⚠️ No node with name {} in context", &caps[1]);
caps[0].to_string()
}
});
augmented_text.to_string()
}
pub fn get_tokens_to_replace(text: &str) -> HashSet<String> {
let mut tokens = HashSet::new();
TOKEN_PLACEHOLDER
.captures_iter(text)
.for_each(|caps: Captures| {
tokens.insert(caps[1].to_string());
});
tokens
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn replace_should_works() {
let text = "some {{namespace}}";
let mut replacements = HashMap::new();
replacements.insert("namespace", "demo-123");
let res = apply_replacements(text, &replacements);
assert_eq!("some demo-123".to_string(), res);
}
#[test]
fn replace_env_should_works() {
let text = "some {{namespace}}";
std::env::set_var("namespace", "demo-123");
// let mut replacements = HashMap::new();
// replacements.insert("namespace", "demo-123");
let res = apply_env_replacements(text);
assert_eq!("some demo-123".to_string(), res);
}
#[test]
fn replace_multiple_should_works() {
let text = r#"some {{namespace}}
other is {{other}}"#;
let augmented_text = r#"some demo-123
other is other-123"#;
let mut replacements = HashMap::new();
replacements.insert("namespace", "demo-123");
replacements.insert("other", "other-123");
let res = apply_replacements(text, &replacements);
assert_eq!(augmented_text, res);
}
#[test]
fn replace_multiple_with_missing_should_works() {
let text = r#"some {{namespace}}
other is {{other}}"#;
let augmented_text = r#"some demo-123
other is {{other}}"#;
let mut replacements = HashMap::new();
replacements.insert("namespace", "demo-123");
let res = apply_replacements(text, &replacements);
assert_eq!(augmented_text, res);
}
#[test]
fn replace_without_replacement_should_leave_text_unchanged() {
let text = "some {{namespace}}";
let mut replacements = HashMap::new();
replacements.insert("other", "demo-123");
let res = apply_replacements(text, &replacements);
assert_eq!(text.to_string(), res);
}
#[test]
fn replace_running_network_should_work() {
let network = json!({
"alice" : {
"multiaddr": "some/demo/127.0.0.1"
}
});
let res = apply_running_network_replacements("{{ZOMBIE:alice:multiaddr}}", &network);
assert_eq!(res.as_str(), "some/demo/127.0.0.1");
}
#[test]
fn replace_running_network_with_compat_should_work() {
let network = json!({
"alice" : {
"multiaddr": "some/demo/127.0.0.1"
}
});
let res = apply_running_network_replacements("{{ZOMBIE:alice:multiAddress}}", &network);
assert_eq!(res.as_str(), "some/demo/127.0.0.1");
}
#[test]
fn replace_running_network_with_missing_field_should_not_replace_nothing() {
let network = json!({
"alice" : {
"multiaddr": "some/demo/127.0.0.1"
}
});
let res = apply_running_network_replacements("{{ZOMBIE:alice:someField}}", &network);
assert_eq!(res.as_str(), "{{ZOMBIE:alice:someField}}");
}
#[test]
fn get_tokens_to_replace_should_work() {
let res = get_tokens_to_replace("{{ZOMBIE:alice:multiaddr}} {{ZOMBIE:bob:multiaddr}}");
let mut expected = HashSet::new();
expected.insert("alice".to_string());
expected.insert("bob".to_string());
assert_eq!(res, expected);
}
}