Skip to content

Commit

Permalink
More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
EmilyMatt committed Jun 5, 2024
1 parent 1d63b55 commit a60b6c2
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 43 deletions.
11 changes: 11 additions & 0 deletions src/client/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,17 @@ impl FalkorSyncClient {
}
}

#[cfg(test)]
pub(crate) fn create_empty_client() -> Arc<FalkorSyncClientInner> {
let (tx, rx) = mpsc::sync_channel(1);
Arc::new(FalkorSyncClientInner {
_inner: Mutex::new(FalkorClientProvider::None),
connection_pool_size: 0,
connection_pool_tx: RwLock::new(tx),
connection_pool_rx: Mutex::new(rx),
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
3 changes: 3 additions & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub(crate) mod blocking;
pub(crate) mod builder;

pub(crate) enum FalkorClientProvider {
None,
#[cfg(feature = "redis")]
Redis {
client: redis::Client,
Expand All @@ -34,6 +35,7 @@ impl FalkorClientProvider {
.get_connection()
.map_err(|err| FalkorDBError::RedisError(err.to_string()))?,
),
FalkorClientProvider::None => Err(FalkorDBError::UnavailableProvider)?,
})
}

Expand All @@ -44,6 +46,7 @@ impl FalkorClientProvider {
) {
match self {
FalkorClientProvider::Redis { sentinel, .. } => *sentinel = Some(sentinel_client),
FalkorClientProvider::None => {}
}
}
}
139 changes: 116 additions & 23 deletions src/graph/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
Constraint, ExecutionPlan, FalkorDBError, FalkorIndex, FalkorParsable, FalkorResponse,
FalkorResult, FalkorValue, ResultSet, SyncGraph,
};
use std::ops::Not;
use std::{collections::HashMap, fmt::Display, marker::PhantomData};

pub(crate) fn construct_query<Q: Display, T: Display, Z: Display>(
Expand All @@ -22,7 +23,12 @@ pub(crate) fn construct_query<Q: Display, T: Display, Z: Display>(
.collect::<Vec<_>>()
.join(" ")
})
.map(|params_str| format!("CYPHER {params_str} "))
.and_then(|params_str| {
params_str
.is_empty()
.not()
.then_some(format!("CYPHER {params_str} "))
})
.unwrap_or_default();
format!("{params_str}{query_str}")
}
Expand Down Expand Up @@ -356,30 +362,117 @@ mod tests {
use super::*;

#[test]
fn test_generate_procedure_call() {
let (query, params) = generate_procedure_call(
"DB.CONSTRAINTS",
Some(&["Hello", "World"]),
Some(&["Foo", "Bar"]),
);

assert_eq!(query, "CALL DB.CONSTRAINTS($Hello,$World) YIELD Foo,Bar");
assert!(params.is_some());

let params = params.unwrap();
assert_eq!(params["param0"], "Hello");
assert_eq!(params["param1"], "World");
fn test_generate_procedure_call_no_args_no_yields() {
let procedure = "my_procedure";
let args: Option<&[String]> = None;
let yields: Option<&[String]> = None;

let expected_query = "CALL my_procedure()".to_string();
let expected_params: Option<HashMap<String, String>> = None;

let result = generate_procedure_call(procedure, args, yields);

assert_eq!(result, (expected_query, expected_params));
}

#[test]
fn test_generate_procedure_call_with_args_no_yields() {
let procedure = "my_procedure";
let args = &["arg1".to_string(), "arg2".to_string()];
let yields: Option<&[String]> = None;

let expected_query = "CALL my_procedure($arg1,$arg2)".to_string();
let mut expected_params = HashMap::new();
expected_params.insert("param0".to_string(), "arg1".to_string());
expected_params.insert("param1".to_string(), "arg2".to_string());

let result = generate_procedure_call(procedure, Some(args), yields);

assert_eq!(result, (expected_query, Some(expected_params)));
}

#[test]
fn test_generate_procedure_call_no_args_with_yields() {
let procedure = "my_procedure";
let args: Option<&[String]> = None;
let yields = &["yield1".to_string(), "yield2".to_string()];

let expected_query = "CALL my_procedure() YIELD yield1,yield2".to_string();
let expected_params: Option<HashMap<String, String>> = None;

let result = generate_procedure_call(procedure, args, Some(yields));

assert_eq!(result, (expected_query, expected_params));
}

#[test]
fn test_generate_procedure_call_with_args_and_yields() {
let procedure = "my_procedure";
let args = &["arg1".to_string(), "arg2".to_string()];
let yields = &["yield1".to_string(), "yield2".to_string()];

let expected_query = "CALL my_procedure($arg1,$arg2) YIELD yield1,yield2".to_string();
let mut expected_params = HashMap::new();
expected_params.insert("param0".to_string(), "arg1".to_string());
expected_params.insert("param1".to_string(), "arg2".to_string());

let result = generate_procedure_call(procedure, Some(args), Some(yields));

assert_eq!(result, (expected_query, Some(expected_params)));
}

#[test]
fn test_construct_query_with_params() {
let query_str = "MATCH (n) RETURN n";
let mut params = HashMap::new();
params.insert("name", "Alice");
params.insert("age", "30");

let result = construct_query(query_str, Some(&params));
assert!(result.starts_with("CYPHER "));
assert!(result.ends_with(" RETURN n"));
assert!(result.contains(" name=Alice "));
assert!(result.contains(" age=30 "));
}

#[test]
fn test_construct_query_without_params() {
let query_str = "MATCH (n) RETURN n";
let result = construct_query::<&str, &str, &str>(query_str, None);
assert_eq!(result, "MATCH (n) RETURN n");
}

#[test]
fn test_construct_query_empty_params() {
let query_str = "MATCH (n) RETURN n";
let params: HashMap<&str, &str> = HashMap::new();
let result = construct_query(query_str, Some(&params));
assert_eq!(result, "MATCH (n) RETURN n");
}

#[test]
fn test_construct_query_single_param() {
let query_str = "MATCH (n) RETURN n";
let mut params = HashMap::new();
params.insert("name", "Alice");

let result = construct_query(query_str, Some(&params));
assert_eq!(result, "CYPHER name=Alice MATCH (n) RETURN n");
}

#[test]
fn test_construct_query() {
let query = construct_query("MATCH (a:actor) WITH a MATCH (b:actor) WHERE a.age = b.age AND a <> b RETURN a, collect(b) LIMIT 100",
Some(&HashMap::from([("Foo", "Bar"), ("Bizz", "Bazz")])));
assert!(query.starts_with("CYPHER "));
assert!(query.ends_with(" MATCH (a:actor) WITH a MATCH (b:actor) WHERE a.age = b.age AND a <> b RETURN a, collect(b) LIMIT 100"));

// Order not guaranteed
assert!(query.contains(" Foo=Bar "));
assert!(query.contains(" Bizz=Bazz "));
fn test_construct_query_multiple_params() {
let query_str = "MATCH (n) RETURN n";
let mut params = HashMap::new();
params.insert("name", "Alice");
params.insert("age", "30");
params.insert("city", "Wonderland");

let result = construct_query(query_str, Some(&params));
assert!(result.starts_with("CYPHER "));
assert!(result.contains(" name=Alice "));
assert!(result.contains(" age=30 "));
assert!(result.contains(" city=Wonderland "));
assert!(result.ends_with("MATCH (n) RETURN n"));
}
}
152 changes: 132 additions & 20 deletions src/value/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,142 @@ impl FalkorParsable for HashMap<String, FalkorValue> {
))?;
}

Ok(val_vec
.chunks_exact(2)
.flat_map(|pair| {
let [key, val]: [FalkorValue; 2] = pair.to_vec().try_into().map_err(|_| {
let mut out_map = HashMap::with_capacity(val_vec.len());
for pair in val_vec.chunks_exact(2) {
let [key, val]: [FalkorValue; 2] = pair.to_vec().try_into().map_err(|_| {
FalkorDBError::ParsingFMap(
"The vec returned from using chunks_exact(2) should be comprised of 2 elements"
.to_string(),
)
})?;

let [type_marker, val]: [FalkorValue; 2] =
val.into_vec()?.try_into().map_err(|_| {
FalkorDBError::ParsingFMap(
"The vec returned from using chunks_exact(2) should be comprised of 2 elements"
"The value in a map should be comprised of a type marker and value"
.to_string(),
)
})?;

let [type_marker, val]: [FalkorValue; 2] = val
.into_vec()?
.try_into()
.map_err(|_| FalkorDBError::ParsingFMap("The value in a map should be comprised of a type marker and value".to_string()))?;

FalkorResult::<_>::Ok((
key.into_string()?,
parse_type(
type_marker.to_i64().ok_or(FalkorDBError::ParsingKTVTypes)?,
val,
graph_schema,
)?,
))
})
.collect())
out_map.insert(
key.into_string()?,
parse_type(
type_marker.to_i64().ok_or(FalkorDBError::ParsingKTVTypes)?,
val,
graph_schema,
)?,
);
}

Ok(out_map)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{client::blocking::create_empty_client, GraphSchema};

#[test]
fn test_not_a_vec() {
let mut graph_schema = GraphSchema::new("test_graph", create_empty_client());

let res: FalkorResult<HashMap<String, FalkorValue>> = FalkorParsable::from_falkor_value(
FalkorValue::String("Hello".to_string()),
&mut graph_schema,
);

assert!(res.is_err())
}

#[test]
fn test_vec_odd_element_count() {
let mut graph_schema = GraphSchema::new("test_graph", create_empty_client());

let res: FalkorResult<HashMap<String, FalkorValue>> = FalkorParsable::from_falkor_value(
FalkorValue::Array(vec![FalkorValue::None; 7]),
&mut graph_schema,
);

assert!(res.is_err())
}

#[test]
fn test_val_element_is_not_array() {
let mut graph_schema = GraphSchema::new("test_graph", create_empty_client());

let res: FalkorResult<HashMap<String, FalkorValue>> = FalkorParsable::from_falkor_value(
FalkorValue::Array(vec![
FalkorValue::String("Key".to_string()),
FalkorValue::Bool(false),
]),
&mut graph_schema,
);

assert!(res.is_err())
}

#[test]
fn test_val_element_has_only_1_element() {
let mut graph_schema = GraphSchema::new("test_graph", create_empty_client());

let res: FalkorResult<HashMap<String, FalkorValue>> = FalkorParsable::from_falkor_value(
FalkorValue::Array(vec![
FalkorValue::String("Key".to_string()),
FalkorValue::Array(vec![FalkorValue::I64(7)]),
]),
&mut graph_schema,
);

assert!(res.is_err())
}

#[test]
fn test_val_element_has_ge_2_elements() {
let mut graph_schema = GraphSchema::new("test_graph", create_empty_client());

let res: FalkorResult<HashMap<String, FalkorValue>> = FalkorParsable::from_falkor_value(
FalkorValue::Array(vec![
FalkorValue::String("Key".to_string()),
FalkorValue::Array(vec![FalkorValue::I64(3); 3]),
]),
&mut graph_schema,
);

assert!(res.is_err())
}

#[test]
fn test_val_element_mismatch_type_marker() {
let mut graph_schema = GraphSchema::new("test_graph", create_empty_client());

let res: FalkorResult<HashMap<String, FalkorValue>> = FalkorParsable::from_falkor_value(
FalkorValue::Array(vec![
FalkorValue::String("Key".to_string()),
FalkorValue::Array(vec![FalkorValue::I64(3), FalkorValue::Bool(true)]),
]),
&mut graph_schema,
);

assert!(res.is_err())
}

#[test]
fn test_ok_values() {
let mut graph_schema = GraphSchema::new("test_graph", create_empty_client());

let res: HashMap<String, FalkorValue> = FalkorParsable::from_falkor_value(
FalkorValue::Array(vec![
FalkorValue::String("IntKey".to_string()),
FalkorValue::Array(vec![FalkorValue::I64(3), FalkorValue::I64(1)]),
FalkorValue::String("BoolKey".to_string()),
FalkorValue::Array(vec![FalkorValue::I64(4), FalkorValue::Bool(true)]),
]),
&mut graph_schema,
)
.expect("Could not parse map");

assert_eq!(res.get("IntKey"), Some(FalkorValue::I64(1)).as_ref());
assert_eq!(res.get("BoolKey"), Some(FalkorValue::Bool(true)).as_ref());
}
}
Loading

0 comments on commit a60b6c2

Please sign in to comment.