#![allow(clippy::unwrap_used)]
use fraiseql_server::routes::api::design::{
CategoryAuditResponse, DesignAuditRequest, DesignIssueResponse,
};
use serde_json::json;
fn minimal_schema() -> serde_json::Value {
json!({
"types": [
{
"name": "Query",
"fields": [
{"name": "hello", "type": "String"}
]
}
]
})
}
fn over_federated_schema() -> serde_json::Value {
json!({
"subgraphs": [
{
"name": "users",
"entities": ["User"]
},
{
"name": "posts",
"entities": ["User", "Post"]
},
{
"name": "comments",
"entities": ["User", "Comment"]
}
],
"types": [
{
"name": "User",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "name", "type": "String"}
]
},
{
"name": "Post",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "title", "type": "String"}
]
},
{
"name": "Comment",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "text", "type": "String"}
]
}
]
})
}
#[test]
fn test_federation_audit_request_deserialization() {
let req = DesignAuditRequest {
schema: minimal_schema(),
};
assert!(req.schema.get("types").is_some());
}
#[test]
fn test_federation_audit_response_structure() {
let response = CategoryAuditResponse {
score: 85,
issues: vec![],
};
assert_eq!(response.score, 85);
assert!(response.issues.is_empty());
}
#[test]
fn test_federation_audit_with_issue() {
let issue = DesignIssueResponse {
severity: "warning".to_string(),
message: "User entity spread across 3 subgraphs".to_string(),
suggestion: "Consolidate in primary subgraph".to_string(),
affected: Some("User".to_string()),
};
assert_eq!(issue.severity, "warning");
assert_eq!(issue.affected, Some("User".to_string()));
}
#[test]
fn test_federation_audit_detects_jsonb_fragmentation() {
let schema = over_federated_schema();
assert!(schema.get("subgraphs").is_some());
let subgraphs = schema["subgraphs"].as_array().unwrap();
assert_eq!(subgraphs.len(), 3);
let mut user_count = 0;
for subgraph in subgraphs {
if let Some(entities) = subgraph.get("entities").and_then(|e| e.as_array()) {
if entities.iter().any(|e| e.as_str() == Some("User")) {
user_count += 1;
}
}
}
assert_eq!(user_count, 3, "User should appear in 3 subgraphs for this test");
}
#[test]
fn test_federation_audit_empty_schema() {
let response = CategoryAuditResponse {
score: 100,
issues: vec![],
};
assert_eq!(response.score, 100);
}
fn deep_nesting_schema() -> serde_json::Value {
json!({
"types": [
{
"name": "Query",
"fields": [
{"name": "user", "type": "User"}
]
},
{
"name": "User",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "posts", "type": "[Post!]"}
]
},
{
"name": "Post",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "comments", "type": "[Comment!]"}
]
},
{
"name": "Comment",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "text", "type": "String"}
]
}
]
})
}
#[test]
fn test_cost_audit_response_structure() {
let response = CategoryAuditResponse {
score: 75,
issues: vec![],
};
assert_eq!(response.score, 75);
}
#[test]
fn test_cost_audit_with_complexity_warning() {
let issue = DesignIssueResponse {
severity: "critical".to_string(),
message: "Query can reach 1250 complexity in worst case".to_string(),
suggestion: "Add depth limit or paginate nested fields".to_string(),
affected: Some("complexity: 1250".to_string()),
};
assert_eq!(issue.severity, "critical");
assert!(issue.message.contains("complexity"));
}
#[test]
fn test_cost_audit_detects_deep_nesting() {
let schema = deep_nesting_schema();
let types = schema["types"].as_array().unwrap();
let user_type = types.iter().find(|t| t["name"] == "User").unwrap();
let posts_field = user_type["fields"].as_array().unwrap().iter().find(|f| f["name"] == "posts");
assert!(posts_field.is_some());
}
#[test]
fn test_cost_audit_unbounded_pagination() {
let schema = json!({
"types": [
{
"name": "Query",
"fields": [
{"name": "allUsers", "type": "[User!]"} ]
},
{
"name": "User",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true}
]
}
]
});
assert!(schema["types"][0]["fields"][0].get("limit").is_none());
}
fn ttl_mismatch_schema() -> serde_json::Value {
json!({
"subgraphs": [
{
"name": "users",
"entities": ["User"],
"cacheTtlSeconds": 300 },
{
"name": "posts",
"entities": ["Post"],
"references": [
{"type": "User", "via": "users", "cacheTtlSeconds": 1800} ]
}
],
"types": [
{
"name": "User",
"fields": [{"name": "id", "type": "ID", "isPrimaryKey": true}]
},
{
"name": "Post",
"fields": [{"name": "id", "type": "ID", "isPrimaryKey": true}]
}
]
})
}
#[test]
fn test_cache_audit_response_structure() {
let response = CategoryAuditResponse {
score: 90,
issues: vec![],
};
assert_eq!(response.score, 90);
assert!(response.issues.is_empty());
}
#[test]
fn test_cache_audit_with_ttl_issue() {
let issue = DesignIssueResponse {
severity: "warning".to_string(),
message: "User cached 5min in users-service, 30min in posts-service".to_string(),
suggestion: "Align TTLs for consistent JSONB coherency".to_string(),
affected: Some("User".to_string()),
};
assert_eq!(issue.severity, "warning");
assert!(issue.message.contains("cached"));
}
#[test]
fn test_cache_audit_ttl_mismatch() {
let schema = ttl_mismatch_schema();
let subgraphs = schema["subgraphs"].as_array().unwrap();
assert_eq!(subgraphs.len(), 2);
let users_ttl = subgraphs[0]["cacheTtlSeconds"].as_i64().unwrap();
let posts_ttl = subgraphs[1]["references"][0]["cacheTtlSeconds"].as_i64().unwrap();
assert_ne!(users_ttl, posts_ttl);
}
#[test]
fn test_cache_audit_missing_directives() {
let schema = json!({
"types": [
{
"name": "Query",
"fields": [
{
"name": "expensiveField",
"type": "String",
"cached": false }
]
}
]
});
assert!(!schema["types"][0]["fields"][0]["cached"].as_bool().unwrap_or(false));
}
fn auth_boundary_leak_schema() -> serde_json::Value {
json!({
"types": [
{
"name": "User",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{
"name": "email",
"type": "String",
"requiresAuth": true, "requiredScopes": ["user:read"]
}
]
}
],
"subgraphs": [
{
"name": "users",
"entities": ["User"]
},
{
"name": "analytics",
"references": [
{
"type": "User",
"via": "users",
"canAccessFields": ["id", "email"] }
]
}
]
})
}
#[test]
fn test_auth_audit_response_structure() {
let response = CategoryAuditResponse {
score: 88,
issues: vec![],
};
assert_eq!(response.score, 88);
}
#[test]
fn test_auth_audit_with_boundary_issue() {
let issue = DesignIssueResponse {
severity: "critical".to_string(),
message: "User.email exposed to analytics-service without auth scope".to_string(),
suggestion: "Add auth boundary check or restrict field access".to_string(),
affected: Some("User.email".to_string()),
};
assert_eq!(issue.severity, "critical");
assert!(issue.message.contains("auth"));
}
#[test]
fn test_auth_audit_boundary_leak() {
let schema = auth_boundary_leak_schema();
let email_field = &schema["types"][0]["fields"][1];
assert_eq!(email_field["name"], "email");
assert!(email_field["requiresAuth"].as_bool().unwrap_or(false));
}
#[test]
fn test_auth_audit_missing_directives() {
let schema = json!({
"types": [
{
"name": "Mutation",
"fields": [
{
"name": "deleteUser",
"type": "Boolean",
"requiresAuth": false }
]
}
]
});
let mutation_field = &schema["types"][0]["fields"][0];
assert!(!mutation_field["requiresAuth"].as_bool().unwrap_or(false));
}
fn circular_types_schema() -> serde_json::Value {
json!({
"types": [
{
"name": "User",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "posts", "type": "[Post!]"} ]
},
{
"name": "Post",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "author", "type": "User"} ]
}
]
})
}
#[test]
fn test_compilation_audit_response_structure() {
let response = CategoryAuditResponse {
score: 80,
issues: vec![],
};
assert_eq!(response.score, 80);
}
#[test]
fn test_compilation_audit_with_circular_issue() {
let issue = DesignIssueResponse {
severity: "warning".to_string(),
message: "Circular type reference: User -> Post -> User".to_string(),
suggestion: "Break cycle by making one direction reference-only".to_string(),
affected: Some("User".to_string()),
};
assert_eq!(issue.severity, "warning");
assert!(issue.message.contains("Circular"));
}
#[test]
fn test_compilation_audit_circular_types() {
let schema = circular_types_schema();
let types = schema["types"].as_array().unwrap();
let user_type = types.iter().find(|t| t["name"] == "User").unwrap();
let post_type = types.iter().find(|t| t["name"] == "Post").unwrap();
let user_has_posts =
user_type["fields"].as_array().unwrap().iter().any(|f| f["name"] == "posts");
assert!(user_has_posts);
let post_has_author =
post_type["fields"].as_array().unwrap().iter().any(|f| f["name"] == "author");
assert!(post_has_author);
}
#[test]
fn test_compilation_audit_missing_primary_keys() {
let schema = json!({
"types": [
{
"name": "User",
"fields": [
{"name": "id", "type": "ID"}, {"name": "name", "type": "String"}
]
}
]
});
let id_field = &schema["types"][0]["fields"][0];
assert!(!id_field.get("isPrimaryKey").and_then(|v| v.as_bool()).unwrap_or(false));
}
#[test]
fn test_design_audit_request_creation() {
let req = DesignAuditRequest {
schema: minimal_schema(),
};
assert!(req.schema.get("types").is_some());
}
#[test]
fn test_design_audit_response_has_all_categories() {
let categories = vec![
"federation",
"cost",
"cache",
"authorization",
"compilation",
];
for category in categories {
assert!(!category.is_empty());
}
}
#[test]
fn test_design_audit_score_range() {
let test_scores = vec![0u8, 50, 100];
for score in test_scores {
assert!(score <= 100, "Score {} should be <= 100", score);
}
}
#[test]
fn test_design_audit_severity_counts() {
use fraiseql_server::routes::api::design::SeverityCountResponse;
let counts = SeverityCountResponse {
critical: 1,
warning: 3,
info: 5,
};
assert_eq!(counts.critical, 1);
assert_eq!(counts.warning, 3);
assert_eq!(counts.info, 5);
}
#[test]
fn test_design_audit_issue_has_suggestion() {
let issue = DesignIssueResponse {
severity: "warning".to_string(),
message: "Some issue".to_string(),
suggestion: "Fix by doing X".to_string(),
affected: None,
};
assert!(!issue.suggestion.is_empty());
}
#[test]
fn test_design_audit_request_with_empty_schema() {
let req = DesignAuditRequest { schema: json!({}) };
assert!(req.schema.is_object());
}
#[test]
fn test_design_audit_request_with_null_fields() {
let req = DesignAuditRequest {
schema: json!({"types": null}),
};
assert!(req.schema.get("types").is_some());
}
#[test]
fn test_design_issue_required_fields() {
let issue = DesignIssueResponse {
severity: "critical".to_string(),
message: "Test issue".to_string(),
suggestion: "Fix this".to_string(),
affected: None,
};
assert!(!issue.severity.is_empty());
assert!(!issue.message.is_empty());
assert!(!issue.suggestion.is_empty());
}
#[test]
fn test_federation_issue_content() {
let issue = DesignIssueResponse {
severity: "critical".to_string(),
message: "User entity spread across 3 subgraphs prevents efficient JSONB batching"
.to_string(),
suggestion: "Move User to primary subgraph, use references elsewhere".to_string(),
affected: Some("User".to_string()),
};
assert!(["critical", "warning", "info"].contains(&issue.severity.as_str()));
assert!(!issue.message.is_empty());
assert!(!issue.suggestion.is_empty());
}
#[test]
fn test_cost_warning_content() {
let issue = DesignIssueResponse {
severity: "warning".to_string(),
message: "Nested lists create O(n²) compiled JSONB cardinality".to_string(),
suggestion: "Add pagination limits or reduce nesting depth".to_string(),
affected: Some("complexity: 1500".to_string()),
};
assert!(issue.message.contains("cardinality") || issue.message.contains("complexity"));
assert!(!issue.suggestion.is_empty());
}
#[test]
fn test_issue_suggestion_is_specific() {
let good_suggestion = "Move User to primary subgraph, use references elsewhere".to_string();
let bad_suggestion = "Fix this issue".to_string();
assert!(good_suggestion.len() > bad_suggestion.len());
assert!(good_suggestion.contains("primary subgraph") || good_suggestion.contains("references"));
}
#[test]
fn test_minimal_schema_audit() {
let schema = minimal_schema();
assert!(schema.get("types").is_some());
let types = schema["types"].as_array().unwrap();
assert!(!types.is_empty());
}
#[test]
fn test_complex_schema_audit() {
let schema = over_federated_schema();
assert!(schema.get("subgraphs").is_some());
assert!(schema.get("types").is_some());
}
#[test]
fn test_score_improves_with_fixes() {
let problematic = over_federated_schema();
let improved = json!({
"subgraphs": [
{
"name": "users",
"entities": ["User"]
}
],
"types": [
{
"name": "User",
"fields": [{"name": "id", "type": "ID", "isPrimaryKey": true}]
}
]
});
assert!(problematic.get("types").is_some());
assert!(improved.get("types").is_some());
let prob_subgraph_count = problematic["subgraphs"].as_array().unwrap().len();
let imp_subgraph_count = improved["subgraphs"].as_array().unwrap().len();
assert!(imp_subgraph_count <= prob_subgraph_count);
}
#[test]
fn test_schema_with_all_required_fields() {
let schema = json!({
"types": [
{
"name": "User",
"fields": [
{"name": "id", "type": "ID", "isPrimaryKey": true},
{"name": "name", "type": "String"}
]
}
]
});
let user_type = &schema["types"][0];
assert_eq!(user_type["name"], "User");
assert!(user_type["fields"].as_array().is_some());
}
#[test]
fn test_schema_federation_structure() {
let schema = over_federated_schema();
if let Some(subgraphs) = schema["subgraphs"].as_array() {
for subgraph in subgraphs {
assert!(subgraph.get("name").is_some());
assert!(subgraph.get("entities").is_some());
}
}
}