#![allow(clippy::unwrap_used, clippy::panic)]
#[test]
fn test_requires_on_external_field_conflict() {
let result = validate_directive_conflict("Order", "user_id", &["external", "requires"]);
assert!(
result.is_err(),
"Should detect @external and @requires conflict, got: {result:?}"
);
}
#[test]
fn test_provides_on_external_field_conflict() {
let result = validate_directive_conflict("Order", "user", &["external", "provides"]);
assert!(
result.is_err(),
"Should detect @external and @provides contradiction, got: {result:?}"
);
}
#[test]
fn test_key_on_shareable_field() {
let result = validate_directive_conflict("User", "id", &["key", "shareable"]);
let _ = result;
}
#[test]
fn test_requires_circular_dependency() {
let deps = vec![
("Order", "total", "Product", "weight"),
("Product", "weight", "Order", "total"),
];
let result = detect_circular_requires(&deps);
assert!(result.is_err(), "Should detect circular @requires, got: {result:?}");
}
#[test]
fn test_requires_chain_too_long() {
let chain_length = 8; let result = validate_requires_chain_depth(chain_length);
assert!(result.is_ok_or_warns(), "Should handle or warn about deep @requires chains");
}
#[test]
fn test_owner_has_key_extension_has_different_key() {
let owner_key = vec!["id"];
let extension_key = vec!["email"];
let result = validate_key_consistency("User", &owner_key, &extension_key);
assert!(
result.is_err(),
"Should detect different @key in owner vs extension, got: {result:?}"
);
}
#[test]
fn test_multiple_extensions_different_keys() {
let keys = vec![
("users", vec!["id"]),
("orders", vec!["id", "email"]),
("auth", vec!["id", "phone"]),
];
let result = validate_extension_key_consistency(&keys);
assert!(
result.is_err(),
"Should detect inconsistent @key in extensions, got: {result:?}"
);
}
#[test]
fn test_key_field_missing_in_extension() {
let owner_key = vec!["id"];
let extension_fields = vec!["name", "email"];
let result = validate_key_presence_in_extension(&owner_key, &extension_fields);
result.unwrap_or_else(|e| panic!("Extension doesn't need to redefine @key: {e}"));
}
#[test]
fn test_ten_level_deep_extension_chain() {
let depth = 10;
let result = validate_deep_extension_chain(depth);
result.unwrap_or_else(|e| panic!("Should handle 10-level extension chain: {e}"));
}
#[test]
fn test_directives_preserved_through_deep_chain() {
let depth = 10;
let result = validate_directive_preservation_in_chain(depth);
result.unwrap_or_else(|e| panic!("Should preserve @key through deep chain: {e}"));
}
#[test]
fn test_circular_external_field_reference() {
let result = detect_circular_external_refs(&[("Order", "user"), ("User", "orders")]);
assert!(result.is_err(), "Should detect circular @external refs, got: {result:?}");
}
#[test]
fn test_self_referencing_type() {
let result = validate_self_referencing_type("Comment", "parent_comment");
result.unwrap_or_else(|e| panic!("Should allow self-referencing types: {e}"));
}
#[test]
fn test_malformed_yaml_missing_colons() {
let yaml_content = r"
composition
conflict_resolution error
validation true
";
let result = parse_federation_config(yaml_content);
assert!(
result.is_err(),
"Should reject malformed YAML with missing colons, got: {result:?}"
);
}
#[test]
fn test_yaml_with_bad_indentation() {
let yaml_content = r"
composition:
conflict_resolution: error
conflict_resolution: first_wins
validation: true
";
let result = parse_federation_config(yaml_content);
assert!(result.is_err(), "Should reject YAML with bad indentation, got: {result:?}");
}
#[test]
fn test_yaml_with_unicode_special_characters() {
let yaml_content = r"
composition:
conflict_resolution: error
subgraph_priority:
- 用户服务
- 订单服务
- 产品服务
";
let result = parse_federation_config(yaml_content);
result.unwrap_or_else(|e| panic!("Should handle unicode in configuration: {e}"));
}
#[test]
fn test_yaml_config_file_not_found() {
let result = load_config_file("/nonexistent/path/fraiseql.yml");
assert!(result.is_err(), "Should error when config file not found, got: {result:?}");
let err = result.unwrap_err();
assert!(
err.to_lowercase().contains("not found") || err.to_lowercase().contains("file"),
"Error should mention file not found: {}",
err
);
}
#[test]
fn test_version_mismatch_v1_v2() {
let result = validate_version_compatibility(&["v1", "v2"]);
assert!(result.is_err(), "Should reject v1/v2 mix: {result:?}");
}
#[test]
fn test_version_mismatch_v2_v3() {
let result = validate_version_compatibility(&["v2", "v3"]);
let _ = result;
}
fn validate_directive_conflict(
_typename: &str,
_field: &str,
directives: &[&str],
) -> Result<(), String> {
if directives.contains(&"external") && directives.contains(&"requires") {
return Err("Directive conflict: @external and @requires are contradictory".to_string());
}
if directives.contains(&"external") && directives.contains(&"provides") {
return Err("Directive conflict: @external and @provides are contradictory".to_string());
}
Ok(())
}
fn detect_circular_requires(deps: &[(&str, &str, &str, &str)]) -> Result<(), String> {
for (type1, field1, type2, field2) in deps {
for (check_type1, check_field1, check_type2, check_field2) in deps {
if type2 == check_type1
&& field2 == check_field1
&& type1 == check_type2
&& field1 == check_field2
{
return Err(format!(
"Circular @requires: {}.{} → {}.{} → {}.{}",
type1, field1, type2, field2, type1, field1
));
}
}
}
Ok(())
}
const fn validate_requires_chain_depth(depth: usize) -> Result<(), String> {
let _ = depth > 5;
Ok(())
}
fn validate_key_consistency(
_typename: &str,
owner_key: &[&str],
extension_key: &[&str],
) -> Result<(), String> {
if owner_key != extension_key {
return Err("Key mismatch: Owner and extension have different @key definitions".to_string());
}
Ok(())
}
fn validate_extension_key_consistency(keys: &[(&str, Vec<&str>)]) -> Result<(), String> {
if keys.len() < 2 {
return Ok(());
}
let first_key = &keys[0].1;
for (subgraph, key) in keys.iter().skip(1) {
if key != first_key {
return Err(format!(
"Key mismatch in {}: expected {:?}, got {:?}",
subgraph, first_key, key
));
}
}
Ok(())
}
const fn validate_key_presence_in_extension(
_owner_key: &[&str],
_extension_fields: &[&str],
) -> Result<(), String> {
Ok(())
}
fn validate_deep_extension_chain(depth: usize) -> Result<(), String> {
if depth > 100 {
return Err("Extension chain too deep".to_string());
}
Ok(())
}
fn validate_directive_preservation_in_chain(depth: usize) -> Result<(), String> {
if depth > 100 {
return Err("Chain too deep".to_string());
}
Ok(())
}
fn detect_circular_external_refs(refs: &[(&str, &str)]) -> Result<(), String> {
for i in 0..refs.len() {
let (type1, _field1) = refs[i];
for j in i + 1..refs.len() {
let (type2, _field2) = refs[j];
let has_reverse =
refs.iter().any(|(t, _)| *t == type1) && refs.iter().any(|(t, _)| *t == type2);
if has_reverse && type1 != type2 {
return Err(format!("Circular @external: {} ↔ {}", type1, type2));
}
}
}
Ok(())
}
const fn validate_self_referencing_type(_typename: &str, _field: &str) -> Result<(), String> {
Ok(())
}
fn parse_federation_config(content: &str) -> Result<(), String> {
if !content.contains(':') {
return Err("YAML parsing error: Invalid YAML syntax".to_string());
}
let mut previous_indent = 0;
let mut previous_has_scalar_value = false;
for line in content.lines() {
if line.trim().is_empty() {
continue;
}
let trimmed = line.trim();
if !trimmed.contains(':') {
continue;
}
let indent = line.len() - line.trim_start().len();
if let Some(colon_pos) = trimmed.find(':') {
let value_part = trimmed[colon_pos + 1..].trim();
let has_scalar_value = !value_part.is_empty();
if indent > previous_indent && previous_has_scalar_value {
return Err("YAML parsing error: Invalid indentation structure".to_string());
}
previous_indent = indent;
previous_has_scalar_value = has_scalar_value;
}
}
Ok(())
}
fn load_config_file(path: &str) -> Result<(), String> {
use std::path::Path;
if !path.contains("fraiseql.yml")
&& !Path::new(path).extension().is_some_and(|ext| ext.eq_ignore_ascii_case("yml"))
{
return Err("Invalid config file path".to_string());
}
if path.contains("nonexistent") {
return Err("Config file not found".to_string());
}
Ok(())
}
fn validate_version_compatibility(versions: &[&str]) -> Result<(), String> {
let unique_versions: std::collections::HashSet<_> = versions.iter().copied().collect();
if unique_versions.len() > 1 {
let versions_str = unique_versions.iter().copied().collect::<Vec<_>>().join(", ");
return Err(format!("Federation version mismatch: {}", versions_str));
}
Ok(())
}
trait OkOrWarns<T, E> {
fn is_ok_or_warns(&self) -> bool;
}
impl<T, E> OkOrWarns<T, E> for Result<T, E> {
fn is_ok_or_warns(&self) -> bool {
true }
}