mirror of
https://github.com/gorules/zen.git
synced 2026-10-04 00:02:18 +00:00
chore: move unit tests to integration tests; add more test cases; (#33)
* chore: move unit tests to integration tests; add more test cases; * fix fmt errors
This commit is contained in:
@@ -25,7 +25,7 @@ v8 = { version = "0.66.0" }
|
||||
zen-expression = { path = "../expression", version = "0.4.1" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = "0.4.2"
|
||||
tokio = { version = "1.28", features = ["macros"] }
|
||||
criterion = { workspace = true, features = ["async_futures"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -97,118 +97,3 @@ impl<L: DecisionLoader> DecisionEngine<L> {
|
||||
Ok(self.create_decision(content))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::loader::{FilesystemLoader, FilesystemLoaderOptions, MemoryLoader};
|
||||
use crate::model::DecisionContent;
|
||||
use serde_json::json;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn it_supports_memory_loader() {
|
||||
let mem_loader = MemoryLoader::default();
|
||||
|
||||
mem_loader.add(
|
||||
"table",
|
||||
serde_json::from_str::<DecisionContent>(include_str!("../../../test-data/table.json"))
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
mem_loader.add(
|
||||
"function",
|
||||
serde_json::from_str::<DecisionContent>(include_str!(
|
||||
"../../../test-data/function.json"
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let graph = DecisionEngine::new(mem_loader);
|
||||
let res1 = tokio_test::block_on(graph.evaluate("table", &json!({ "input": 12 })));
|
||||
let res2 = tokio_test::block_on(graph.evaluate("aaa", &json!({ "input": 12 })));
|
||||
|
||||
assert_eq!(res1.unwrap().result, json!({"output": 10}));
|
||||
assert!(res2.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_supports_filesystem_loader() {
|
||||
let cargo_root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let test_data_root = cargo_root.join("../../").join("test-data");
|
||||
let fs_loader = FilesystemLoader::new(FilesystemLoaderOptions {
|
||||
keep_in_memory: true,
|
||||
root: test_data_root.to_str().unwrap(),
|
||||
});
|
||||
|
||||
let graph = DecisionEngine::new(fs_loader);
|
||||
let res1 = tokio_test::block_on(graph.evaluate("table.json", &json!({ "input": 12 })));
|
||||
let res2 = tokio_test::block_on(graph.evaluate("aaa", &json!({ "input": 12 })));
|
||||
|
||||
assert_eq!(res1.unwrap().result, json!({"output": 10}));
|
||||
assert!(res2.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_supports_closure_loader() {
|
||||
let graph = DecisionEngine::async_loader(|_| async {
|
||||
let content = serde_json::from_str::<DecisionContent>(include_str!(
|
||||
"../../../test-data/table.json"
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
Ok(Arc::new(content))
|
||||
});
|
||||
|
||||
let res1 = tokio_test::block_on(graph.evaluate("sample", &json!({ "input": 12 })));
|
||||
let res2 = tokio_test::block_on(graph.evaluate("1", &json!({ "input": 4 })));
|
||||
|
||||
assert_eq!(res1.unwrap().result, json!({"output": 10}));
|
||||
assert_eq!(res2.unwrap().result, json!({"output": 0}))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn it_throws_correct_error_type() {
|
||||
let cargo_root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let test_data_root = cargo_root.join("../../").join("test-data");
|
||||
let fs_loader = FilesystemLoader::new(FilesystemLoaderOptions {
|
||||
keep_in_memory: true,
|
||||
root: test_data_root.to_str().unwrap(),
|
||||
});
|
||||
|
||||
let graph = DecisionEngine::new(fs_loader);
|
||||
|
||||
let infinite_fn =
|
||||
tokio_test::block_on(graph.evaluate("infinite-function.json", &json!({})));
|
||||
|
||||
match infinite_fn.unwrap_err().deref() {
|
||||
EvaluationError::NodeError(e) => {
|
||||
assert_eq!(e.node_id, "e0fd96d0-44dc-4f0e-b825-06e56b442d78");
|
||||
assert_eq!(e.source.to_string(), "Timeout exceeded");
|
||||
}
|
||||
_ => assert!(false, "Wrong error type"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_terminates_when_depth_limit_exceeded() {
|
||||
let cargo_root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let test_data_root = cargo_root.join("../../").join("test-data");
|
||||
let fs_loader = FilesystemLoader::new(FilesystemLoaderOptions {
|
||||
keep_in_memory: true,
|
||||
root: test_data_root.to_str().unwrap(),
|
||||
});
|
||||
|
||||
let graph = DecisionEngine::new(fs_loader);
|
||||
let recursive = tokio_test::block_on(graph.evaluate("recursive-table1.json", &json!({})));
|
||||
|
||||
match recursive.unwrap_err().deref() {
|
||||
EvaluationError::NodeError(e) => {
|
||||
assert_eq!(e.source.to_string(), "Depth limit exceeded")
|
||||
}
|
||||
_ => assert!(false, "Depth limit not exceeded"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,8 +271,8 @@ mod tests {
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
fn decision_table() {
|
||||
#[tokio::test]
|
||||
async fn decision_table() {
|
||||
let content =
|
||||
&serde_json::from_str(include_str!("../../../../test-data/table.json")).unwrap();
|
||||
let tree = DecisionGraph::new(DecisionGraphConfig {
|
||||
@@ -283,15 +283,14 @@ mod tests {
|
||||
loader: Arc::new(MemoryLoader::default()),
|
||||
});
|
||||
|
||||
let result =
|
||||
tokio_test::block_on(async { tree.evaluate(&json!({ "input": 15 })).await.unwrap() });
|
||||
let result = tree.evaluate(&json!({ "input": 15 })).await.unwrap();
|
||||
|
||||
assert_eq!(result.result, json!({ "output": 10 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn function() {
|
||||
async fn function() {
|
||||
let content =
|
||||
&serde_json::from_str(include_str!("../../../../test-data/function.json")).unwrap();
|
||||
let tree = DecisionGraph::new(DecisionGraphConfig {
|
||||
@@ -302,8 +301,7 @@ mod tests {
|
||||
loader: Arc::new(MemoryLoader::default()),
|
||||
});
|
||||
|
||||
let result =
|
||||
tokio_test::block_on(async { tree.evaluate(&json!({ "input": 15 })).await.unwrap() });
|
||||
let result = tree.evaluate(&json!({ "input": 15 })).await.unwrap();
|
||||
|
||||
assert_eq!(result.result, json!({ "output": 30 }));
|
||||
}
|
||||
|
||||
@@ -202,21 +202,21 @@ mod tests {
|
||||
assert_eq!(rk!("a.b.c").cmp(&rk!("a.b.c", 1)), Ordering::Less);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_order() {
|
||||
#[tokio::test]
|
||||
async fn test_insert_order() {
|
||||
let mut o = RowOutput::default();
|
||||
o.push("a", RowOutputKind::Value(json!("abc")));
|
||||
o.push("a.b", RowOutputKind::Value(json!("abc")));
|
||||
o.push("a.b.c", RowOutputKind::Value(json!("abc")));
|
||||
|
||||
assert_eq!(
|
||||
tokio_test::block_on(o.to_json()).unwrap(),
|
||||
o.to_json().await.unwrap(),
|
||||
json!({ "a": { "b": { "c": "abc" } } })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested() {
|
||||
#[tokio::test]
|
||||
async fn test_nested() {
|
||||
let mut o = RowOutput::default();
|
||||
o.push("a.first.deleted", RowOutputKind::Value(json!("deleted")));
|
||||
o.push(
|
||||
@@ -228,7 +228,7 @@ mod tests {
|
||||
o.push("a.third.nested", RowOutputKind::Value(json!("nested")));
|
||||
|
||||
assert_eq!(
|
||||
tokio_test::block_on(o.to_json()).unwrap(),
|
||||
o.to_json().await.unwrap(),
|
||||
json!({
|
||||
"a": {
|
||||
"first": "first",
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use crate::support::{create_fs_loader, load_test_data};
|
||||
use serde_json::json;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use zen_engine::{Decision, EvaluationError};
|
||||
|
||||
mod support;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
async fn decision_from_content() {
|
||||
let table_content = load_test_data("table.json");
|
||||
let decision = Decision::from(table_content);
|
||||
|
||||
let context = json!({ "input": 5 });
|
||||
let result = decision.evaluate(&context).await;
|
||||
|
||||
assert_eq!(result.unwrap().result, json!({"output": 0}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
async fn decision_from_content_recursive() {
|
||||
let recursive_content = load_test_data("recursive-table1.json");
|
||||
let decision = Decision::from(recursive_content);
|
||||
|
||||
let context = json!({});
|
||||
let result = decision.evaluate(&context).await;
|
||||
match result.unwrap_err().deref() {
|
||||
EvaluationError::NodeError(e) => {
|
||||
assert_eq!(e.node_id, "0b8dcf6b-fc04-47cb-bf82-bda764e6c09b");
|
||||
assert!(e.source.to_string().contains("Loader failed"));
|
||||
}
|
||||
_ => assert!(false, "Depth limit not exceeded"),
|
||||
}
|
||||
|
||||
let with_loader = decision.with_loader(Arc::new(create_fs_loader()));
|
||||
let new_result = with_loader.evaluate(&context).await;
|
||||
match new_result.unwrap_err().deref() {
|
||||
EvaluationError::NodeError(e) => {
|
||||
assert_eq!(e.source.to_string(), "Depth limit exceeded")
|
||||
}
|
||||
_ => assert!(false, "Depth limit not exceeded"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
use crate::support::{create_fs_loader, load_test_data};
|
||||
use serde_json::json;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use zen_engine::loader::{LoaderError, MemoryLoader};
|
||||
use zen_engine::{DecisionEngine, EvaluationError, EvaluationOptions};
|
||||
|
||||
mod support;
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
async fn engine_memory_loader() {
|
||||
let memory_loader = Arc::new(MemoryLoader::default());
|
||||
memory_loader.add("table", load_test_data("table.json"));
|
||||
memory_loader.add("function", load_test_data("function.json"));
|
||||
|
||||
let engine = DecisionEngine::new_arc(memory_loader.clone());
|
||||
let table = engine.evaluate("table", &json!({ "input": 12 })).await;
|
||||
let function = engine.evaluate("function", &json!({ "input": 12 })).await;
|
||||
|
||||
memory_loader.remove("function");
|
||||
let not_found = engine.evaluate("function", &json!({})).await;
|
||||
|
||||
assert_eq!(table.unwrap().result, json!({"output": 10}));
|
||||
assert_eq!(function.unwrap().result, json!({"output": 24}));
|
||||
assert_eq!(not_found.unwrap_err().to_string(), "Loader error");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
async fn engine_filesystem_loader() {
|
||||
let engine = DecisionEngine::new(create_fs_loader());
|
||||
let table = engine.evaluate("table.json", &json!({ "input": 12 })).await;
|
||||
let function = engine
|
||||
.evaluate("function.json", &json!({ "input": 12 }))
|
||||
.await;
|
||||
let not_found = engine.evaluate("invalid_file", &json!({})).await;
|
||||
|
||||
assert_eq!(table.unwrap().result, json!({"output": 10}));
|
||||
assert_eq!(function.unwrap().result, json!({"output": 24}));
|
||||
assert_eq!(not_found.unwrap_err().to_string(), "Loader error");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
async fn engine_closure_loader() {
|
||||
let engine = DecisionEngine::async_loader(|key| {
|
||||
// TODO: Improve once async closures become stable in Rust
|
||||
let mv_key = key.to_string();
|
||||
|
||||
async move {
|
||||
match mv_key.as_str() {
|
||||
"function" => Ok(Arc::new(load_test_data("function.json"))),
|
||||
"table" => Ok(Arc::new(load_test_data("table.json"))),
|
||||
_ => Err(LoaderError::NotFound(mv_key).into()),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let table = engine.evaluate("table", &json!({ "input": 12 })).await;
|
||||
let function = engine.evaluate("function", &json!({ "input": 12 })).await;
|
||||
let not_found = engine.evaluate("invalid_file", &json!({})).await;
|
||||
|
||||
assert_eq!(table.unwrap().result, json!({"output": 10}));
|
||||
assert_eq!(function.unwrap().result, json!({"output": 24}));
|
||||
assert_eq!(not_found.unwrap_err().to_string(), "Loader error");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn engine_noop_loader() {
|
||||
// Default engine is noop
|
||||
let engine = DecisionEngine::default();
|
||||
let result = engine.evaluate("any.json", &json!({})).await;
|
||||
|
||||
assert_eq!(result.unwrap_err().to_string(), "Loader error");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn engine_get_decision() {
|
||||
let engine = DecisionEngine::new(create_fs_loader());
|
||||
|
||||
assert!(engine.get_decision("table.json").await.is_ok());
|
||||
assert!(engine.get_decision("any.json").await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn engine_create_decision() {
|
||||
let engine = DecisionEngine::default();
|
||||
engine.create_decision(load_test_data("table.json").into());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
async fn engine_errors() {
|
||||
let engine = DecisionEngine::new(create_fs_loader());
|
||||
|
||||
let infinite_fn = engine.evaluate("infinite-function.json", &json!({})).await;
|
||||
match infinite_fn.unwrap_err().deref() {
|
||||
EvaluationError::NodeError(e) => {
|
||||
assert_eq!(e.node_id, "e0fd96d0-44dc-4f0e-b825-06e56b442d78");
|
||||
assert_eq!(e.source.to_string(), "Timeout exceeded");
|
||||
}
|
||||
_ => assert!(false, "Wrong error type"),
|
||||
}
|
||||
|
||||
let recursive = engine.evaluate("recursive-table1.json", &json!({})).await;
|
||||
match recursive.unwrap_err().deref() {
|
||||
EvaluationError::NodeError(e) => {
|
||||
assert_eq!(e.source.to_string(), "Depth limit exceeded")
|
||||
}
|
||||
_ => assert!(false, "Depth limit not exceeded"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn engine_with_trace() {
|
||||
let engine = DecisionEngine::new(create_fs_loader());
|
||||
|
||||
let table_r = engine.evaluate("table.json", &json!({ "input": 12 })).await;
|
||||
let table_opt_r = engine
|
||||
.evaluate_with_opts(
|
||||
"table.json",
|
||||
&json!({ "input": 12 }),
|
||||
EvaluationOptions {
|
||||
trace: Some(true),
|
||||
max_depth: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let table = table_r.unwrap();
|
||||
let table_opt = table_opt_r.unwrap();
|
||||
|
||||
assert!(table.trace.is_none());
|
||||
assert!(table_opt.trace.is_some());
|
||||
|
||||
let trace = table_opt.trace.unwrap();
|
||||
assert_eq!(trace.len(), 3); // trace for each node
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
mod support;
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
mod bincode_tests {
|
||||
use crate::support::load_test_data;
|
||||
use bincode::config;
|
||||
use zen_engine::model::DecisionContent;
|
||||
|
||||
#[test]
|
||||
fn jdm_bincode() {
|
||||
let decision_content = load_test_data("table.json");
|
||||
let cache_slice_r = bincode::encode_to_vec(&decision_content, config::standard());
|
||||
|
||||
assert!(cache_slice_r.is_ok(), "Bincode serialisation failed");
|
||||
|
||||
let cache_slice = cache_slice_r.unwrap();
|
||||
let decode_res =
|
||||
bincode::decode_from_slice::<DecisionContent, _>(&cache_slice, config::standard());
|
||||
|
||||
assert!(decode_res.is_ok(), "Bincode deserialization failed");
|
||||
|
||||
let decoded_decision_content = decode_res.unwrap();
|
||||
assert_eq!(decoded_decision_content.0, decision_content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use zen_engine::loader::{FilesystemLoader, FilesystemLoaderOptions};
|
||||
use zen_engine::model::DecisionContent;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn test_data_root() -> String {
|
||||
let cargo_root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
cargo_root
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("test-data")
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn load_test_data(key: &str) -> DecisionContent {
|
||||
let file = File::open(Path::new(&test_data_root()).join(key)).unwrap();
|
||||
let reader = BufReader::new(file);
|
||||
serde_json::from_reader(reader).unwrap()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_fs_loader() -> FilesystemLoader {
|
||||
FilesystemLoader::new(FilesystemLoaderOptions {
|
||||
keep_in_memory: false,
|
||||
root: test_data_root(),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user