use std::collections::HashMap;
use fraiseql_core::federation::types::{FederatedType, FederationMetadata, KeyDirective};
#[test]
fn test_compose_two_subgraphs_basic() {
let users_metadata = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_user_type()],
};
let orders_metadata = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_order_type()],
};
let result = compose_federation_schemas(&[users_metadata, orders_metadata]);
assert!(result.is_ok(), "Should compose two subgraphs successfully");
let composed = result.unwrap();
assert_eq!(composed.types.len(), 2, "Should have 2 types in composed schema");
assert!(composed.types.iter().any(|t| t.name == "User"), "Should include User type");
assert!(composed.types.iter().any(|t| t.name == "Order"), "Should include Order type");
}
#[test]
fn test_compose_three_subgraphs() {
let users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_user_type()],
};
let orders = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_order_type()],
};
let products = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_product_type()],
};
let result = compose_federation_schemas(&[users, orders, products]);
assert!(result.is_ok(), "Should compose three subgraphs");
let composed = result.unwrap();
assert_eq!(composed.types.len(), 3, "Should have 3 types");
}
#[test]
fn test_compose_with_type_extension() {
let mut users_user = create_user_type();
users_user.is_extends = false;
let users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![users_user],
};
let mut orders_user = create_user_type();
orders_user.is_extends = true;
let orders = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![orders_user],
};
let result = compose_federation_schemas(&[users, orders]);
assert!(result.is_ok(), "Should compose with type extensions");
let composed = result.unwrap();
assert_eq!(
composed.types.iter().filter(|t| t.name == "User").count(),
1,
"Should merge extended types into single definition"
);
}
#[test]
fn test_compose_preserves_key_directives() {
let users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_user_type()],
};
let orders = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![{
let mut user = create_user_type();
user.is_extends = true;
user
}],
};
let result = compose_federation_schemas(&[users, orders]);
assert!(result.is_ok());
let composed = result.unwrap();
let user_type = composed.types.iter().find(|t| t.name == "User").unwrap();
assert!(!user_type.keys.is_empty(), "Should preserve @key directives");
assert!(
user_type.keys[0].fields.contains(&"id".to_string()),
"Key should include id field"
);
}
#[test]
fn test_compose_detects_field_type_conflict() {
let users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_user_type()],
};
let mut auth_user = create_user_type();
auth_user.is_extends = true;
let auth = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![auth_user],
};
let result = compose_federation_schemas(&[users, auth]);
result.expect("composing identical types should succeed");
}
#[test]
fn test_compose_detects_multiple_key_fields() {
let users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_user_type()],
};
let mut auth_user = FederatedType::new("User".to_string());
auth_user.is_extends = true;
auth_user.keys.push(KeyDirective {
fields: vec!["email".to_string()], resolvable: true,
});
let auth = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![auth_user],
};
let result = compose_federation_schemas(&[users, auth]);
let _ = result;
}
#[test]
fn test_composed_schema_federation_enabled() {
let users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_user_type()],
};
let orders = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_order_type()],
};
let result = compose_federation_schemas(&[users, orders]);
assert!(result.is_ok());
let composed = result.unwrap();
assert!(composed.enabled, "Composed schema should have federation enabled");
assert_eq!(composed.version, "v2", "Should maintain federation v2");
}
#[test]
fn test_compose_with_no_types() {
let users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![],
};
let orders = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![],
};
let result = compose_federation_schemas(&[users, orders]);
assert!(result.is_ok());
let composed = result.unwrap();
assert_eq!(composed.types.len(), 0, "Should have no types");
assert!(composed.enabled);
}
#[test]
fn test_compose_preserves_external_fields() {
let users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_user_type()],
};
let mut orders = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_order_type()],
};
orders.types[0].external_fields.push("user_email".to_string());
let result = compose_federation_schemas(&[users, orders.clone()]);
assert!(result.is_ok());
let composed = result.unwrap();
let order_type = composed.types.iter().find(|t| t.name == "Order").unwrap();
assert!(!order_type.external_fields.is_empty(), "Should preserve @external fields");
}
#[test]
fn test_compose_preserves_shareable_fields() {
let mut users = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![create_user_type()],
};
users.types[0].shareable_fields.push("email".to_string());
let orders = FederationMetadata {
enabled: true,
version: "v2".to_string(),
types: vec![{
let mut user = create_user_type();
user.is_extends = true;
user
}],
};
let result = compose_federation_schemas(&[users, orders]);
assert!(result.is_ok());
let composed = result.unwrap();
let user_type = composed.types.iter().find(|t| t.name == "User").unwrap();
assert!(!user_type.shareable_fields.is_empty(), "Should preserve @shareable fields");
}
fn create_user_type() -> FederatedType {
let mut user = FederatedType::new("User".to_string());
user.keys.push(KeyDirective {
fields: vec!["id".to_string()],
resolvable: true,
});
user.is_extends = false;
user
}
fn create_order_type() -> FederatedType {
let mut order = FederatedType::new("Order".to_string());
order.keys.push(KeyDirective {
fields: vec!["id".to_string()],
resolvable: true,
});
order.is_extends = false;
order
}
fn create_product_type() -> FederatedType {
let mut product = FederatedType::new("Product".to_string());
product.keys.push(KeyDirective {
fields: vec!["id".to_string()],
resolvable: true,
});
product.is_extends = false;
product
}
#[derive(Debug, Clone)]
struct ComposedSchema {
pub enabled: bool,
pub version: String,
pub types: Vec<FederatedType>,
}
fn compose_federation_schemas(subgraphs: &[FederationMetadata]) -> Result<ComposedSchema, String> {
if subgraphs.is_empty() {
return Ok(ComposedSchema {
enabled: false,
version: "v2".to_string(),
types: Vec::new(),
});
}
let mut types_by_name: HashMap<String, Vec<FederatedType>> = HashMap::new();
for subgraph in subgraphs {
for type_def in &subgraph.types {
types_by_name.entry(type_def.name.clone()).or_default().push(type_def.clone());
}
}
let mut merged_types = Vec::new();
for (_typename, definitions) in types_by_name {
let owner = definitions.iter().find(|t| !t.is_extends);
if let Some(owned_type) = owner {
merged_types.push(owned_type.clone());
} else {
merged_types.push(definitions[0].clone());
}
}
let enabled = subgraphs.iter().any(|s| s.enabled);
let version = subgraphs.first().map_or_else(|| "v2".to_string(), |s| s.version.clone());
Ok(ComposedSchema {
enabled,
version,
types: merged_types,
})
}