mod common;
use common::workflow;
use dataflow_rs::engine::message::Message;
use dataflow_rs::{Engine, IssueCode, Severity, Workflow};
use serde_json::{Value, json};
async fn mapped(logic: Value) -> Value {
let engine = Engine::builder()
.with_workflow(workflow(json!({
"id": "w", "name": "w", "priority": 0,
"tasks": [{"id": "t", "name": "t", "function": {"name": "map", "input": {
"mappings": [{"path": "data.out", "logic": logic}]
}}}]
})))
.build()
.expect("engine should build");
let mut message = Message::from_value(&json!({}));
engine
.process_message(&mut message)
.await
.expect("processing should succeed");
Value::from(message.data()).get("out").cloned().unwrap()
}
#[tokio::test]
async fn an_escaped_key_emits_the_literal_object() {
assert_eq!(mapped(json!({"cat": ["a", "b"]})).await, json!("ab"));
assert_eq!(
mapped(json!({"$cat": ["a", "b"]})).await,
json!({"cat": ["a", "b"]})
);
}
#[tokio::test]
async fn exactly_one_prefix_is_stripped() {
assert_eq!(
mapped(json!({"$$oid": "abc"})).await,
json!({"$oid": "abc"})
);
assert_eq!(
mapped(json!({"$$$oid": "abc"})).await,
json!({"$$oid": "abc"})
);
}
#[tokio::test]
async fn the_escape_applies_at_every_depth_and_inside_operators() {
assert_eq!(
mapped(json!({"outer": {"$cat": ["a", "b"]}})).await,
json!({"outer": {"cat": ["a", "b"]}})
);
assert_eq!(
mapped(json!({"if": [true, {"$cat": ["y"]}, 0]})).await,
json!({"cat": ["y"]})
);
assert_eq!(
mapped(json!({"map": [[1, 2], {"$v": {"var": ""}}]})).await,
json!([{"v": 1}, {"v": 2}])
);
}
#[tokio::test]
async fn escaping_leaves_non_colliding_keys_alone_but_still_strips_them() {
assert_eq!(mapped(json!({"$total": 1})).await, json!({"total": 1}));
assert_eq!(mapped(json!({"total": 1})).await, json!({"total": 1}));
}
fn wf_with(logic: Value) -> Workflow {
workflow(json!({
"id": "w", "name": "w", "priority": 0,
"tasks": [{"id": "t", "name": "t", "function": {"name": "map", "input": {
"mappings": [{"path": "data.out", "logic": logic}]
}}}]
}))
}
#[test]
fn keys_that_collide_after_stripping_are_refused_at_build() {
let w = wf_with(json!({"$a": 1, "a": 2}));
let issues = Engine::builder().check_workflow(&w);
let collision = issues
.iter()
.find(|i| i.code == IssueCode::DuplicateTemplateKey)
.unwrap_or_else(|| panic!("check_workflow must report the collision: {issues:?}"));
assert_eq!(collision.severity(), Severity::Rejected);
let err = match Engine::builder().with_workflow(w).build() {
Err(e) => e,
Ok(_) => panic!("build must refuse a duplicate template key"),
};
assert!(err.to_string().contains("emit the key 'a'"), "{err}");
}
#[test]
fn escaped_keys_are_reported_for_audit_but_never_refused() {
let w = wf_with(json!({"$oid": {"var": "data.id"}, "kind": "ref"}));
let issues = Engine::builder().check_workflow(&w);
let escaped: Vec<_> = issues
.iter()
.filter(|i| i.code == IssueCode::EscapedTemplateKey)
.collect();
assert_eq!(escaped.len(), 1, "one escaped key, got {issues:?}");
assert_eq!(
escaped[0].path.as_deref(),
Some("function.input.mappings[0].logic.$oid")
);
assert_eq!(escaped[0].task_id.as_deref(), Some("t"));
assert!(
escaped[0].severity() == Severity::Advisory,
"the escape is the sanctioned spelling, not a defect"
);
assert!(
escaped[0].message.contains("emitted as 'oid'"),
"the message must say what it becomes: {}",
escaped[0].message
);
Engine::builder()
.with_workflow(w)
.build()
.expect("an escaped key is legal — it is the sanctioned spelling");
}
#[test]
fn an_ordinary_single_key_output_template_is_not_reported() {
let issues = Engine::builder().check_workflow(&wf_with(json!({"result": {"var": "data.x"}})));
assert!(
issues.is_empty(),
"no issue for a normal template: {issues:?}"
);
}
#[test]
fn a_custom_tasks_input_is_config_not_a_template() {
let w = workflow(json!({
"id": "w", "name": "w", "priority": 0,
"tasks": [{"id": "t", "name": "t", "function": {"name": "logger", "input": {
"$a": 1, "a": 2
}}}]
}));
Engine::builder()
.register("logger", common::LoggingTask)
.with_workflow(w)
.build()
.expect("a custom input is config, not a template");
}