#![allow(clippy::unwrap_used, clippy::panic, clippy::print_stderr)]
use std::{collections::HashMap, sync::Arc};
use fraiseql_core::{
runtime::Executor,
schema::{
CompiledSchema, FederationConfig, FederationEntity, FieldDefinition, FieldType,
TypeDefinition,
},
};
use serde_json::{Value, json};
use super::common;
const USER_RELATION: &str = "v_entity_nested_user";
fn user_schema() -> CompiledSchema {
let mut schema = CompiledSchema::new();
schema.federation = Some(FederationConfig {
enabled: true,
version: Some("v2".to_string()),
entities: vec![FederationEntity {
name: "User".to_string(),
key_fields: vec!["id".to_string()],
..Default::default()
}],
..Default::default()
});
schema.types.push({
let mut t = TypeDefinition::new("User", USER_RELATION);
t.fields = vec![
FieldDefinition::new("id", FieldType::String),
FieldDefinition::new(
"orders",
FieldType::List(Box::new(FieldType::Object("Order".to_string()))),
),
];
t
});
schema.types.push({
let mut t = TypeDefinition::new("Order", "v_entity_nested_order");
t.fields = vec![
FieldDefinition::new("id", FieldType::String),
FieldDefinition::new("user_id", FieldType::String),
FieldDefinition::new("status", FieldType::String),
FieldDefinition::new("total", FieldType::Float),
FieldDefinition::new("created_at", FieldType::String),
];
t
});
schema.build_indexes();
schema
}
async fn fixture() -> Option<(fraiseql_test_support::Service, Executor)> {
let row: HashMap<String, Value> = std::iter::once((
"data".to_string(),
json!({
"id": "u-1",
"orders": [
{"id": "o-1", "user_id": "u-1", "status": "completed",
"total": 99.99, "created_at": "2026-08-25T16:50:12Z"},
{"id": "o-2", "user_id": "u-1", "status": "pending",
"total": 10.50, "created_at": "2026-08-26T09:01:00Z"}
]
}),
))
.collect();
let (pg, adapter) = common::pg_entity_fixture(USER_RELATION, &["data jsonb"], &[row]).await?;
Some((pg, Executor::new(user_schema(), Arc::clone(&adapter))))
}
fn entities_query(fields: &str) -> String {
format!(
r#"{{ _entities(representations: [{{ __typename: "User", id: "u-1" }}]) {{ ... on User {{ {fields} }} }} }}"#
)
}
fn representations() -> Value {
json!({ "representations": [{ "__typename": "User", "id": "u-1" }] })
}
async fn entity(executor: &Executor, fields: &str) -> Value {
executor
.execute(&entities_query(fields), Some(&representations()))
.await
.expect("the _entities query must resolve")["data"]["_entities"][0]
.clone()
}
fn order_keys(entity: &Value) -> Vec<String> {
let mut keys: Vec<String> = entity["orders"][0]
.as_object()
.unwrap_or_else(|| panic!("expected an object at orders[0], got: {entity}"))
.keys()
.cloned()
.collect();
keys.sort();
keys
}
#[tokio::test]
async fn a_nested_object_returns_only_the_selected_fields() {
let Some((_pg, executor)) = fixture().await else {
eprintln!("SKIP a_nested_object_returns_only_the_selected_fields: no postgres");
return;
};
let user = entity(&executor, "id orders { id }").await;
assert_eq!(
order_keys(&user),
vec!["id".to_string()],
"each order carries five stored keys; the router asked for one: {user}"
);
}
#[tokio::test]
async fn a_nested_object_returns_exactly_the_selected_subset() {
let Some((_pg, executor)) = fixture().await else {
eprintln!("SKIP a_nested_object_returns_exactly_the_selected_subset: no postgres");
return;
};
let user = entity(&executor, "id orders { id status }").await;
assert_eq!(
order_keys(&user),
vec!["id".to_string(), "status".to_string()],
"response was: {user}"
);
}
#[tokio::test]
async fn a_field_selected_inside_a_nested_object_does_not_land_on_the_parent() {
let Some((_pg, executor)) = fixture().await else {
eprintln!(
"SKIP a_field_selected_inside_a_nested_object_does_not_land_on_the_parent: no postgres"
);
return;
};
let user = entity(&executor, "id orders { id status total }").await;
let parent = user.as_object().expect("entity is an object");
assert!(
!parent.contains_key("total"),
"`total` is a field of Order, not of User — the selection parser flattened it \
onto the parent: {user}"
);
assert!(!parent.contains_key("status"), "`status` is likewise not a User field: {user}");
}
#[tokio::test]
async fn every_element_of_the_nested_list_is_projected() {
let Some((_pg, executor)) = fixture().await else {
eprintln!("SKIP every_element_of_the_nested_list_is_projected: no postgres");
return;
};
let user = entity(&executor, "id orders { id }").await;
let orders = user["orders"].as_array().unwrap_or_else(|| panic!("expected a list: {user}"));
assert_eq!(orders.len(), 2, "both stored orders must survive projection: {user}");
for (i, order) in orders.iter().enumerate() {
let keys: Vec<&String> = order.as_object().expect("order is an object").keys().collect();
assert_eq!(keys, vec!["id"], "orders[{i}] was: {order}");
}
}
#[tokio::test]
async fn scalar_fields_and_typename_are_unaffected() {
let Some((_pg, executor)) = fixture().await else {
eprintln!("SKIP scalar_fields_and_typename_are_unaffected: no postgres");
return;
};
let user = entity(&executor, "__typename id").await;
assert_eq!(user["__typename"], json!("User"), "response was: {user}");
assert_eq!(user["id"], json!("u-1"), "response was: {user}");
}