use std::collections::{BTreeMap, HashMap};
use insta::{assert_debug_snapshot, with_settings};
use crate::config::{PathMap, KV};
use crate::{Error, Provider};
pub const ROOT_PATH_A: &str = "secret/development";
pub const ROOT_PATH_B: &str = "secret/multiple/app-1";
pub const ROOT_PATH_C: &str = "secret/multiple/app-2";
pub const ROOT_PATH_PAGING: &str = "secret/multiple/lotsakeys";
const PATH_A_KEY_1: &str = "db";
const PATH_A_KEY_2: &str = "log_level";
const PATH_A_KEY_3: &str = "app";
const PATH_A_VALUE_1: &str = "{\"DB_PASS\": \"1234\",\"DB_NAME\": \"FOO\"}";
const PATH_A_VALUE_2: &str = "DEBUG";
const PATH_A_VALUE_3: &str = "Teller";
const PATH_B_KEY_1: &str = "log_level";
const PATH_B_VALUE_1: &str = "DEBUG";
const PATH_C_KEY_1: &str = "foo";
const PATH_C_VALUE_1: &str = "bar";
const PATH_C_VALUE_1_UPDATE: &str = "baz";
pub struct ProviderTest {
pub root_prefix: Option<String>,
pub provider: Box<dyn Provider + Send + Sync>,
}
impl ProviderTest {
pub fn new(provider: Box<dyn Provider + Send + Sync>) -> Self {
Self {
root_prefix: None,
provider,
}
}
pub fn with_root_prefix(mut self, root_prefix: &str) -> Self {
self.root_prefix = Some(root_prefix.to_string());
self
}
pub async fn run(&self) {
let path_tree = self.get_tree();
self.validate_get_unexisting_key().await;
self.validate_put(&path_tree).await;
self.validate_get(&path_tree).await;
self.validate_get_selective(&path_tree).await;
self.validate_update().await;
self.validate_delete().await;
self.validate_delete_keys().await;
}
fn get_tree(&self) -> HashMap<&str, Vec<KV>> {
HashMap::from([
(
ROOT_PATH_A,
vec![
KV::from_literal(
"",
PATH_A_KEY_1,
PATH_A_VALUE_1,
self.provider.as_ref().kind(),
),
KV::from_literal(
"",
PATH_A_KEY_2,
PATH_A_VALUE_2,
self.provider.as_ref().kind(),
),
KV::from_literal(
"",
PATH_A_KEY_3,
PATH_A_VALUE_3,
self.provider.as_ref().kind(),
),
],
),
(
ROOT_PATH_B,
vec![KV::from_literal(
"",
PATH_B_KEY_1,
PATH_B_VALUE_1,
self.provider.as_ref().kind(),
)],
),
(
ROOT_PATH_C,
vec![KV::from_literal(
"",
PATH_C_KEY_1,
PATH_C_VALUE_1,
self.provider.as_ref().kind(),
)],
),
(
ROOT_PATH_PAGING,
(0..100)
.map(|idx| {
KV::from_literal(
"",
&format!("{PATH_C_KEY_1}_{idx}"),
PATH_C_VALUE_1,
self.provider.as_ref().kind(),
)
})
.collect::<Vec<_>>(),
),
])
}
fn get_key_path(&self, root_path: &str) -> String {
self.root_prefix.as_ref().map_or_else(
|| root_path.to_string(),
|prefix| format!("{prefix}{root_path}"),
)
}
async fn validate_get_unexisting_key(&self) {
let res = self
.provider
.as_ref()
.get(&PathMap::from_path(&format!("{ROOT_PATH_A}/invalid-path")))
.await;
println!("validate_get_unexisting_key: {res:?}");
assert!(res.is_err());
}
async fn validate_put(&self, path_tree: &HashMap<&str, Vec<KV>>) {
for (root_path, keys) in path_tree {
let path_map = PathMap::from_path(&self.get_key_path(root_path));
let res = self.provider.as_ref().put(&path_map, keys).await;
println!("validate_put: {res:?}");
assert!(res.is_ok());
assert_debug_snapshot!(format!("[put-{}]", root_path.replace('/', "_"),), res);
}
}
async fn validate_get(&self, path_tree: &HashMap<&str, Vec<KV>>) {
for root_path in path_tree.keys() {
let res = self
.provider
.as_ref()
.get(&PathMap::from_path(&self.get_key_path(root_path)))
.await;
println!("validate_get: {res:?}");
assert!(res.is_ok());
let mut res = res.unwrap();
res.sort_by(|a: &KV, b| a.value.cmp(&b.value));
res.sort_by(|a: &KV, b| a.key.cmp(&b.key));
with_settings!({filters => vec![
(format!("{:?}", self.provider.as_ref().kind().kind).as_str(), "PROVIDER_KIND"),
(format!("{:?}", self.provider.as_ref().kind().name).as_str(), "PROVIDER_NAME"),
(format!("\".*{ROOT_PATH_A}").as_str(), format!("\"{ROOT_PATH_A}").as_str()),
(format!("\".*{ROOT_PATH_B}").as_str(), format!("\"{ROOT_PATH_B}").as_str()),
(format!("\".*{ROOT_PATH_C}").as_str(), format!("\"{ROOT_PATH_C}").as_str()),
(format!("\".*{ROOT_PATH_PAGING}").as_str(), format!("\"{ROOT_PATH_PAGING}").as_str()),
]}, {
assert_debug_snapshot!(
format!("[get-{}]", root_path.replace('/', "_"),),
res
);
});
}
}
async fn validate_get_selective(&self, path_tree: &HashMap<&str, Vec<KV>>) {
let mut selective_pm = PathMap::from_path(&self.get_key_path(ROOT_PATH_A));
selective_pm
.keys
.insert(PATH_A_KEY_1.to_string(), PATH_A_KEY_1.to_string());
let res = self.provider.as_ref().get(&selective_pm).await;
println!("validate_get_selective: {res:?}");
assert!(res.is_ok());
with_settings!({filters => vec![
(format!("{:?}", self.provider.as_ref().kind().kind).as_str(), "PROVIDER_KIND"),
(format!("{:?}", self.provider.as_ref().kind().name).as_str(), "PROVIDER_NAME"),
(format!("\".*{ROOT_PATH_A}").as_str(), format!("\"{ROOT_PATH_A}").as_str()),
(format!("\".*{ROOT_PATH_B}").as_str(), format!("\"{ROOT_PATH_B}").as_str()),
(format!("\".*{ROOT_PATH_C}").as_str(), format!("\"{ROOT_PATH_C}").as_str()),
(format!("\".*{ROOT_PATH_PAGING}").as_str(), format!("\"{ROOT_PATH_PAGING}").as_str()),
]}, {
assert_debug_snapshot!(
format!("[get-selective-{}]", PATH_A_KEY_1.replace('/', "_"),),
res
);
});
}
async fn validate_update(&self) {
let update_keys = vec![KV::from_literal(
"",
PATH_C_KEY_1,
PATH_C_VALUE_1_UPDATE,
self.provider.as_ref().kind(),
)];
let update_res = self
.provider
.as_ref()
.put(
&PathMap::from_path(&self.get_key_path(ROOT_PATH_C)),
&update_keys,
)
.await;
assert!(update_res.is_ok());
assert_debug_snapshot!(
format!("[put-update-{}]", ROOT_PATH_C.replace('/', "_"),),
update_res
);
let read_after_update_res = self
.provider
.as_ref()
.get(&PathMap::from_path(&self.get_key_path(ROOT_PATH_C)))
.await;
assert!(read_after_update_res.is_ok());
with_settings!({filters => vec![
(format!("{:?}", self.provider.as_ref().kind().kind).as_str(), "PROVIDER_KIND"),
(format!("{:?}", self.provider.as_ref().kind().name).as_str(), "PROVIDER_NAME"),
(format!("\".*{ROOT_PATH_A}").as_str(), format!("\"{ROOT_PATH_A}").as_str()),
(format!("\".*{ROOT_PATH_B}").as_str(), format!("\"{ROOT_PATH_B}").as_str()),
(format!("\".*{ROOT_PATH_C}").as_str(), format!("\"{ROOT_PATH_C}").as_str()),
]}, {
assert_debug_snapshot!(
format!("[get-after-update-{}]", ROOT_PATH_C.replace('/', "_"),),
read_after_update_res
);
});
}
async fn validate_delete(&self) {
let delete_res = self
.provider
.as_ref()
.del(&PathMap::from_path(&self.get_key_path(ROOT_PATH_B)))
.await;
assert!(delete_res.is_ok());
assert_debug_snapshot!(
format!("[del-{}]", ROOT_PATH_B.replace('/', "_"),),
delete_res
);
let get_del_res = self
.provider
.as_ref()
.get(&PathMap::from_path(&self.get_key_path(ROOT_PATH_B)))
.await;
assert!(matches!(
get_del_res,
Err(Error::NotFound { path: _, msg: _ })
));
assert!(get_del_res.is_err());
}
async fn validate_delete_keys(&self) {
let mut path_path = PathMap::from_path(&self.get_key_path(ROOT_PATH_A));
path_path.keys = BTreeMap::from([
(PATH_A_KEY_2.to_string(), String::new()),
(PATH_A_KEY_3.to_string(), String::new()),
]);
let delete_keys_res = self.provider.as_ref().del(&path_path).await;
println!("validate_delete_keys: {delete_keys_res:#?}");
assert!(delete_keys_res.is_ok());
assert_debug_snapshot!(
format!("[del-keys-{}]", ROOT_PATH_A.replace('/', "_")),
delete_keys_res
);
let get_del_res = self
.provider
.as_ref()
.get(&PathMap::from_path(&self.get_key_path(ROOT_PATH_A)))
.await;
with_settings!({filters => vec![
(format!("{:?}", self.provider.as_ref().kind().kind).as_str(), "PROVIDER_KIND"),
(format!("{:?}", self.provider.as_ref().kind().name).as_str(), "PROVIDER_NAME"),
(format!("\".*{ROOT_PATH_A}").as_str(), format!("\"{ROOT_PATH_A}").as_str()),
]}, {
assert_debug_snapshot!(
format!("[get-del-keys-{}]", ROOT_PATH_A.replace('/', "_")),
get_del_res
);
});
}
}