#![allow(clippy::unwrap_used, clippy::print_stdout, clippy::print_stderr)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::missing_errors_doc)] #![allow(missing_docs)] #![allow(clippy::items_after_statements)] #![allow(clippy::todo)]
use std::{collections::HashMap, sync::Arc};
use fraiseql_functions::{
FunctionDefinition, FunctionModule, FunctionObserver, RuntimeType, TriggerRegistry,
};
use fraiseql_server::subsystems::{BeforeMutationHooks, FunctionsSubsystem, ServerSubsystems};
#[test]
fn test_platform_e2e_server_subsystems_none_is_all_absent() {
let subsystems = ServerSubsystems::none();
assert!(!subsystems.is_storage_enabled());
assert!(!subsystems.is_functions_enabled());
assert!(!subsystems.is_realtime_enabled());
}
#[test]
fn test_platform_e2e_before_mutation_hooks_construction() {
let defs = vec![FunctionDefinition::new(
"validate",
"before:mutation:createUser",
RuntimeType::Deno,
)];
let registry = TriggerRegistry::load_from_definitions(&defs).unwrap();
let observer = Arc::new(FunctionObserver::new());
let module_registry: HashMap<String, FunctionModule> = HashMap::new();
let hooks = BeforeMutationHooks {
trigger_registry: registry,
module_registry,
observer,
};
assert!(
hooks.trigger_registry.before_chain("createUser").is_some(),
"should find before:mutation chain for createUser"
);
assert!(
hooks.trigger_registry.before_chain("deleteUser").is_none(),
"should return None for unregistered mutation"
);
}
#[test]
fn test_platform_e2e_functions_subsystem_full_construction() {
let defs = vec![
FunctionDefinition::new("validate", "before:mutation:createUser", RuntimeType::Deno),
FunctionDefinition::new("onCreated", "after:mutation:User:insert", RuntimeType::Deno),
FunctionDefinition::new("dailyJob", "cron:0 2 * * *", RuntimeType::Deno),
];
let trigger_registry = TriggerRegistry::load_from_definitions(&defs).unwrap();
let observer = Arc::new(FunctionObserver::new());
let module_registry: HashMap<String, FunctionModule> = HashMap::new();
let config = fraiseql_server::schema::loader::FunctionsConfig {
definitions: defs,
module_dir: std::env::temp_dir().join("fraiseql_test_functions"),
};
let subsystem = FunctionsSubsystem {
observer,
trigger_registry,
module_registry,
config,
};
assert_eq!(subsystem.trigger_registry.before_mutation_count(), 1);
assert_eq!(subsystem.trigger_registry.cron_trigger_count(), 1);
}
#[test]
fn test_platform_e2e_registry_to_cron_scheduler_pipeline() {
let defs = vec![
FunctionDefinition::new("dailyCleanup", "cron:0 2 * * *", RuntimeType::Deno),
FunctionDefinition::new("hourlySync", "cron:0 * * * *", RuntimeType::Deno),
FunctionDefinition::new("validate", "before:mutation:createUser", RuntimeType::Deno),
];
let registry = TriggerRegistry::load_from_definitions(&defs).unwrap();
let scheduler = registry.cron_scheduler();
assert!(scheduler.is_some(), "should build a scheduler when cron triggers exist");
let scheduler = scheduler.unwrap();
assert_eq!(scheduler.trigger_count(), 2);
}
#[test]
fn test_platform_e2e_registry_no_cron_returns_none() {
let defs = vec![FunctionDefinition::new(
"validate",
"before:mutation:createUser",
RuntimeType::Deno,
)];
let registry = TriggerRegistry::load_from_definitions(&defs).unwrap();
assert!(
registry.cron_scheduler().is_none(),
"no cron triggers → cron_scheduler() returns None (zero-overhead fast path)"
);
}
#[test]
fn test_platform_e2e_before_mutation_chain_finds_correct_triggers() {
let defs = vec![
FunctionDefinition::new("validateName", "before:mutation:createUser", RuntimeType::Deno),
FunctionDefinition::new("checkDups", "before:mutation:createUser", RuntimeType::Deno),
FunctionDefinition::new("auditDelete", "before:mutation:deleteUser", RuntimeType::Deno),
];
let registry = TriggerRegistry::load_from_definitions(&defs).unwrap();
let observer = Arc::new(FunctionObserver::new());
let hooks = BeforeMutationHooks {
trigger_registry: registry,
module_registry: HashMap::new(),
observer,
};
let chain = hooks.trigger_registry.before_chain("createUser");
assert!(chain.is_some());
assert_eq!(chain.unwrap().triggers.len(), 2);
let chain = hooks.trigger_registry.before_chain("deleteUser");
assert!(chain.is_some());
assert_eq!(chain.unwrap().triggers.len(), 1);
assert!(hooks.trigger_registry.before_chain("updateUser").is_none());
}
#[test]
fn test_platform_e2e_all_trigger_types_coexist() {
let defs = vec![
FunctionDefinition::new("onUserCreated", "after:mutation:User:insert", RuntimeType::Deno),
FunctionDefinition::new("validateUser", "before:mutation:createUser", RuntimeType::Deno),
FunctionDefinition::new("dailyReport", "cron:0 2 * * *", RuntimeType::Deno),
FunctionDefinition::new("getMetrics", "http:GET:/functions/v1/metrics", RuntimeType::Deno),
];
let registry = TriggerRegistry::load_from_definitions(&defs).unwrap();
assert_eq!(registry.function_count, 4);
assert_eq!(registry.before_mutation_count(), 1);
assert_eq!(registry.cron_trigger_count(), 1);
assert_eq!(registry.http_route_count(), 1);
let scheduler = registry.cron_scheduler().unwrap();
assert_eq!(scheduler.trigger_count(), 1);
}
#[tokio::test]
async fn test_platform_e2e_cron_scheduler_starts_on_server_start() {
let defs = vec![
FunctionDefinition::new("neverFires", "cron:0 0 31 2 *", RuntimeType::Deno),
];
let registry = TriggerRegistry::load_from_definitions(&defs).unwrap();
let observer = Arc::new(FunctionObserver::new());
let scheduler = registry.cron_scheduler().expect("should have scheduler");
let handle = scheduler.start(observer, HashMap::new());
handle.stop();
tokio::task::yield_now().await;
}
#[test]
fn test_platform_e2e_realtime_observer_hook_is_accessible() {
use fraiseql_server::realtime::observer::RealtimeBroadcastObserver;
let (observer, _rx) = RealtimeBroadcastObserver::new(64);
let observer = Arc::new(observer);
assert_eq!(observer.events_dropped_total(), 0);
use fraiseql_server::realtime::delivery::{EntityEvent, EventKindSerde};
let event = EntityEvent {
entity: "User".to_string(),
event_kind: EventKindSerde::Insert,
new: Some(serde_json::json!({ "id": 1, "name": "Alice" })),
old: None,
timestamp: chrono::Utc::now().to_rfc3339(),
};
observer.on_mutation_complete(event);
assert_eq!(observer.events_dropped_total(), 0);
}
#[test]
fn test_platform_e2e_realtime_observer_drops_events_on_backpressure() {
use fraiseql_server::realtime::{
delivery::{EntityEvent, EventKindSerde},
observer::RealtimeBroadcastObserver,
};
let (observer, _rx) = RealtimeBroadcastObserver::new(1);
let make_event = || EntityEvent {
entity: "Post".to_string(),
event_kind: EventKindSerde::Insert,
new: Some(serde_json::json!({ "id": 1 })),
old: None,
timestamp: chrono::Utc::now().to_rfc3339(),
};
observer.on_mutation_complete(make_event());
assert_eq!(observer.events_dropped_total(), 0);
observer.on_mutation_complete(make_event());
assert_eq!(observer.events_dropped_total(), 1);
}
fn platform_e2e_available() -> bool {
std::env::var("FRAISEQL_PLATFORM_E2E").is_ok()
}
#[tokio::test]
#[ignore = "requires full platform stack (FRAISEQL_PLATFORM_E2E=1)"]
async fn test_e2e_before_mutation_validates_input() {
if !platform_e2e_available() {
eprintln!("skipped: FRAISEQL_PLATFORM_E2E not set");
return;
}
let base_url =
std::env::var("FRAISEQL_TEST_URL").unwrap_or_else(|_| "http://localhost:8000".to_string());
let client = reqwest::Client::new();
let mutation = serde_json::json!({
"query": "mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id } }",
"variables": { "input": { "name": "" } }
});
let response = client
.post(format!("{base_url}/graphql"))
.json(&mutation)
.send()
.await
.expect("request failed");
let body: serde_json::Value = response.json().await.expect("parse response");
assert!(
body.get("errors").is_some(),
"before:mutation abort should produce a GraphQL error"
);
let errors = body["errors"].as_array().unwrap();
assert!(!errors.is_empty(), "should have at least one error");
}
#[tokio::test]
#[ignore = "requires full platform stack (FRAISEQL_PLATFORM_E2E=1)"]
async fn test_e2e_realtime_subscription_receives_insert() {
if !platform_e2e_available() {
eprintln!("skipped: FRAISEQL_PLATFORM_E2E not set");
return;
}
todo!("requires WS client helper and running platform stack")
}
#[tokio::test]
#[ignore = "requires PostgreSQL and cron scheduler running"]
async fn test_e2e_cron_fires_and_persists_state() {
if !platform_e2e_available() {
eprintln!("skipped: FRAISEQL_PLATFORM_E2E not set");
return;
}
todo!("requires running PostgreSQL with _fraiseql_cron_state table")
}
#[tokio::test]
#[ignore = "requires full platform stack with Deno runtime (FRAISEQL_PLATFORM_E2E=1)"]
async fn test_e2e_http_trigger_calls_graphql() {
if !platform_e2e_available() {
eprintln!("skipped: FRAISEQL_PLATFORM_E2E not set");
return;
}
let base_url =
std::env::var("FRAISEQL_TEST_URL").unwrap_or_else(|_| "http://localhost:8000".to_string());
let client = reqwest::Client::new();
let response = client
.get(format!("{base_url}/functions/v1/user-count"))
.send()
.await
.expect("request failed");
assert!(response.status().is_success(), "HTTP trigger should return 2xx");
}
#[tokio::test]
#[ignore = "requires PostgreSQL + Deno runtime (FRAISEQL_PLATFORM_E2E=1)"]
async fn test_e2e_after_mutation_function_receives_event() {
if !platform_e2e_available() {
eprintln!("skipped: FRAISEQL_PLATFORM_E2E not set");
return;
}
todo!("requires full platform stack with observer pipeline active")
}