#![allow(clippy::unwrap_used, clippy::panic)]
use std::collections::HashMap;
use serde_json::{Value, json};
use super::{contains_defer, split};
use crate::graphql::types::{Directive, FieldSelection, GraphQLArgument};
fn field(name: &str) -> FieldSelection {
FieldSelection {
name: name.to_owned(),
alias: None,
arguments: vec![],
nested_fields: vec![],
directives: vec![],
}
}
fn with_children(name: &str, children: Vec<FieldSelection>) -> FieldSelection {
FieldSelection {
nested_fields: children,
..field(name)
}
}
fn defer(args: Vec<(&str, &str, &str)>) -> Directive {
Directive {
name: "defer".to_owned(),
arguments: args
.into_iter()
.map(|(name, value_type, value_json)| GraphQLArgument {
name: name.to_owned(),
value_type: value_type.to_owned(),
value_json: value_json.to_owned(),
})
.collect(),
}
}
fn deferred(mut selection: FieldSelection, directive: Directive) -> FieldSelection {
selection.directives.push(directive);
selection
}
fn no_vars() -> HashMap<String, Value> {
HashMap::new()
}
#[test]
fn a_document_without_defer_is_untouched() {
let selections = vec![with_children("user", vec![field("id"), field("email")])];
let mut data = json!({"user": {"id": 1, "email": "a@b.c"}});
assert!(!contains_defer(&selections, &no_vars()));
let payloads = split(&selections, &mut data, &no_vars());
assert!(payloads.is_empty());
assert_eq!(data, json!({"user": {"id": 1, "email": "a@b.c"}}));
}
#[test]
fn a_deferred_field_leaves_data_and_is_addressed_by_its_parent_path() {
let selections = vec![with_children(
"user",
vec![field("id"), deferred(field("email"), defer(vec![]))],
)];
let mut data = json!({"user": {"id": 1, "email": "a@b.c"}});
assert!(contains_defer(&selections, &no_vars()));
let payloads = split(&selections, &mut data, &no_vars());
assert_eq!(
data,
json!({"user": {"id": 1}}),
"the deferred key must leave the immediate payload"
);
assert_eq!(payloads.len(), 1);
assert_eq!(payloads[0].path, vec![json!("user")]);
assert_eq!(payloads[0].data, json!({"email": "a@b.c"}).as_object().cloned().unwrap());
}
#[test]
fn fields_sharing_a_path_and_label_are_grouped_into_one_payload() {
let d = defer(vec![("label", "string", "\"details\"")]);
let selections = vec![with_children(
"user",
vec![
field("id"),
deferred(field("email"), d.clone()),
deferred(field("bio"), d),
],
)];
let mut data = json!({"user": {"id": 1, "email": "a@b.c", "bio": "hi"}});
let payloads = split(&selections, &mut data, &no_vars());
assert_eq!(payloads.len(), 1, "one fragment, one payload: {payloads:?}");
assert_eq!(payloads[0].label.as_deref(), Some("details"));
assert_eq!(
payloads[0].data,
json!({"email": "a@b.c", "bio": "hi"}).as_object().cloned().unwrap()
);
}
#[test]
fn different_labels_under_one_parent_stay_separate() {
let selections = vec![with_children(
"user",
vec![
deferred(field("email"), defer(vec![("label", "string", "\"contact\"")])),
deferred(field("bio"), defer(vec![("label", "string", "\"profile\"")])),
],
)];
let mut data = json!({"user": {"email": "a@b.c", "bio": "hi"}});
let payloads = split(&selections, &mut data, &no_vars());
assert_eq!(payloads.len(), 2);
assert_eq!(payloads[0].label.as_deref(), Some("contact"));
assert_eq!(payloads[1].label.as_deref(), Some("profile"));
}
#[test]
fn each_list_element_gets_its_own_indexed_path() {
let selections = vec![with_children(
"users",
vec![field("id"), deferred(field("email"), defer(vec![]))],
)];
let mut data = json!({"users": [
{"id": 1, "email": "one@x"},
{"id": 2, "email": "two@x"},
]});
let payloads = split(&selections, &mut data, &no_vars());
assert_eq!(data, json!({"users": [{"id": 1}, {"id": 2}]}));
assert_eq!(payloads.len(), 2, "one payload per element: {payloads:?}");
assert_eq!(payloads[0].path, vec![json!("users"), json!(0)]);
assert_eq!(payloads[1].path, vec![json!("users"), json!(1)]);
assert_eq!(payloads[1].data, json!({"email": "two@x"}).as_object().cloned().unwrap());
}
#[test]
fn a_deferred_root_field_is_addressed_by_the_empty_path() {
let selections = vec![deferred(
with_children("user", vec![field("id")]),
defer(vec![]),
)];
let mut data = json!({"user": {"id": 1}});
let payloads = split(&selections, &mut data, &no_vars());
assert_eq!(data, json!({}));
assert_eq!(payloads.len(), 1);
assert!(payloads[0].path.is_empty(), "root-level defer has no path: {payloads:?}");
assert_eq!(payloads[0].data, json!({"user": {"id": 1}}).as_object().cloned().unwrap());
}
#[test]
fn defer_if_false_is_not_a_defer() {
let selections = vec![with_children(
"user",
vec![deferred(
field("email"),
defer(vec![("if", "boolean", "false")]),
)],
)];
let mut data = json!({"user": {"email": "a@b.c"}});
assert!(
!contains_defer(&selections, &no_vars()),
"an if:false @defer must not arm the split"
);
let payloads = split(&selections, &mut data, &no_vars());
assert!(payloads.is_empty());
assert_eq!(data, json!({"user": {"email": "a@b.c"}}));
}
#[test]
fn defer_if_resolves_a_variable() {
let selections = vec![with_children(
"user",
vec![deferred(
field("email"),
defer(vec![("if", "variable", r#"{"$var":"slow"}"#)]),
)],
)];
let mut vars = HashMap::new();
vars.insert("slow".to_owned(), json!(false));
assert!(!contains_defer(&selections, &vars));
vars.insert("slow".to_owned(), json!(true));
assert!(contains_defer(&selections, &vars));
let mut data = json!({"user": {"email": "a@b.c"}});
let payloads = split(&selections, &mut data, &vars);
assert_eq!(payloads.len(), 1);
}
#[test]
fn a_deferred_field_absent_from_the_response_produces_no_payload() {
let selections = vec![with_children(
"user",
vec![field("id"), deferred(field("email"), defer(vec![]))],
)];
let mut data = json!({"user": {"id": 1}});
let payloads = split(&selections, &mut data, &no_vars());
assert!(payloads.is_empty(), "no key removed, so no payload: {payloads:?}");
assert_eq!(data, json!({"user": {"id": 1}}));
}
#[test]
fn the_split_keys_on_the_alias_not_the_field_name() {
let mut aliased = field("email");
aliased.alias = Some("contactEmail".to_owned());
let selections = vec![with_children(
"user",
vec![deferred(aliased, defer(vec![]))],
)];
let mut data = json!({"user": {"contactEmail": "a@b.c", "email": "wrong"}});
let payloads = split(&selections, &mut data, &no_vars());
assert_eq!(
data,
json!({"user": {"email": "wrong"}}),
"the aliased key is the one that leaves; the same-named unaliased key stays"
);
assert_eq!(payloads[0].data, json!({"contactEmail": "a@b.c"}).as_object().cloned().unwrap());
}
#[test]
fn the_outermost_defer_decides_the_group() {
let selections = vec![with_children(
"user",
vec![FieldSelection {
directives: vec![
defer(vec![("label", "string", "\"outer\"")]),
defer(vec![("label", "string", "\"inner\"")]),
],
..field("email")
}],
)];
let mut data = json!({"user": {"email": "a@b.c"}});
let payloads = split(&selections, &mut data, &no_vars());
assert_eq!(payloads.len(), 1);
assert_eq!(payloads[0].label.as_deref(), Some("outer"));
}
#[test]
fn the_split_descends_through_undeferred_nesting() {
let selections = vec![with_children(
"user",
vec![with_children(
"profile",
vec![field("name"), deferred(field("bio"), defer(vec![]))],
)],
)];
let mut data = json!({"user": {"profile": {"name": "n", "bio": "b"}}});
let payloads = split(&selections, &mut data, &no_vars());
assert_eq!(data, json!({"user": {"profile": {"name": "n"}}}));
assert_eq!(payloads[0].path, vec![json!("user"), json!("profile")]);
}