mockforge_graphql/
executor.rs1use async_graphql::http::GraphQLPlaygroundConfig;
4use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
5use axum::{
6 extract::State,
7 response::{Html, IntoResponse},
8 routing::{get, post},
9 Router,
10};
11use mockforge_observability::get_global_registry;
12use std::sync::Arc;
13use std::time::Instant;
14use tokio::net::TcpListener;
15
16use crate::GraphQLSchema;
17
18pub struct GraphQLExecutor {
20 schema: Arc<GraphQLSchema>,
21}
22
23impl GraphQLExecutor {
24 pub fn new(schema: GraphQLSchema) -> Self {
26 Self {
27 schema: Arc::new(schema),
28 }
29 }
30
31 pub async fn execute(&self, request: GraphQLRequest) -> GraphQLResponse {
33 let response = self.schema.schema().execute(request.into_inner()).await;
34 response.into()
35 }
36
37 pub fn schema(&self) -> &GraphQLSchema {
39 &self.schema
40 }
41}
42
43pub async fn start_graphql_server(
45 port: u16,
46 latency_profile: Option<mockforge_core::LatencyProfile>,
47) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
48 let addr = mockforge_core::wildcard_socket_addr(port);
49 tracing::info!("GraphQL server listening on {}", addr);
50
51 let app = create_graphql_router(latency_profile).await?;
52
53 let listener = TcpListener::bind(addr).await?;
54 axum::serve(listener, app).await?;
55
56 Ok(())
57}
58
59pub async fn create_graphql_router(
61 latency_profile: Option<mockforge_core::LatencyProfile>,
62) -> Result<Router, Box<dyn std::error::Error + Send + Sync>> {
63 let schema = GraphQLSchema::generate_basic_schema();
65 let executor = GraphQLExecutor::new(schema);
66
67 let mut app = Router::new()
68 .route("/graphql", post(graphql_handler))
69 .route("/graphql", get(graphql_playground))
70 .with_state(Arc::new(executor));
71
72 if let Some(profile) = latency_profile {
74 let latency_injector =
75 mockforge_core::latency::LatencyInjector::new(profile, Default::default());
76 app = app.layer(axum::middleware::from_fn(
77 move |req: axum::http::Request<axum::body::Body>, next: axum::middleware::Next| {
78 let injector = latency_injector.clone();
79 async move {
80 let _ = injector.inject_latency(&[]).await;
81 next.run(req).await
82 }
83 },
84 ));
85 }
86
87 Ok(app)
88}
89
90async fn graphql_handler(
92 State(executor): State<Arc<GraphQLExecutor>>,
93 req: GraphQLRequest,
94) -> GraphQLResponse {
95 let start = Instant::now();
96 let registry = get_global_registry();
97
98 registry.increment_in_flight("graphql");
100
101 let response = executor.execute(req).await;
103
104 registry.decrement_in_flight("graphql");
106
107 let duration = start.elapsed().as_secs_f64();
108 let status = if response.0.is_ok() { 200 } else { 400 };
109
110 registry.record_graphql_request("query", status, duration);
112
113 if !response.0.is_ok() {
114 registry.record_error("graphql", "graphql_error");
115 }
116
117 response
118}
119
120async fn graphql_playground() -> impl IntoResponse {
122 Html(async_graphql::http::playground_source(
123 GraphQLPlaygroundConfig::new("/graphql")
124 .title("MockForge GraphQL Playground")
125 .subscription_endpoint("/graphql"),
126 ))
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_graphql_executor_new() {
135 let schema = GraphQLSchema::new();
136 let executor = GraphQLExecutor::new(schema);
137
138 assert!(!executor.schema.schema().sdl().is_empty());
140 }
141
142 #[test]
143 fn test_graphql_executor_schema_getter() {
144 let schema = GraphQLSchema::new();
145 let executor = GraphQLExecutor::new(schema);
146
147 let retrieved_schema = executor.schema();
148 assert!(!retrieved_schema.schema().sdl().is_empty());
149 }
150
151 #[tokio::test]
152 async fn test_graphql_executor_can_execute() {
153 let schema = GraphQLSchema::new();
154 let executor = GraphQLExecutor::new(schema);
155
156 assert!(executor.schema().schema().sdl().contains("Query"));
158 }
159
160 #[tokio::test]
161 async fn test_create_graphql_router_no_latency() {
162 let result = create_graphql_router(None).await;
163 assert!(result.is_ok());
164 }
165
166 #[tokio::test]
167 async fn test_create_graphql_router_with_latency() {
168 let latency = mockforge_core::LatencyProfile::default();
169 let result = create_graphql_router(Some(latency)).await;
170 assert!(result.is_ok());
171 }
172
173 #[tokio::test]
174 async fn test_create_graphql_router_with_custom_latency() {
175 let latency = mockforge_core::LatencyProfile::new(100, 25);
176 let result = create_graphql_router(Some(latency)).await;
177 assert!(result.is_ok());
178 }
179
180 #[tokio::test]
181 async fn test_graphql_playground_returns_html() {
182 let response = graphql_playground().await;
183 let _html_response = response.into_response();
185 }
186
187 #[test]
188 fn test_graphql_handler_setup() {
189 let schema = GraphQLSchema::new();
190 let executor = Arc::new(GraphQLExecutor::new(schema));
191
192 assert_eq!(Arc::strong_count(&executor), 1);
194 }
195
196 #[test]
197 fn test_executor_arc_shared_ownership() {
198 let schema = GraphQLSchema::new();
199 let executor = Arc::new(GraphQLExecutor::new(schema));
200
201 let executor_clone = Arc::clone(&executor);
202 assert_eq!(Arc::strong_count(&executor), 2);
203
204 drop(executor_clone);
205 assert_eq!(Arc::strong_count(&executor), 1);
206 }
207
208 #[test]
209 fn test_executor_schema_contains_query_type() {
210 let schema = GraphQLSchema::new();
211 let executor = GraphQLExecutor::new(schema);
212
213 let sdl = executor.schema().schema().sdl();
214 assert!(sdl.contains("Query"));
215 }
216
217 #[test]
218 fn test_executor_schema_contains_user_type() {
219 let schema = GraphQLSchema::new();
220 let executor = GraphQLExecutor::new(schema);
221
222 let sdl = executor.schema().schema().sdl();
223 assert!(sdl.contains("User"));
224 }
225
226 #[test]
227 fn test_executor_schema_contains_post_type() {
228 let schema = GraphQLSchema::new();
229 let executor = GraphQLExecutor::new(schema);
230
231 let sdl = executor.schema().schema().sdl();
232 assert!(sdl.contains("Post"));
233 }
234}